@journeykit/canon 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,7 +25,9 @@ a feature, a one-line request included. A human is needed once per scope:
25
25
  approves the brief and the policy, `.canon/policy.yaml`, which is what
26
26
  the gate grades the work against. Record it only from the human's answer:
27
27
  `canon brief approve --change <slug> --by <the human> --note "<their words>"`.
28
- Never edit the policy to make the gate pass. A brief edited after its
28
+ Never edit the policy to make the gate pass: a policy that differs from
29
+ the approved one fails closed (`POLICY_CHANGED_SINCE_APPROVAL`, not
30
+ waivable); stop and ask the human. A brief edited after its
29
31
  approval is not approved (`BRIEF_CHANGED_SINCE_APPROVAL`): it needs a new
30
32
  answer. The ship guard and `canon evidence add` refuse an unapproved or
31
33
  edited brief, but those are backstops: the stop itself is at step 1.
@@ -35,7 +37,9 @@ a feature, a one-line request included. A human is needed once per scope:
35
37
  implement, evidence, independent review. A follow-up that adds or changes
36
38
  a step of the brief edits the brief: that is a scope change and needs a
37
39
  new answer. A follow-up on a shipped subject reopens it
38
- (`canon subject status <id> --to verifying --reason "<why>"`).
40
+ (`canon subject status <id> --to verifying --reason "<why>"`); a fix
41
+ closed `fixed`, and an approved chore, then need the whole suite evidenced
42
+ again (`evidence add --all-scenarios`) and re-reviewed before it ships.
39
43
  - When no human can answer one of the brief's questions, write your
40
44
  assumption into the brief's Questions and assumptions, build on it, and say
41
45
  so in your report. That covers the brief's questions and work inside an
@@ -87,10 +91,11 @@ A role is guidance, not enforcement: `--role` on `evidence add` and
87
91
  - Pass `--json` and read stdout as one document. On failure it is
88
92
  `{"error":{"code","message","hint","retryable","field","expected"}}`: branch
89
93
  on `error.code`, fix the option `field` names using the `expected` values,
90
- and follow `error.hint`. The codes are stable; `canon schema --errors --json`
91
- lists them.
92
- - A renamed command still runs for one release, warns on stderr and sets
93
- `meta.deprecated`; use the new name it prints.
94
+ and follow `error.hint`. Every refusal carries a concrete `hint`: follow
95
+ it. The codes are stable; `canon schema --errors --json` lists them.
96
+ - `canon status` sends the planner to write the brief while `brief check`
97
+ has blockers; the human stop appears only when the brief is clean and
98
+ unapproved.
94
99
  - Page long lists with `--limit <n>` and `--cursor <nextCursor>` (`--all`
95
100
  for everything), and project fields with `--fields a,b` (`status`, `export`,
96
101
  `query`, `finding list`, `divergence status`, `context`).
@@ -110,6 +110,9 @@ Fix it and the next command indexes the files (see `canon-inventory`).
110
110
 
111
111
  - `--actor <id>` (or `CANON_ACTOR` in the environment) puts YOUR id on the
112
112
  provenance line. Default is `canon-scaffold`.
113
+ - **Keep the `# canon:` lines when you fill in the scenario.** Dropping one
114
+ makes `canon inventory sync` abort on `missing-provenance`; restore it
115
+ from git history or a sibling scenario (the error shows its shape).
113
116
  - **Never hand-type, move, or edit a `# canon:` line.** The scaffold
114
117
  writes it directly above the scenario's tags:
115
118
 
@@ -21,15 +21,15 @@ Every violation carries one of these stable, grep-able strings:
21
21
 
22
22
  | Class | Meaning |
23
23
  |---|---|
24
- | `uncovered-cell` | Either a policy-required evidence cell (role × artifact) with no matching record, or — with `spec_coverage` enabled — a spec scenario that is unimplemented or mismatched, or a feature surface missing a `require_cases` case. The detail string distinguishes them. Coverage means "a test exists", not "a test passed": even a `Divergent` verdict satisfies the role-cell form. |
25
- | `unreviewed-promotion` | An artifact tagged `reviewed` has no matching ledger review record — or, with `spec_coverage.require_review` set, an in-scope scenario has no qualifying `Review`: none at all, only self-reviews, only stale reviews (the spec or evidence it pinned changed since), or only `indeterminate`/`unavailable` ones. See `canon-review`. |
24
+ | `uncovered-cell` | Either a policy-required evidence cell (role × artifact) with no matching record, or — with `spec_coverage` enabled — a spec scenario that is unimplemented or mismatched, or a feature surface missing a `require_cases` case. The detail string distinguishes them. Coverage means "a test exists", not "a test passed": even a `Divergent` verdict satisfies the role-cell form. At `verifying → shipped`, a `Divergent` latest verdict is `uncovered-cell` unless a reviewer accepted it (see Accepted divergence below), and a chore's approval, like a blocker closed `fixed`, needs every evidenced scenario evidenced again afterwards (see Re-verification below). |
25
+ | `unreviewed-promotion` | An artifact tagged `reviewed` has no matching ledger review record — or, with `spec_coverage.require_review` set, an in-scope scenario has no qualifying `Review`: none at all, only self-reviews, only stale reviews (the spec or evidence it pinned changed since), or only `indeterminate`/`unavailable` ones. Also, subject `policy`: the policy changed since its approval or its approval is unreadable (fails closed; see `canon-policy`). See `canon-review`. |
26
26
  | `trust-below-required` | Achieved trust level is below `policy.yaml`'s `trust_required` for its class — RELEASE-scoped only (`canon gate check --release`); never fires on an ordinary run. |
27
27
  | `stale-evidence` | A passing record degraded to stale: its declared surface changed since its `evidence_sha`, or HEAD moved past `staleness.max_commits_behind`. Only degrades an already-green record. Also: a file bound to a latest evidence record whose bytes are gone — no intact blob in `.canon/artifacts/sha256/` and no working-tree copy with the recorded digest — or a file bound to an in-effect review with no intact blob (see Bound evidence stays provable below). |
28
28
  | `malformed-evidence` | A candidate record doesn't parse, is misfiled, or carries an unparseable interim tag. Malformed evidence is no evidence. |
29
29
  | `flagged` | The human-only `flagged` overlay is set — never green regardless of passing evidence. |
30
30
  | `unevidenced-flip` | `canon gate task <task_id>` was asked to flip a checkbox with no matching, non-`Divergent` evidence record. |
31
31
  | `fabricated-evidence` | An evidence note contains a blocklisted marker (`"would pass"`, `"TBD"`, `"n/a"`) standing as a whole word — never inside a path, a file name, a URL or a longer word, so `pack.json/atlas` is not `n/a` — or a bare `verified` claim with no attached command result. `canon evidence add` refuses such a note before writing (`canon evidence add: refused — fabricated-evidence …`, exit 1). |
32
- | `open-blocker` | With `spec_coverage.require_review` set (and `block_on_findings`, the default), a Finding whose latest version is severity `blocker`, disposition `open`, on a change an in-scope subject adopted. Subject string `<change_id>#<round>.<seq>`. See `canon-review`. |
32
+ | `open-blocker` | With `spec_coverage.require_review` set (and `block_on_findings`, the default), a Finding whose latest version is severity `blocker`, disposition `open`, on a change an in-scope subject adopted — or closed `fixed` while an evidenced scenario of the subject has no evidence recorded after the close (re-verification). Subject string `<change_id>#<round>.<seq>`. See `canon-review`. |
33
33
  | `false-success` | The latest audit record (`canon evidence add --kind audit`) of a scenario says `refuted`: an external evaluator found a counterexample to the claimed success. Always on, regardless of policy or sampling, and takes precedence over any declared faithful verdict; only a newer audit (`confirmed` or `indeterminate`) clears it. The detail names the evaluator, its version, its sha256 and the counterexample paths. |
34
34
 
35
35
  ## `canon gate check [--repo <dir>] [--release]`
@@ -51,11 +51,12 @@ Exit `0` clean, `1` gate-red (any violation), `2` usage/load failure
51
51
  resolves through the nearest-ancestor `canon.yaml` walk, so it reads the
52
52
  repo ROOT's `.canon/policy.yaml` and `.canon/ledger` from any subdirectory.
53
53
 
54
- When `.canon/policy.yaml` exists but has no approval, or its bytes no
55
- longer match the approved digest, the gate prints `advisory: policy not
56
- approved: …` or `advisory: policy changed since approval: …` after its
57
- result, naming `canon policy approve --by <who>`; the exit code is
58
- unchanged (see `canon-policy`).
54
+ When `.canon/policy.yaml` exists but has no approval, the gate prints
55
+ `advisory: policy not approved: …` after its result, naming
56
+ `canon policy approve --by <who>`; the exit code is unchanged. When its
57
+ bytes no longer match the approved digest, or the approval cannot be read,
58
+ the gate fails closed: the violation is reported and the exit is `1` (see
59
+ `canon-policy`).
59
60
 
60
61
  Before it reads the scenario index, the gate rebuilds any stale part of
61
62
  it from the changed `.feature` files (the same code `canon inventory
@@ -200,6 +201,35 @@ Malformed rows in those three kinds do NOT surface as
200
201
  `malformed-evidence`; they are kept off the evidence violation set on
201
202
  purpose, so enabling nothing changes no existing verdict.
202
203
 
204
+ ### Accepted divergence and re-verification
205
+
206
+ ```bash
207
+ canon divergence accept --project-id <root-id> --scenario-id <id> --reason "<why>" --tracking <issue-url-or-ref> [--expiry YYYY-MM-DD] --reviewer <me> --actor-id <me> --session-id <session> --as reviewer
208
+ ```
209
+
210
+ A reviewer accepts a scenario whose latest evidence is `divergent` as a
211
+ known gap limited by something outside the change; never to skip a fix.
212
+ Refused (`DIVERGENCE_ACCEPT_REFUSED`) unless the latest verdict is
213
+ divergent and the session and actor differ from the evidence's. The record
214
+ pins that evidence: new evidence for the scenario, or `--expiry` passing,
215
+ ends it. While it holds, the ship guard counts the cell covered and
216
+ `status` and `gate check` keep listing it, never silently:
217
+
218
+ ```
219
+ accepted divergences: 1 known gap(s) — not failing the gate:
220
+ accepted-divergence demo.page.02 — https://example.test/issues/1 (no expiry)
221
+ ```
222
+
223
+ Re-verification is one rule with two triggers: a blocker whose latest
224
+ disposition is `fixed`, and a chore change once approved, keep the subject
225
+ from shipping until every one of its evidenced scenarios has evidence
226
+ recorded after the close or the approval. The refusal names the scenarios
227
+ and the batch command: `canon evidence add --project-id <root-id>
228
+ --all-scenarios --report <format>:<path> --kind test-run --role
229
+ implementer --ref "<test command>" …`. That evidence stales the pinned
230
+ reviews, so a re-review follows (and a new `divergence accept`, where the
231
+ gap remains).
232
+
203
233
  ### Audit records (`--kind audit`)
204
234
 
205
235
  canon never generates or runs an audit. An external evaluator does, and
@@ -72,7 +72,7 @@ scenario index # refreshed by every reader; `canon inventory sy
72
72
  │
73
73
  ▼
74
74
  canon review add # attests a review — see canon-inventory downstream / canon-gate
75
- canon divergence {add,promote,resolve,defer,status} # tracks/resolves a divergence
75
+ canon divergence {add,promote,resolve,defer,accept,status} # tracks/resolves a divergence
76
76
  canon gate check [--release] # trust-ladder / staleness / coverage — see canon-gate
77
77
  │
78
78
  ▼
@@ -158,7 +158,7 @@ specs:
158
158
 
159
159
  - **Review** — `canon review add` attests a verdict against a synced
160
160
  scenario. (See `canon-inventory` downstream commands and each command's `--help`.)
161
- - **Divergence** — `canon divergence {add,promote,resolve,defer,status}`
161
+ - **Divergence** — `canon divergence {add,promote,resolve,defer,accept,status}`
162
162
  tracks/resolves divergences against synced scenarios.
163
163
  - **Gate** — `canon gate check` enforces trust-ladder / staleness /
164
164
  coverage over the corpus (see `canon-gate`).
@@ -101,21 +101,43 @@ its result:
101
101
  advisory: policy not approved: .canon/policy.yaml has no approval record (.canon/policy.approval.json); a human reviews it and runs `canon policy approve --by <who>`
102
102
  ```
103
103
 
104
- Once the policy's bytes no longer match the approved digest, it prints
105
- this instead:
104
+ Once the policy's bytes no longer match the approved digest, or the
105
+ approval file cannot be read, the gate fails closed instead:
106
106
 
107
107
  ```
108
- advisory: policy changed since approval: .canon/policy.yaml is sha256:<now>, but `<who>` approved sha256:<then> at <time>; a human reviews it and runs `canon policy approve --by <who>`
108
+ unreviewed-promotion policy — policy changed since approval: .canon/policy.yaml is sha256:<now>, but `<who>` approved sha256:<then> at <time>; … (--override-reason does not waive it); the human re-approves the policy in force with `canon policy approve --by <human> --note "<words>"`, or restore the approved .canon/policy.yaml (and .canon/policy.approval.json) from git
109
109
  ```
110
110
 
111
- An approval file that does not parse reads
112
- `advisory: policy approval unreadable: …`.
113
- `canon status` prints the same text as a `warning:` line (and in
114
- `--json` `warnings`). The advisory never changes an exit code: canon
115
- cannot tell whether a human or an agent ran the command, so the record
116
- names who approved which bytes, and the advisory says when that no
117
- longer holds. An agent runs `canon policy approve` only to record a
118
- human's answer, never without one and never on its own behalf.
111
+ `canon gate check` exits `1`; `canon subject status` (the ship guard and
112
+ the reopen) refuses with `POLICY_CHANGED_SINCE_APPROVAL`, or
113
+ `POLICY_APPROVAL_UNREADABLE` for an unreadable approval, and
114
+ `--override-reason` does not waive either. A policy never approved stays
115
+ an advisory. `canon status` prints the same text as a `warning:` line (and
116
+ in `--json` `warnings`). canon cannot tell whether a human or an agent
117
+ ran `policy approve`, so the record names who approved which bytes and
118
+ the gate fails when that no longer holds. An agent never edits the policy
119
+ to pass the gate: it stops and asks the human, who re-approves or restores
120
+ it. An agent runs `canon policy approve` only to record a human's answer,
121
+ never without one and never on its own behalf.
122
+
123
+ ## `canon policy show [--json]`
124
+
125
+ Prints the policy path and current digest, and the approval state
126
+ (`approved`, `changed`, `unreadable` or `unapproved`) with the approver,
127
+ time and approved digest:
128
+
129
+ ```
130
+ policy: .canon/policy.yaml
131
+ digest: sha256:37ea…
132
+ approval: approved
133
+ approved by: Ann
134
+ approved at: 2026-10-17T04:57:45Z
135
+ approved digest: sha256:37ea…
136
+ note: ok
137
+ ```
138
+
139
+ For `changed` and `unreadable` it adds `gate: fails closed (…)` with the
140
+ human's way out. Read-only; it writes nothing.
119
141
 
120
142
  ## The closed CEL profile
121
143
 
@@ -150,7 +150,8 @@ may carry the same scenario id. Exactly one provenance ref is required.
150
150
  `--actor-id` defaults to `canon`; `--session-id` fills `actor.session_id`.
151
151
  A Review carries no verdict about the behavior; the `verifying → shipped`
152
152
  ship gate still refuses missing or `Divergent` evidence verdicts on its
153
- own. It is written directly to the ledger, with no staging step, and says
153
+ own (a `Divergent` one passes only while a reviewer's `canon divergence
154
+ accept` holds). It is written directly to the ledger, with no staging step, and says
154
155
  so: `canon review add: wrote <path> (new) — written directly; nothing to
155
156
  promote`.
156
157
 
@@ -146,7 +146,14 @@ owns must also carry a scenario of each required `@case:` (e.g. one
146
146
  `@case:failure`) — attested golden-path scenarios alone do not ship. A
147
147
  Subject with no tagged scenario is refused rather than shipped on an
148
148
  empty set; so is one whose `scenario` records route away from the
149
- gate's rung. Each refusal prints by failure class (`uncovered-cell`),
149
+ gate's rung. A `Divergent` latest verdict is refused as `uncovered-cell`
150
+ unless a reviewer accepted it (`canon divergence accept`, see
151
+ `canon-gate`): the hint names both ways out. Re-verification: a blocker
152
+ closed `fixed` keeps blocking, and a chore's approval keeps the subject
153
+ from shipping, until every evidenced scenario has evidence recorded after
154
+ the close or the approval; the refusal names the scenarios and the
155
+ `canon evidence add --all-scenarios …` command. Each refusal prints by
156
+ failure class (`uncovered-cell`), carries a `hint`,
150
157
  exits 1, and the status stays `verifying`. `retired` is not gated.
151
158
 
152
159
  **The brief (0.16, 0.17, 0.19):** `verifying → shipped` also refuses where a brief
@@ -265,11 +272,13 @@ canon subject status: reopened `demo-subject` from shipped, recorded by `impl`:
265
272
  canon subject status: demo-subject → verifying — written directly; nothing to promote
266
273
  ```
267
274
 
268
- - `--reason "<one line>"` is required for `shipped → verifying` and
269
- refused on any other transition (exit 2, as is a blank one). The new
270
- record carries it with the actor and the change the work goes into:
275
+ - `--reason "<one line>"` is required for `shipped → verifying` (exit 2
276
+ without it, as for a blank one) and optional on every other transition,
277
+ where it is an audit note. The new record carries it with the actor as
278
+ `status_reason: { reason: …, actor: { agent_id: impl } }`; a reopen also
279
+ records the change the work goes into:
271
280
  `reopen: { reason: …, change: c-demo, actor: { agent_id: impl } }`. The
272
- next status write clears it, as it clears a waiver.
281
+ next status write clears both, as it clears a waiver.
273
282
  - Inside the approved scope it needs no human: the subject's newest
274
283
  adopted change's brief is approved, or there is no brief to answer.
275
284
  Otherwise it refuses, exit 1, naming the brief to approve, and
@@ -278,6 +287,12 @@ canon subject status: demo-subject → verifying — written directly; nothing t
278
287
  - The review guard does not run on a reopen (it prints `skipped … — a
279
288
  reopen moves the subject back to fix its gaps; shipping again runs
280
289
  it`). `verifying → shipped` afterwards runs every guard above again.
290
+ - A policy that fails closed (`POLICY_CHANGED_SINCE_APPROVAL`,
291
+ `POLICY_APPROVAL_UNREADABLE`: see `canon-policy`) refuses the reopen and
292
+ the ship, and `--override-reason` does not waive it.
293
+ - `canon change new` on a shipped subject prints the reopen command
294
+ (`canon subject status <id> --to verifying --reason "<why it reopens>"`)
295
+ in its output and `meta.next`.
281
296
  - `canon status` flags a shipped subject whose gate went red (whatever
282
297
  `verifying → shipped` would now refuse it on) and its only step is
283
298
  this reopen.
@@ -561,10 +576,14 @@ uses, so status and the gate cannot disagree about a gap:
561
576
  than one `specs.roots[]` entry, every suggested `canon scenario new`
562
577
  carries `--project`: the owning root for a missing case, `<root-id>`
563
578
  (the reason lists the roots) for a subject that owns no scenario yet.
564
- - **The one human step.** Every step is the agent's to run except one:
565
- a subject whose brief is enforced (its newest change's canon
579
+ - **The one human step.** Every step is the agent's to run except one,
580
+ and it comes only once the brief is clean: while `canon brief check
581
+ --change <slug>` has blockers, the subject gets the planner step
582
+ `write the brief: canon brief check --change <slug> lists N blockers`
583
+ (`role: planner`) instead. A subject whose brief is enforced (its
584
+ newest change's canon
566
585
  `brief.md` has no `Approved:` line, or it has no canon `brief.md` yet
567
- and comes from an external openspec `proposal.md`) gets
586
+ and comes from an external openspec `proposal.md`) and clean gets
568
587
  `human: stop and report the brief in <brief>; build and attest nothing for <id> until the human answers (one answer approves brief and policy)`,
569
588
  where `<brief>` is the canon `brief.md`, or the external `proposal.md`
570
589
  when no canon brief exists yet. Its reason reads ``<id>: <why
@@ -18,6 +18,7 @@ canon scenario new <area>.<surface>.<nn> --subject <subject-id> --flow <F-id> --
18
18
  canon scenario new --from - --as implementer --json # many at once: NDJSON on stdin, one {"id","title","subject","flow","case",…} per line
19
19
  canon evidence add --project-id <root-id> --all-scenarios --report <junit|cucumber|swift-testing>:<path> --kind test-run --role implementer --ref "<test command>" --actor-id <unit> --session-id <session> --as implementer --json
20
20
  canon evidence add --project-id <root-id> --scenario-id <id> --artifact <path> --kind test-run --role implementer --ref "<what it shows>" --actor-id <unit> --session-id <session> --as implementer --json
21
+ canon evidence add --project-id <root-id> --scenario-id <id> --verdict divergent --artifact <path> --kind test-run --role implementer --ref "<the external limit, and where it is tracked>" --actor-id <unit> --session-id <session> --as implementer --json # honest divergent evidence
21
22
  canon finding add --change-id <slug> --round <n> --seq <n> --severity <blocker|should-fix|note> --reviewer <unit> --actor-id <unit> --session-id <session> --summary "<defect>" --as implementer --json
22
23
  canon inventory sync --as implementer --json # only after a write printed NOT refreshed
23
24
  canon gate promote --as implementer --json # only with write.promote: manual
@@ -39,7 +40,8 @@ After the first `status`, follow `meta.next` of each write; do not call
39
40
  violation or a duplicate id: it names each problem). Specs live at
40
41
  `<root>/kind=feature/area=<area>/<surface>.feature` under a `specs.roots[]`
41
42
  root (default `.canon/specs`); `reference/canon-authoring.md` has the
42
- scaffold.
43
+ scaffold. When you fill in a scenario, keep its `# canon: {...}` provenance
44
+ lines: dropping one aborts `inventory sync` on `missing-provenance`.
43
45
  2. **Units.** Give each unit its own actor id and session id and pass them on
44
46
  everything it records. A delegated unit attests its own work; never attest
45
47
  another unit's work under your id.
@@ -56,7 +58,11 @@ After the first `status`, follow `meta.next` of each write; do not call
56
58
  or recording (`--artifact <path>`). An external evaluator's audit is
57
59
  `--kind audit`; canon never generates one. Evidence for a task of an
58
60
  imported plan (`--task <id>`) must bind the artifact or report that proves
59
- the task; a canon command is never that proof.
61
+ the task; a canon command is never that proof. When the behavior cannot
62
+ match the scenario because of something outside this change, record the
63
+ evidence `divergent` and say why in `--ref`; never record `faithful` to
64
+ pass. A reviewer may accept the tracked gap (`canon divergence accept`);
65
+ fixing the behavior and recording again is the other way out.
60
66
  5. **Promote.** Under `write: { promote: auto }` (what `canon init` writes)
61
67
  the record is final when `evidence add` returns. Only with
62
68
  `write.promote: manual` (or no `write:` key) follow it with
@@ -64,6 +70,13 @@ After the first `status`, follow `meta.next` of each write; do not call
64
70
  6. **A new record makes a signed review stale.** The add prints the reviewer
65
71
  and the `canon review add … --pin <sha>` that re-pins it; that is the
66
72
  reviewer's call. Do not run it yourself.
73
+ 7. **After a fix or a chore, record the whole suite again.** A blocker the
74
+ reviewer closed `fixed`, and an approved chore, are not done until every
75
+ evidenced scenario has evidence newer than the close or the approval:
76
+ run the suite and bind it with `canon evidence add --all-scenarios …`
77
+ (the shipping refusal names the scenarios and this command). The
78
+ re-review follows; a scenario whose gap was accepted is accepted again
79
+ by the reviewer once new evidence supersedes the old.
67
80
 
68
81
  ## Retry safety
69
82
 
@@ -76,5 +89,5 @@ response, run it again.
76
89
  When every scenario has evidence, the review is the reviewer's session, in a
77
90
  separate process: do not review your own change. A chore adds no scenarios:
78
91
  run the suite again and bind it with `--all-scenarios`, which supersedes the
79
- old evidence and makes pinned reviews stale. Reference:
80
- `reference/canon-gate.md`.
92
+ old evidence and makes pinned reviews stale; shipping needs that evidence
93
+ newer than the chore's approval. Reference: `reference/canon-gate.md`.
@@ -12,7 +12,8 @@ Every line is complete; fill the `<placeholders>`.
12
12
  canon status --as orchestrator --json --fields next,waitingOn # at the start and when resuming
13
13
  canon brief approve --change <slug> --by "<the human>" --note "<their words>" --as orchestrator --json
14
14
  canon subject status <subject-id> --to <proposed|specced|building|verifying|shipped> --actor-id <unit> --as orchestrator --json
15
- canon subject status <subject-id> --to verifying --reason "<why>" --actor-id <unit> --as orchestrator --json # follow-up on a shipped subject
15
+ canon subject status <subject-id> --to verifying --reason "<why>" --actor-id <unit> --as orchestrator --json # reopen a shipped subject (--reason is required here; any other transition accepts it as a note)
16
+ canon policy show --as orchestrator --json # policy digest and its approval: approved, changed, unreadable or unapproved
16
17
  canon gate check --as orchestrator --json
17
18
  ```
18
19
 
@@ -57,9 +58,14 @@ When `status` or `meta.next` shows a step with `actor: human` (the brief's
57
58
  approval), stop everything for that scope, report the brief to the human in
58
59
  its language, and wait. Do not start the implementer before the answer.
59
60
  Record the answer with `brief approve`. Never approve it yourself and never
60
- edit the policy to pass the gate. Inside an approved scope never stop for a
61
- human: fixes, follow-ups and re-reviews go straight to the implementer and
62
- reviewer in the same change.
61
+ edit the policy to pass the gate. A policy that no longer matches the
62
+ approval fails closed (`POLICY_CHANGED_SINCE_APPROVAL`, or
63
+ `POLICY_APPROVAL_UNREADABLE`): the ship guard and the reopen refuse, and
64
+ `--override-reason` does not waive it. Run `canon policy show`, then stop and
65
+ ask the human to re-approve (`canon policy approve --by <human>`) or restore
66
+ the approved policy from git; never edit the policy yourself. Inside an
67
+ approved scope never stop for a human: fixes, follow-ups and re-reviews go
68
+ straight to the implementer and reviewer in the same change.
63
69
 
64
70
  ## Move the subject
65
71
 
@@ -69,7 +75,12 @@ evidence or reviews, open blockers, an unapproved brief); fix the gap rather
69
75
  than overriding. Move to `shipped` yourself once its guard passes, unless the
70
76
  brief says the human ships. A follow-up on a shipped subject goes back to
71
77
  `verifying` with `--reason`, then the implementer and reviewer in the same
72
- change, then `--to shipped` again. `canon status` flags a shipped subject
78
+ change, then `--to shipped` again. A chore on a shipped subject (`change
79
+ new … --kind chore` names the reopen command) is approved first, then
80
+ reopened the same way; shipping again needs the whole suite evidenced after
81
+ the chore's approval (`evidence add --all-scenarios`) and a re-review. A
82
+ blocker closed `fixed` needs the same. The refusal names the scenarios and
83
+ the command; never override them. `canon status` flags a shipped subject
73
84
  whose gate went red. A bad `--to` fails with `error.field` `--to` and
74
85
  `error.expected` listing the states.
75
86
 
@@ -21,7 +21,11 @@ canon brief approve --change <slug> --by "<the human>" --note "<their words>" --
21
21
 
22
22
  After the first `status`, follow `meta.next` of each write. `brief check`
23
23
  lists blockers and advisories; bare `canon brief --as planner` names the next
24
- steps.
24
+ steps. While the brief has blockers, `status` and `change new`'s `meta.next`
25
+ send you to write it (`write the brief: canon brief check --change <slug>
26
+ lists N blockers`); the human step appears only once the brief is clean and
27
+ unapproved. A policy that no longer matches its approval fails closed
28
+ (`POLICY_CHANGED_SINCE_APPROVAL`): never edit it; the human re-approves.
25
29
 
26
30
  ## Steps
27
31
 
@@ -75,7 +79,10 @@ can observe: `change new … --kind chore`. Its brief is short (intent,
75
79
  category, invariants, checks, out of scope) and approved like any brief, one
76
80
  human stop. A chore adds no scenarios: the
77
81
  implementer runs the subject's suite again and binds it with
78
- `canon evidence add --all-scenarios …`, then a re-review follows. Work that
82
+ `canon evidence add --all-scenarios …`, then a re-review follows. On a
83
+ shipped subject `change new` prints the reopen command for the orchestrator
84
+ (`subject status <id> --to verifying --reason "<why>"`); the subject cannot
85
+ ship again until the suite is evidenced after the chore's approval. Work that
79
86
  changes behavior is never a chore: it is a feature change.
80
87
 
81
88
  ## Hand-off
@@ -17,6 +17,7 @@ canon finding list --change-id <slug> --open --as reviewer --json
17
17
  canon finding close --change-id <slug> --round <n> --seq <n> --disposition fixed --resolution-sha <sha> --as reviewer --json
18
18
  canon review add --project-id <root-id> --scenario-id <id> --scenario-id <id> --reviewer <me> --actor-id <me> --role reviewer --session-id <session> --pin <sha> --upstream-ref <ref> --as reviewer --json
19
19
  canon review add --project-id <root-id> --scenario-ids-from - --reviewer <me> --actor-id <me> --role reviewer --session-id <session> --pin <sha> --upstream-ref <ref> --as reviewer --json # ids on stdin, one per line
20
+ canon divergence accept --project-id <root-id> --scenario-id <id> --reason "<why the gap stays>" --tracking <issue-url-or-ref> [--expiry YYYY-MM-DD] --reviewer <me> --actor-id <me> --session-id <session> --as reviewer --json
20
21
  ```
21
22
 
22
23
  `review add` needs `--upstream-ref <ref>` or `--original-spec-ref <path>`;
@@ -56,6 +57,27 @@ what you could not run: a reviewer that cannot run the tests says so and
56
57
  does not sign off; record that instead (`--outcome indeterminate`, or
57
58
  `unavailable` when you could not run).
58
59
 
60
+ ## Check the checks
61
+
62
+ Passing is not enough: read each bound test against its scenario's Then
63
+ steps. Record a finding (`finding add`) when a check pins behavior the
64
+ scenario does not require, or skips a Then step the scenario states: such a
65
+ test passes without proving the scenario. Do this before you sign.
66
+
67
+ ## Accept a known gap
68
+
69
+ `divergence accept` is for a scenario whose honest evidence is `divergent`
70
+ and whose gap is limited by something outside this change (an upstream
71
+ limit, a platform bug) and is tracked there: give the reason and the
72
+ `--tracking` reference, and an `--expiry` when the limit should be gone by
73
+ a date. Never use it to skip a fix you can make in this change. It is your
74
+ step, from a session that did not attest the evidence
75
+ (`DIVERGENCE_ACCEPT_REFUSED` otherwise). The acceptance pins that evidence:
76
+ new evidence for the scenario, or its expiry, ends it, and you accept (or
77
+ the implementer fixes) again. An accepted gap counts as covered for the
78
+ ship guard and stays listed as `accepted-divergence` in `status` and
79
+ `gate check`.
80
+
59
81
  ## Record
60
82
 
61
83
  - Every defect is a finding; `finding list` shows round, seq, severity and
@@ -64,6 +86,9 @@ does not sign off; record that instead (`--outcome indeterminate`, or
64
86
  - Sign off in one batch with `review add`, all or nothing. It writes
65
87
  directly; nothing to promote. canon stores and re-checks attached bytes
66
88
  and never compares images.
89
+ - A blocker you close `fixed` stays blocking until the implementer records
90
+ the whole suite again (`evidence add --all-scenarios`); re-review after
91
+ it, since new evidence makes your pinned review stale.
67
92
  - Fixes you ask for return as a re-review of the same change. When a new
68
93
  evidence record makes your review stale, look again before you re-pin.
69
94
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@journeykit/canon",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "description": "canon — harness knowledge substrate. JS launcher: platform detection + native binary resolution.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -30,9 +30,9 @@
30
30
  },
31
31
  "dependencies": {},
32
32
  "optionalDependencies": {
33
- "@journeykit/canon-core-darwin-arm64": "0.21.0",
34
- "@journeykit/canon-core-linux-x64": "0.21.0",
35
- "@journeykit/canon-core-win32-x64": "0.21.0"
33
+ "@journeykit/canon-core-darwin-arm64": "0.22.0",
34
+ "@journeykit/canon-core-linux-x64": "0.22.0",
35
+ "@journeykit/canon-core-win32-x64": "0.22.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^20.0.0",