Skip to content

The Vows — Consumer Contract

What your project owes, written where your project can read it.

A Vow is an obligation the machine has not forgotten. Not a TODO comment nobody greps, not a line in a chat transcript that scrolled away — a row in an append-only register that every read surface in DivineSWE consults before it tells you the coast is clear.

This document is the contract between DivineSWE and your repository. It is written for somebody who has installed the plugin and nothing else: no Books, no cycle, no orchestration. Every shell block below is a runnable recipe — copy it, paste it, it exits zero. Illustrative output lives in json and text blocks and is never something you are asked to run. Where the register deliberately refuses, the refusal is described in prose, because a contract that ships a command its own reader cannot run is a contract that lies.

Runnable recipes use only this declared placeholder vocabulary: <vow-id>, <book>, <evidence>, <item>, and <repo> for ordinary consumer operations, plus <vows-dir> for the configured-dwelling move. $CLAUDE_PLUGIN_ROOT and ${CLAUDE_PLUGIN_ROOT} name the installed plugin root and are substituted by the consumer’s environment.

The governing invariant of the whole register, stated once and honoured everywhere below:

An unreadable source reports UNKNOWN — never zero, never clean. “Nothing is owed” and “I could not look” are different facts, and this register will never render them as the same bytes.


Start here — the .divineswe/ prerequisite

By default, the register lives at .divineswe/vows.jsonl, inside your repository. The project-only vows_dir setting may give it another dwelling; the filename remains fixed as vows.jsonl. A default or in-tree dwelling is per-project, so two checkouts of two projects do not read each other’s debts. An absolute dwelling is a deliberate escape hatch: if two projects name the same absolute dwelling, the resolver and facade permit that shared register, so one project can list the other’s vows and a vow judge attestation in one can discharge the other’s debt. Choose that shared location only when cross-project state is intentional.

A repository that has never met DivineSWE has no .divineswe/ directory, and vow open will refuse it by name — directory not found: <your repo>/.divineswe. That refusal is deliberate: scaffolding a state directory as a side effect of recording a debt would silently create orchestration state in whatever directory you happened to be standing in.

So the first step is the directory. Run this from your repository root:

Terminal window
mkdir -p .divineswe

That is the whole prerequisite for the Vows. The /divineswe:divineswe-init skill does this and more — it seeds a config, wires the statusline, creates ~/.divineswe/, installs the permission ask rule that the judge workflow below depends on, and performs the two git steps in the next section — and it is the recommended path for a project that intends to run cycles. The one line above is the minimum a consumer needs to start recording obligations today.

Committing the register — two lines, and why they are not optional

.divineswe/vows.jsonl is the one file under .divineswe/ that is meant to be committed. Everything else there is ephemeral orchestration state; this is the durable record of what your project owes, and an obligation that exists only on the machine that recorded it is one the next clone has already forgotten.

Two git facts have to be true, and /divineswe:divineswe-init writes both (Step 3.5, idempotently). If you took the one-line mkdir path above instead, they are two lines you add by hand — no command to run, just two files to edit.

Add this line to .gitattributes at your repository root:

.divineswe/vows.jsonl merge=union

And only if your .gitignore already sweeps .divineswe/, add these three lines after that entry:

!.divineswe/
.divineswe/*
!.divineswe/vows.jsonl

All three, in that order. Git will not re-include a file whose parent directory is excluded, and .divineswe/ excludes the directory — so a lone !.divineswe/vows.jsonl after it is a no-op: git check-ignore still reports the register ignored and git add still refuses it. The first line re-includes the directory, the second excludes its contents again (a trailing /* matches children, not the directory), and only then can the negation bite. Everything else under .divineswe/ stays ignored.

Check it rather than trusting it: git check-ignore -q .divineswe/vows.jsonl must exit 1 (nothing ignored). If it exits 0, the register is still swept up and no merge=union attribute can help, because git is never tracking the file at all. divineswe-init Step 3.5 runs exactly that check and reports what git said, rather than announcing success because it appended a line.

The merge=union attribute is the load-bearing one. The register is append-only, so two branches can only ever have added different lines — precisely the case union merge resolves correctly, by keeping both sides. Without it, two agents in two worktrees recording vows hand you a conflicted register at merge time, and the natural resolution (“take one side”) silently discards the other branch’s obligations.

Union merge is safe here only because the fold is order-independent: the same event set folds to identical state under any interleaving, duplicate lines are idempotent, and a torn line is skipped and counted rather than laundered into a forged event. If any of those ever ceases to hold, the attribute becomes a hazard rather than a convenience.

The negation line matters only if your .gitignore already sweeps .divineswe/. If it does and you skip it, the register is never committed, and the first clone of your repository starts life owing nothing at all.

Every command in this document is run from the repository root. To drive a repository somewhere else, name it explicitly with --base-path rather than trusting the working directory — see the machine-read surface below.

Moving the register to a configured vows_dir dwelling

Changing vows_dir does not migrate an existing register automatically. For a relative dwelling inside the repository, this is a one-time, manual move. Update the project config first, then run these steps from the repository root, in this order:

  1. Create the configured dwelling. A configured directory that does not exist is discovered as missing only when a write reaches it, so create it before moving the register:

    Terminal window
    mkdir -p <vows-dir>
  2. Move the tracked register with git mv, preserving its history and tracked identity:

    Terminal window
    git mv .divineswe/vows.jsonl <vows-dir>/vows.jsonl
  3. Move the .gitattributes rule as part of the same migration. Remove the old .divineswe/vows.jsonl merge=union line and replace it with the line below. Move the rule; do not merely add a second line, because leaving the old path behind creates a stale attribute and leaves the new, append-only register without union merging:

    <vows-dir>/vows.jsonl merge=union

If .gitignore already sweeps the new dwelling, update its re-inclusion rules for the new path as well. Re-run git check-ignore -q <vows-dir>/vows.jsonl and confirm it exits 1; an exit of 0 means the moved register is still ignored and cannot be committed. This is a manual operation: DivineSWE does not perform the move for you.

An absolute vows_dir is a deliberate escape hatch and is honoured by the resolver. When that absolute dwelling sits outside the repository work tree, its vows.jsonl register is local-only and uncommittable: Git cannot track or commit it, .gitattributes and .gitignore cannot make it part of the repository, and it will not survive a clone. Do not treat the in-tree git mv recipe above as a way to make an out-of-tree register shared; the local machine remains its only home.


Enabling the gates

Two gate keys govern this layer. vows_enabled ships live — you do not have to switch anything on to start owing work, and you switch it closed when you want a project to go dark. decrees_enabled ships closed since the Decrees were deprecated.

  • vows_enabled — default true. The obligation register: .divineswe/vows.jsonl, every vow verb, and every read surface that counts owed work.
  • decrees_enabled — default false. The initiative layer of committed briefs above the Books, deprecated with the Book cycle; a project that keeps Decrees sets it true.

The Vows’ polarity is deliberately opposite to DivineSWE’s three memory rites. A register you have to opt into is a register that is empty exactly where it matters — on the machine of the operator who never heard of it. For completeness, the other six gates and their defaults:

  • atlas_enabled — default true.
  • ledger_enabled — default true.
  • scrolls_enabled — default true.
  • offerings_enabled — default false.
  • retrospective_enabled — default false.
  • teachings_enabled — default false.

vows_enabled and decrees_enabled are PROJECT-ONLY

Both keys resolve from your project’s own .divineswe/config.json and nowhere else. A declaration in the machine-wide ~/.divineswe/config.json is inert for these two: obligation state and committed intent are per-repository facts, so no one machine-wide file may speak for every repository on the machine. If you set vows_enabled in your home config and wonder why nothing changed, this is why — you set a key that layer does not carry.

Only four keys are globally settable: offerings_enabled, retrospective_enabled, teachings_enabled and atlas_enabled.

To take one project dark, write its own config:

{
"vows_enabled": false
}

Only strict booleans count. "false" in quotes is a typo, never consent, and a key the resolver cannot read is a key it treats as unwritten.

A closed gate is SUPPRESSED, not empty

This is the part worth internalising before you close anything. A closed vows_enabled does not make vow list answer “0 vows”. It makes it answer “the register was not read”, at exit 0, in whichever format you asked for:

{"vows":[],"suppressed":true,"reason":"vows_enabled is false for this project — the register was NOT read. This is a SUPPRESSED count, not an empty one.","warnings":["vows_suppressed_by_gate"]}

Writes behave the same way: a suppressed write is a no-op that names the transition it discarded on stderr. And an argv mistake stays exit 1 in both gate states — the gate suppresses the write, never the diagnosis.


The gate-dependency matrix

The Decrees are a layer built over the Vows: a Decree reaches done only through a judgment recorded as a vow, and the degradation ladder’s middle rung — “skip loudly and record the debt as a vow” — has nowhere to write when the register is dark. So one of the four combinations is incoherent, and the resolver says so.

vows_enableddecrees_enabledVerdictWhat you get
truetrueCoherentBoth layers live, for a project that still keeps Decrees.
truefalseCoherentObligations recorded; no initiative layer above them. The shipped shape.
falsefalseCoherentBoth dark. Reads report SUPPRESSED, never empty.
falsetrueIncoherentThe Decree layer is stranded over a register it cannot write to.

The incoherent pair is reported, never silently repaired. The resolver emits dependent_gate_without_substrate:decrees_enabled and leaves both declarations exactly as you wrote them. Silently closing the dependent gate would make your config file and the tool’s verdict disagree; silently opening the substrate would manufacture consent your project never gave. You are told; you decide.

The rule reads the resolved verdict, not the raw text. A malformed vows_enabled is an absent one, so it falls back to its own default and the pair is coherent again.

To see what actually resolved on your machine, including the silent failures, run the resolver directly:

Terminal window
node $CLAUDE_PLUGIN_ROOT/scripts/shared/rite-gates.mjs .

It prints one JSON line carrying the resolved gates and a warnings[] array, and always exits 0.


Recording an obligation

One verb, four required flags:

Terminal window
node $CLAUDE_PLUGIN_ROOT/scripts/divineswe-state.mjs vow open --item "<item>" --kind deferral --owner operator --source operator --book <book> --trigger "when the release checklist is next opened"

It prints the assigned id — vow opened: vow-91-a86d27e1a206.

FlagRequiredValues
--itemyesFree text. The wording IS the identity — see below.
--kindyesdeferral, post_seal_step, precondition, validation, backlog
--owneryesoperator, agent — whose inbox this lands in
--sourceyesseal, retrospective, consult, operator, backfill, audit, rca
--book / --prophecynoBare non-negative integers scoping the debt to the work that spawned it
--triggernoWhen a human should next look at it. Inert prose.
--preconditionnoWhat must hold first. Inert prose.
--duenoAn ISO instant
--prioritynoHow soon it should be paid: a whole number from 0 (the highest priority) to 10000 (the lowest). Change it later with vow update --id <id> --priority <n>; vow surface lists owed vows by it, unprioritized ones last
--if-absentnoThe REPLAY contract — see Backfilling

The id is derived, not minted. It is a hash of the normalized tuple [book, prophecy, kind, item], which is what makes recording the same obligation twice fold to one vow rather than two. Two consequences follow, and both surprise people:

  • An open may resolve to a vow that already exists. Against a still-owed vow it says so on stderr and adopts it.
  • Against a discharged vow it refuses, by name, at exit 1. An opened event is not a terminal transition, so recording one against a settled debt would report success while the re-incurred obligation stayed quietly settled. If the debt has genuinely been re-incurred, record it as a distinct obligation: a --book / --prophecy scope naming the occasion, or wording that describes the new instance.

For the one-line path — the shower-thought capture — the /divineswe:vow-capture skill supplies --kind backlog --owner operator --source operator for you, so all you have to say is the thing you owe.


The machine-read surface (--json)

Two documents, one rule: --json carries its document alone on stdout, and nothing else. Diagnostics — a torn register line, an orphan transition, a suppressed transition — go to stderr on every verb, reads and writes alike, so the answer stays parseable while the register’s condition stays audible.

vow list --json — the rows

Terminal window
node $CLAUDE_PLUGIN_ROOT/scripts/divineswe-state.mjs vow list --json
{"vows":[{"v":1,"id":"vow-91-a86d27e1a206","item":"pin the release checklist","owner":"operator","kind":"deferral","source":"operator","declared_status":"open","status":"open","effectiveStatus":"open","effective_status":"open","book":91,"trigger":"when the release checklist is next opened","created_at":"2026-08-03T19:45:48.705Z","updated_at":"2026-08-03T19:45:48.705Z"}],"blind":false,"malformed":0,"home_residue":"","warnings":[]}

home_residue is the L14 window on the machine surface: the empty string means the superseded home follow-up register holds nothing this register lacks, and a non-empty one describes what it still holds. Check it before you trust a zero, exactly as you check blind and suppressed — a debt that never migrated is invisible in vows[] by construction. A register that could not be opened at all — unreadable, symlinked, or sitting in a directory this process cannot search — answers blind: true with a reason, never blind: false with an empty vows[]; the read verbs stay exit 0 on that shape (A23), so a consumer must branch on the field rather than on the exit code.

The fixed filters --owner, --book, --kind, --status and --declared-status AND together, and an out-of-enum value is refused loudly rather than matching nothing — a filter that quietly matched nothing would report a clean empty register, which is the most dangerous wrong answer this surface can give.

Three status spellings, one meaning each, and the reason to care is fraud detection:

  • declared_status — what the register was told.
  • effective_status (and its effectiveStatus twin) — what the register believes, with the read-side evidence gate applied.
  • status — the safe-by-default spelling.

A row reading declared_status: "closed" beside effective_status: "open" says somebody moved to dismiss this debt and showed nothing for it. That is why both survive into the projection: publishing only the belief would erase the very signal the evidence gate exists to raise.

Two shapes mean “I could not look”, and neither is an empty list: suppressed: true (the gate is closed) and blind: true (the register exists and nothing survived the fold). Check for them before you trust a zero.

To describe a repository other than the one you are standing in, name it — never rely on the working directory:

Terminal window
node $CLAUDE_PLUGIN_ROOT/scripts/divineswe-state.mjs vow list --json --base-path <repo>

scry --json — the deprecated wider report

The Scry — deprecated; read what is owed with vow surface — is the wider read: every source this repository owes something from, collapsed into one document.

Terminal window
node $CLAUDE_PLUGIN_ROOT/scripts/divineswe-state.mjs scry --json
{
"version": 1,
"generated_at": "2026-08-03T19:46:00.001Z",
"base_path": "/path/to/your/repo",
"tier": "core",
"purity": "read-only",
"sections": [
{ "id": "open_vows", "title": "Open vows", "status": "ok", "state": "ok", "items": [], "count": 0 },
{ "id": "unmerged_branches", "title": "Unmerged branches", "status": "unknown", "state": "unknown",
"reason": "git could not answer for this repository: fatal: not a git repository" }
],
"pending_heals": [],
"warnings": []
}

Read the shape, because the shape is the honesty guarantee:

  • sections is an array, because a report has a reading order — what a human owes before what a machine noticed.
  • Every section declares state: ok, unknown, or suppressed. There is no fourth answer.
  • An unknown section carries no number at all. Not a zero, not a null — the numeric field is absent by construction, so a consumer cannot accidentally render a confident zero over a blind sensor.
  • suppressed means a gate closed the source, so it was never read. Not applicable is not could not see.

Plain scry writes nothing. That is a property of the module’s static import graph, not a promise about its behaviour: scripts/scry.mjs links no register writer, no schema gate and no lifecycle writer, and every git call it makes runs with optional locks disabled so even an index refresh cannot touch a byte. Two flags are the documented exceptions, and both are explicit opt-ins: --format html is the one write (a self-contained page at .divineswe/reports/scry.html), and --heal is a delegation across a process boundary to the mutation verb vow reconcile — the writes are that verb’s, made under its own name.

For the human reading, vow surface is the default; the deprecated /divineswe:scry-project still runs the Scry’s prose surface and relays its verdict verbatim.


Backfilling obligations you already owe

A register that starts empty starts lying. If your project already carries deferred work — in a spec, a stale TODO sweep, a release checklist, an issue tracker you are leaving behind — record it, and record where it came from.

Three provenance fields do that work:

  • --source backfill — this obligation was recorded retroactively off an outside record, not raised by a seal, an audit or a consultation. It is the honest label, and it is what lets a later reader tell an obligation the pipeline observed from one a human transcribed.
  • --book <book> (and --prophecy where it applies) — the work the debt belongs to. Scope is part of the derived id, so two prophecies of one Book may defer identical wording without collapsing into one row.
  • --trigger — the occasion on which a human should next look at it. Carried verbatim, evaluated never.
Terminal window
node $CLAUDE_PLUGIN_ROOT/scripts/divineswe-state.mjs vow open --item "<item>" --kind backlog --owner operator --source backfill --book <book> --trigger "when this obligation next comes up" --if-absent

--if-absent is the replay contract, and it is why a backfill script is safe to re-run. The flag exists because two different callers use one verb. An operator typing vow open is asserting a debt: if the register will not record it, that invocation failed and must exit 1. A replay — a backfill loop, a re-run seal, a reconcile walking an archived Book — is asking a different question, “is this obligation on the register”, and an already-settled one is a legitimate no-op for it. Answering that with exit 1 would abort a set -e loop at the first discharged line and leave every remaining obligation unreconciled: the same missing-debt failure wearing a loud exit code.

With --if-absent, an existing tuple is never fatal and never appended against, and stdout says which case it was, so a loop can tell a skip from a failure without parsing a message:

vow opened: vow-91-a86d27e1a206
vow open skipped (already open): vow-91-a86d27e1a206
vow open skipped (already discharged): vow-85-6c7619588162

Pick your --kind honestly, because it decides which surface the debt appears on: backlog for an unscheduled debt somebody thought of, deferral for work a cycle consciously put down, precondition for something that must hold before other work may start, post_seal_step for a chain step after a release, validation for a claim awaiting a judgment.


The judge workflow

A judgment is the one thing in this system a machine may not do.

vow judge renders a verdict — validated or rejected — against one or more vows, on the record, with evidence:

Terminal window
node $CLAUDE_PLUGIN_ROOT/scripts/divineswe-state.mjs vow judge --id <vow-id> --verdict validated --evidence "<evidence>" --actor operator
vow: judge: "operator" (supplied; process observed as state-cli:dev@host:8121) rendered the verdict "validated" on 1 vow(s): vow-91-a86d27e1a206
vow judged 1: vow-91-a86d27e1a206

The rules, in the order they bite:

  1. Exactly one selector. Either --id <id> — comma-separated for several, because a repeated flag keeps only its last value and a multi-id verb that judged one vow while reporting success would forge a verdict for the rest — or --book <N>, which judges that Book’s still-open validation vows and never reaches another Book’s. A --book sweep is a mass discharge whose blast radius is invisible in the argv, so the resolved subject list is printed on stderr, by id, before a byte is appended.
  2. Evidence is required in substance. A pointer — a file, a commit, a URL, an artifact. n/a, - and tbd are refused: they are the shape of evidence without the content, and the register weighs the pointer carried by the terminal transition, never one the vow accumulated earlier.
  3. validated is an evidence-backed close. rejected leaves the debt owed and records why.
  4. The verdict carries provenance. actor is what --actor claimed; actor_derived is the OS identity the process actually ran as, recorded alongside rather than instead of it; actor_source says which is which. That is a record, not a proof — a local process can claim anything — but “agents never render judgment” is only auditable after the fact if the register says who did.

The attestation ask-rule — read this before you script around it

vow judge and decree advance --to done are carved out of DivineSWE’s blanket facade auto-approval. Every other verb of the state CLI is granted at the script level; these two are never granted by any shipped artifact, and divineswe-init writes an ask permission rule for both so the prompt is mechanically reached.

The CLI backs that up rather than relying on it. It refuses both verbs from a detected agent context, at the argv seam and again in the store that performs the transition, with no override flag — --force, --as-operator and --allow-agent are all rejected as unregistered, because a documented door through an attestation guard is the guard’s absence with extra steps.

Two signals trigger the refusal, either sufficient:

  • the ambient marker DIVINESWE_AGENT carrying any non-blank value — 0, false and off all read as “an agent is speaking”; the only way to say “not an agent” is to unset or blank the variable;
  • an --actor that normalizes to a DivineSWE agent name (prophet, oracle, preacher, atlas, thoth, monkey, 4eyes, styx, mason, loki).

A refusal names the signal it fired on, so a false positive is clearable. The matching is by word token, not substring, so a human handle that happens to contain an agent’s name is not accused for it — and the derived OS identity is never weighed at all, so no username or hostname can accuse its owner.

Be honest with yourself about the tier this buys: raised cost plus a reconstructible record, never impossibility. The prompt exists on claude-code and copilot-cli and not on opencode or codex-cli; a blanket allow rule you add yourself re-opens both verbs; and one blanked environment variable defeats prefix matching. It is stated here at its real size rather than its rhetorical one.


Reconciling the register

vow reconcile is the mutation verb — the counterpart to the pure-read Scry. One pass stamps, closes, heals and advances, because a second pass is a second chance to skew:

Terminal window
node $CLAUDE_PLUGIN_ROOT/scripts/divineswe-state.mjs vow reconcile
vow reconcile — plan
archive: TABLETS.md — absent · 0 Books in scope
horizon: gated · considered 0 Book(s) · excluded 0
closes: 0
still owed: 0
stamps: 0
heals: 0 re-opened from the archive · 0 orphan(s) reported, none closed
applied: 0 transition(s), 0 decree advance(s)

The second line names the archive the pass read — TABLETS.md at the project root, the path book seal-bookkeeping writes — and whether it was there (— present when it is). Only the archive brings a Book into scope, so over an absent archive the pass weighs no seal vow at all: when the register still owes one, the still-owed line names each such vow as out of scope instead of printing a count — still owed: out of scope — 1 owed seal vow(s) not weighed, because the absent archive brings no Book into scope: vow-94-…. With --json, the same facts ride the plan’s archive field: present, path, a reason naming an absent archive, and out_of_scope.

Three flags and no more: --deep widens the horizon to the full history, --json publishes the plan verbatim (the same document the Scry reports), and --cheap is the offline variant that makes no network call — it still stamps, heals and advances, and it closes nothing, which is the honest answer for a pass that never asked a remote anything.

The close predicate has exactly two arms. verified is an ls-remote observation that a remote ref contains the stamped commit — tip-equals or provably-descends — and it is the only strength a discharge may be minted with. unknown is everything else and closes nothing: an unreachable remote is not a discharge, and neither is a containment question this clone cannot decide without a fetch.

A third arm used to exist, closing on a local remote-tracking ref, and it was deleted. A local tracking ref is a local file any process can write, so it is never affirmative observation of a remote — git remote add origin /tmp/nope.git plus one git update-ref satisfied it outright. The label it minted, approximate, remains readable but unmintable: rows a previous release discharged with it exist on real registers, so the field still rides the fold and vow list’s prose row still prints the word beside the status. Nothing new will ever wear it again.

Re-running is a no-op at exit 0. An absent archive, a directory that is not a git repository, an unreachable remote, a malformed brief and a closed gate all produce a plan plus warnings at exit 0 — a verb that failed when there was nothing to do would abort a set -e loop on a perfectly healthy repository.


Migrating off the home follow-up register

Before the Vows, deferred debts were recorded in a home-scoped follow-up ledger at ~/.divineswe/follow-ups-<project>.jsonl. That register is retired. This release ships the one-time consumer operation that carries it across:

Terminal window
node $CLAUDE_PLUGIN_ROOT/scripts/divineswe-state.mjs vow migrate
vow migrate: 3 follow-up entries migrated into the vow register
note: the home register CLAIMS "superseded" for consumer: replace the ad-hoc TODO sweep and carries NO evidence pointer — migrated as OPEN.

One flag, --json, which publishes the pass verbatim. There is deliberately no --home, no --project and no --force: the file it consumes is named by provenance the migration itself recorded, and a --force is a flag whose only function is to skip the checks that keep an obligation from vanishing.

The mapping table

Follow-up statusVow outcomeWhy
openopen, no evidenceAn owed debt stays owed.
closed with an evidence pointerclosed, pointer carried verbatimA backed discharge is a discharge.
closed, no pointeropen — and named on the reportAn unbacked dismissal is not a discharge.
superseded with an evidence pointerwaived, pointer carried verbatimDeliberately dropped, on the record.
superseded, no pointeropen — and named on the reportSame rule; a claim is not a proof.

The two unbacked rows are the point of the whole table. The retiring register’s own read gate already resurfaced an unbacked dismissal as open, so trusting the declared status here would launder a hand-written dismissal into a permanently discharged vow — a debt the source still surfaced becoming one no surface ever shows again, with the migration as the step that did it.

Every migrated debt lands as kind: deferral (that ledger held nothing else) from source: backfill (an obligation recorded retroactively off an outside record), owned by agent unless the source row declared otherwise. item and trigger are carried byte-for-byte — neutralization is a render concern, and a store that rewrote them would have changed what the obligation says.

Safe to run twice, safe to interrupt

Four properties, each earned:

  • The destination is the only authority. What to mint is decided against your register’s own events, never against the presence of a stamp — so a home file carrying a stamp and unmigrated rows is migrated rather than greeted as finished.
  • Vows are written first, the stamp last. Vows-then-crash converges on a re-run; stamp-then-crash would not.
  • An unreadable source reports UNKNOWN, exits non-zero, and is never stamped. Stamping a file nobody read declares a migration that never happened and hides every debt behind the watermark.
  • The consumed register’s NAME is recorded on every migrated event as origin_ref. The home register is keyed on the project directory name, so a worktree, a renamed clone or a second checkout would otherwise re-derive a different name, find nothing, and report clean forever. A name, never a path: this register is committed and shared, so a path in it would be attacker-supplied input to a read on every clone — and it would name a home directory that does not exist on any machine but the one that wrote it.

vows_enabled closed suppresses the whole pass — migrating into a register that discards every transition and then retiring the source is the two-write operation completing exactly the half that destroys. retrospective_enabled closed does not block the read (a closed rite must never make a live obligation look like an empty file) but withholds the stamp and says so: the pass may not declare a rite retired while the rite is switched off.

The stale-append window, which is yours to watch

The home file is stamped superseded — never deleted. That is deliberate, and it leaves a real window: a lagging plugin elsewhere on the machine — an older install, a second checkout, a version-skewed agent — can still append to a file this repository has stopped writing to. A deleted file would turn that append into a silently lost obligation.

So the vow read surfaces watch it. vow surface and vow list — prose and --json alike — scan the superseded home register on each read and name, loudly, every entry it holds that your register does not:

Terminal window
node $CLAUDE_PLUGIN_ROOT/scripts/divineswe-state.mjs vow surface

In vow list --json the same finding arrives as a home_residue string and a home_follow_up_residue token in warnings[], so a machine consumer can branch on it without matching English. It is computed above both gates: a closed vows_enabled suppresses your own register, never the news that obligations exist outside it.

scry does not watch it, and that is worth stating plainly rather than leaving to inference: the Scry reads Books, Decrees, specs and the archive — it does not read the vow register at all, and it will not tell you about the home follow-up ledger. Use vow surface or vow list for that question.

Which register is scanned is the union of the register the house convention derives for your project directory today and every register in your own ~/.divineswe/ whose name a past migration recorded (origin_ref). A recorded name is matched against a listing of that directory — never joined onto a root, never opened by path — so nothing in the committed register can point a read anywhere, and a pointer written on another machine still resolves under your home. A recorded name with no register of that name on this machine is stated on the migration report and is not an alarm: it is a true local fact, and your register already holds whatever a past migration moved out of it. At most 25 registers are opened per read; a bounded read says how many it did not look at.

The scan is a raw-line set difference anchored on your register, and every cheaper reading was tried and refused: a follow-up row carries no timestamp, so “entries newer than the stamp” has no field to stand on; a positional watermark is defeated by prepending a row; a tuple-keyed diff is defeated by reusing a migrated row’s label with a different trigger; and anything keyed on the stamp’s claimed time, or on the file’s mtime, is switched off forever by one line in a file every stale writer can edit.

An unmigrated repository gets the same surface with every home entry on it — so the retirement can never bury the debts it exists to carry.


Triggers and preconditions are inert data

A --trigger and a --precondition are inert prose: the register stores them, a human reads them, and they are never evaluated — not by the CLI, not by a shell, not compiled to a regex, not interpolated, not used as a lookup key.

This is a promise you are meant to lean on. Write the trigger in the words that will actually mean something to you at 9am six weeks from now:

--trigger "when we finally touch the auth middleware again"
--trigger "$(date) — before the next release"
--trigger "if `npm audit` ever comes back clean"

The second and third look executable. They are not, and nothing anywhere in this system will make them so. They are matching cues for a human being and text for a renderer, and that is the whole of their contract.

What does happen to them is rendering discipline, which is a different concern with a different reason. Every free-text field passes through a shared neutralizer and then through each surface’s own record grammar before it is displayed, because an item reading Ship it | due: 2099-12-31 | OVERDUE would otherwise forge a deadline the register does not hold on the plan-time projection every downstream agent reads. Your words are stored verbatim and rendered inside a quarantine from which no sequence of field bytes can escape.


The Canon addendum — what your project declares about itself

This section is illustrative, not runnable. The Canon’s own facade verbs (canon show, canon list, canon validate, canon drift, canon genesis) need a real docs/canon/ tree or a real survey document to do anything honest, and neither exists in a bare consumer repository — so, unlike every recipe above, nothing here is lifted into the A15 wiring suite. Read it for the shape of the thing; run it against your own repository once it has a Canon to read.

The Canon lives beside the Vows rather than inside them, at docs/canon/: a committed append-only event store .canon-log.jsonl (merge=union) plus one <capability>.md file per capability, which is a derived artifact and nothing else. Where a Vow is a debt, a Canon section is a declaration — what this project currently claims to be true about itself, one section per requirement, stamped with the Book that sealed it and the tier a human signed it into: apocrypha (declared, unsigned), verified (a vow judge validated verdict), or decanonized (a vow judge rejected verdict).

Reading it

Four verbs are pure reads, and their answers carry the same honesty rule as everything above: an unreadable log, a torn capability file, or a closed canon_enabled gate reports unknown (or suppressed) — never a clean valid: true.

node $CLAUDE_PLUGIN_ROOT/scripts/divineswe-state.mjs canon show --capability <slug> --json
node $CLAUDE_PLUGIN_ROOT/scripts/divineswe-state.mjs canon validate --json
node $CLAUDE_PLUGIN_ROOT/scripts/divineswe-state.mjs canon drift --json
{"verdict":"clean","valid":true,"findings":[],"drifted":[],"missing":[],"orphaned":[],"checked":12}

canon_enabled is the gate: default false since 0.68.0 (a project that keeps a Canon opts in), PROJECT-ONLY. It depends on vows_enabled exactly as decrees_enabled does — the incoherent pair is reported, never silently flipped.

The drift audit becomes a vow

The Canon has exactly one write path outside the seal and the judgment — everything else is a read. The drift audit, scripts/canon-drift.mjs (fronted by the canon-drift skill), honours that by construction: its import graph reaches the Canon’s readers and reaches no writer at all, and it re-exports none either, so “accuses, never authors” is a property of the code rather than a promise about it. It joins the paths git says changed against each capability’s frontmatter paths: watch globs, weighed per section against that section’s own provenance recency, and — where a declaration and the code may have drifted apart — hands the compare to one dispatched agent.

node $CLAUDE_PLUGIN_ROOT/scripts/canon-drift.mjs gather
node $CLAUDE_PLUGIN_ROOT/scripts/canon-drift.mjs prompt
node $CLAUDE_PLUGIN_ROOT/scripts/canon-drift.mjs relay --book <n> --findings <path-to-findings.json>

Whatever either producer finds crosses one schema gate — schemas/canon-drift-findings.schema.json, refusing any of the six fields a Canon writer reads (delta, verdict, judgment, status, vow_id, signed_at) by name — before it becomes anything, and what it becomes is a vow — Book-scoped (--book is required, because a register row naming no Book is a citation the Canon’s judgment binding cannot weigh), de-duplicated on (code, capability, slug) against what already stands open, and capped, with an over-large document refused whole rather than truncated — filed on this same register and readable by vow list and vow surface like any other. The honest bound, stated rather than implied: the recency window is a commit date the committer supplies, so a backdated commit to a watched file reads settled and the audit cannot see it. That bound rides on every reading as basis, because a control that can be bypassed is documented as advisory, never claimed as enforcement.

Bootstrapping with canon genesis

A project with no Canon has nothing to audit. canon genesis scaffolds one from a survey — an Atlas exploration’s output — wholesale, into the Apocrypha, and it is the Canon’s only writer of its own besides the seal and the judgment:

node $CLAUDE_PLUGIN_ROOT/scripts/divineswe-state.mjs canon genesis --survey <repo-relative survey.json> --book <book>

It cannot mint anything above the Apocrypha, however the survey is shaped — a tier is a property of the fold, only a judgment event raises one, and genesis emits delta events alone. A survey entry that carries status, judgment, verdict, tier, vow_id or signed_at is refused wholesale rather than quietly downgraded. It refuses to clobber a store that already carries an event, and it opens no vows — the judgment inbox reads the register, and a bootstrap that opened one validation vow per capability would flood it through the front door at exactly the scale genesis reaches.

Where to go next

You wantRead
The full verb reference, field by fielddocs/state-model.md
Every config key and its resolution orderdocs/configuration.md
How the register fits the pipelinedocs/architecture.md
What the honor-system tiers actually promisedocs/honor-system-rules.md
Every Canon verb, field by fielddocs/state-model.md

And when you just want to know what today owes you, run vow surface.