Skip to content

polydeukes

The umbrella package is the only package a consumer installs. It ships the pdks bin, the judge, both surfaces’ composition roots, the session-surface runner subpath, and the bundled schema artifact.

Specifier What it is
pdks / polydeukes The executable. One CLI under two names in bin
polydeukes/schema.json The bundled config JSON Schema

There is no . entry point. import 'polydeukes' fails with ERR_PACKAGE_PATH_NOT_EXPORTED; what a consumer reaches is the bin and the schema file. A session surface reaches this package the same way a shell does — by spawning pdks covenant check — so an agent adapter takes it as a peer dependency rather than importing it.

Command Purpose
pdks covenant check Judge an input IR (default, the session surface) or a unified diff (--diff, the change-set surface) from stdin
pdks init Create the project scaffold: config file and telemetry ignore line
pdks explain Render the assembled registration table without judging
pdks docs [topic] Read a bundled topic
pdks docs search <query> Search the bundled docs
pdks docs show <document-id> Show one bundled document or section

Session-surface installers live on the adapters: pdks-claude-code init, pdks-grok init, and pdks-codex init.

covenant check reads its surface off how the input arrived, never off the input’s own keys. An IR on stdin is one call a host observed before it ran, so the run is the session surface; --diff is a finished change set from some producer, so the run is the change-set surface. Each surface compiles the shared list plus its own:

Input Surface Lists compiled
an input IR on stdin session disciplines, then sessionDisciplines
a unified diff on stdin under --diff changeSet disciplines, then changeSetDisciplines

The IR’s session key says something else: what the host proved about the call — the witness valve’s human turns, the transcript axis, and the pre-state channel shell-write evidence needs. A host that carries no session (the Grok adapter) is still judged on the session surface, and what it lacks is the evidence those three read, not the surface. pdks explain prints each surface with its two list names and counts.

pdks docs is offline. It reads the installed package, not the network. Flags, JSON, and exit codes are in pdks docs.

Artifact Notes
polydeukes.schema.json The config schema copy that ships with the umbrella package.

The judge is the umbrella’s src/covenant/ module. Everything that turns a declared promise into a verdict lives there, and both composition roots plus pdks explain import it directly. There is nothing to install, nothing to import, and no peer to satisfy: what you reach is its behaviour, through the disciplines: block in your config and the rows it writes to .polydeukes/roi.log.

Unit What it does
runCovenant wrapper Runs a judge body, translates its non-blocking 1 into the blocking 2, and logs every call. No covenant runs unmeasured
Path-routing dispatcher Registers covenants against protected paths and runs every matching one — no short-circuit, so the telemetry never under-counts
Meta-covenants Three registrations that protect the judging chain itself
TTL witness The time-boxed human valve, consulted only after a verdict blocked
Delta layer New-violation-only judgment over a file’s before/after pair
Discipline library Config disciplines: entries become enforcement without a line of code

A disciplines: entry is one declarationjudge = relate ∘ extract over the observation as a world. What the declaration’s sources bind decides what evidence the judgment needs, which is also what decides whether it can be judged on a given surface.

Sources Judges Evidence needed List
the fixed names target.path · pre · post · state The change itself A file change disciplines
the fixed name changes The observation’s whole change set A finished change set changeSetDisciplines
the fixed name command The shell call’s command line A shell call — an Edit carries none sessionDisciplines
the fixed name actor Who made the observation An actor the host proved sessionDisciplines
{ transcript: true } Session history — was a qualifying call actually executed before this one A session sessionDisciplines
{ file: … } Another file The surface’s reader for it disciplines
{ sidecar: true } The spawn-record channel The surface’s channel reader sessionDisciplines

The writing guide for these entries is the configuration reference’s disciplines section; the declaration grammar is the core’s algebra-declaration.schema.json.

Three meta-covenants protect the judging chain. They are covenants like any other; the vocabulary below applies to them unchanged.

Registration Axis Judges
self-mod Tool Mutations to protected paths through editing tools. Only the call’s proven mutation target is compared — a protected path inside an edit’s content is a mention and passes
shell-mod Shell The same, through a command line. A command mentioning a protected path passes only with read-only proof: an allowlisted leading word, or a supported argument-sensitive reader such as git ls-files, find without mutation, execution, prompting, or file-output actions, or numeric-range sed -n …p
transcript-mod Transcript Writes to the live session transcript, judged by whole-path equality — never as a protected ancestor

Six words are the telemetry contract — five verdicts and one observation. A row in .polydeukes/roi.log carries exactly one of them, and the CLI, the docs, and the tests use the same word for the same event. How to read a row is in troubleshooting.

Verdict Means
passed The call was judged and upheld the covenant
blocked The call was judged and broke it
witnessed A blocked verdict a human opened in person. Never silent, never a clean call
advised A break recorded without stopping the call — the default for every discipline entry on both surfaces, unless the entry itself says enforce: block
skipped The call reached a registration that could not judge it. Not a pass — the recorded absence of a judgment
unattributed A protected entry’s on-disk state moved and no judgment row explains it. Not a verdict — no call is blocked or passed by it; the session surface writes it after comparing state against a stored baseline

unattributed answers a question the other five cannot. They are all written by a judge about a call it was handed, so a write that arrives without a declared call — through an interpreter, a test runner’s child process, a script that assembles the path from its own arguments — leaves no row at all. The comparison observes the result rather than the spelling, so it records that write after the fact. It never blocks: the write already happened, and the comparison fails open on both sides of the verdict.

  • The disciplines: block in your config. One entry compiles into one registration, carrying its own telemetry label.
  • protectedPaths, which the path-routing dispatcher matches against.
  • The witness block, which arms the TTL valve.
  • .polydeukes/roi.log, where every judgment lands as one row.

No import. The umbrella assembles the module for both surfaces.

  • The shell axis leaves skipped rows, and that row is the contract. Predicting a shell command’s target from its text is undecidable, so the invariant this axis holds is not “nothing gets through” — it is that no call passes unrecorded. A new spelling landing in skipped is the declared limit showing itself. A pass with no row at all, or one recorded passed without a judgment, is the defect class.
  • A session-only declaration on a host that proves no session records skipped. The entry is compiled — it is on the session surface — but the transcript the declaration binds is absent, and the entry’s own supply: pass disposes of that with a skipped row carrying the reason supply-pass. With no policy the missing session is unjudgeable (exit 2), not an automatic skip.
  • A surface never registers the other surface’s list. A command or transcript declaration is not compiled for a change set, and a changes declaration is not compiled for one call, so neither leaves a row on the surface that does not observe it. Which entries a surface carries is read from pdks explain and from the config’s own list names, not from the telemetry log.
  • A declaration the compiler cannot resolve compiles to a skip registration — routing intact, no body: a step outside the registry, an argument outside a step’s keys, a pattern that does not compile, a paired/single mismatch. Assembly therefore never throws: one unresolvable entry cannot take down its siblings, the meta-covenants, and the valve, which would leave no way to fix the config that caused it. A reserved mechanism, or one whose axes and relations the catalogue refuses, is a different stage: config loading rejects the file (exit 2), and no skip row is written. A source the world lacks at judgment time is a third case — the declaration’s own supply policy disposes of it, and with no policy the body answers unjudgeable (exit 2), never upheld.
  • Complete containment is a non-goal. There are no blocklists here — enumerating bypass spellings is always one step behind, so the logic is inverted: a mention of a protected path blocks unless proven safe. Residual vectors such as indirect path computation are telemetry targets, not block targets. The two friction valves — read-only proof and the TTL witness — both leave a measurable trace.
  • The valve stands after the verdict. Only a judgment that actually blocked can be witnessed open, a mid-sentence mention of the token does not arm it, and an AI can never open the valve for itself.

  • runCovenantCheck() never throws; it resolves to { exitCode: 0 \| 2 }.
  • The numeric codes are EXIT_UPHOLD (0), EXIT_BREAK_NON_BLOCKING (1), and EXIT_BREAK_BLOCKING (2) from @polydeukes/core. The umbrella runners expose only 0 or 2; they never return 1.
  • pdks covenant check never prompts. It reads stdin and exits 0 or 2; the caller decides what that exit code means.
  • pdks docs and pdks explain print nothing partial on failure.