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.
Entry points
Section titled “Entry points”| 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.
CLI surface
Section titled “CLI surface”| 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.
The input mode is the surface
Section titled “The input mode is the surface”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.
Export map
Section titled “Export map”./schema.json
Section titled “./schema.json”| Artifact | Notes |
|---|---|
polydeukes.schema.json |
The config schema copy that ships with the umbrella package. |
The judge (covenant module)
Section titled “The judge (covenant module)”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.
What the module owns
Section titled “What the module owns”| 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 |
Discipline families and meta-covenants
Section titled “Discipline families and meta-covenants”A disciplines: entry is one declaration — judge = 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.
Where the consumer touches it
Section titled “Where the consumer touches it”- 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
witnessblock, 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.
Declared limits
Section titled “Declared limits”- The shell axis leaves
skippedrows, 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 inskippedis the declared limit showing itself. A pass with no row at all, or one recordedpassedwithout 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 ownsupply: passdisposes of that with askippedrow carrying the reasonsupply-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
commandor transcript declaration is not compiled for a change set, and achangesdeclaration 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 frompdks explainand 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
supplypolicy 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.
Failure boundaries
Section titled “Failure boundaries”runCovenantCheck()never throws; it resolves to{ exitCode: 0 \| 2 }.- The numeric codes are
EXIT_UPHOLD(0),EXIT_BREAK_NON_BLOCKING(1), andEXIT_BREAK_BLOCKING(2) from@polydeukes/core. The umbrella runners expose only0or2; they never return1. pdks covenant checknever prompts. It reads stdin and exits 0 or 2; the caller decides what that exit code means.pdks docsandpdks explainprint nothing partial on failure.