@journeykit/canon 0.10.0 → 0.12.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.
@@ -36,6 +36,7 @@ installed explicitly with `--source canon/skills-dev`.
36
36
  - **Capture replayable run inputs / version prompts:** read `reference/canon-context.md`, then use `canon context-pack create|show|verify` and `canon prompt register|show` with explicit repository-relative JSON manifests.
37
37
  - **Validate or index specs:** read `reference/canon-fmt.md` and `reference/canon-inventory.md`.
38
38
  - **Run evidence gates, flip tasks, install hooks:** read `reference/canon-gate.md`.
39
+ - **Finish a subject (claim work done):** read `reference/canon-review.md`. Order: test-run evidence (`canon evidence add`) → independent review of the adopted change (`canon finding add` per issue, fix, `canon finding close --disposition fixed --resolution-sha <sha>`) → `canon review add` per scenario by a reviewer other than the evidence actor → `canon subject status`. Commit each record with `canon gate promote`.
39
40
  - **Ingest artifacts or sessions:** read `reference/canon-artifact-ingest.md`, `reference/canon-session-ingest.md`, or `reference/canon-plan-import.md`.
40
41
  - **Configure storage, policy, plugins, or strategy learning:** read `reference/canon-storage.md`, `reference/canon-policy.md`, `reference/canon-plugins.md`, or `reference/canon-learn.md`.
41
42
  - **Retrieve dispatch guidance:** read `reference/canon-retrieve.md` and load `scripts/canon-retrieve-pre-dispatch.sh` when wiring hooks.
@@ -5,7 +5,9 @@ vocabulary registry `canon format`/`canon gate` validate against — never
5
5
  validation itself. It answers "what CAN I author in this repo, and how?":
6
6
  record kinds + their envelope fields, enum domains, join-key grammars,
7
7
  partition layout, policy-derived evidence requirements, the typed
8
- authoring vocabulary, and the CEL binding surface — folded into one
8
+ authoring vocabulary, the review vocabulary (`review`: finding severities
9
+ and dispositions, Review fields, the active `require_review`; see
10
+ `canon-review`), and the CEL binding surface — folded into one
9
11
  deterministic authoring surface.
10
12
 
11
13
  ## `canon context [--repo <dir>] [--json]`
@@ -15,20 +15,21 @@ release-scoped check (below) enforces it. A human-only `flagged` overlay
15
15
  overrides the ladder — a flagged artifact is never green regardless of
16
16
  evidence. (Author `policy.yaml`'s trust/staleness fields via `canon-policy`.)
17
17
 
18
- ## The eight failure classes
18
+ ## The nine failure classes
19
19
 
20
20
  Every violation carries one of these stable, grep-able strings:
21
21
 
22
22
  | Class | Meaning |
23
23
  |---|---|
24
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. |
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, or only reviews by its own evidence actor). 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. |
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"`) or a bare `verified` claim with no attached command result. |
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
33
 
33
34
  ## `canon gate check [--repo <dir>] [--release]`
34
35
 
@@ -65,6 +66,7 @@ spec_coverage:
65
66
  scope: [building, verifying] # optional; omit for the whole corpus
66
67
  exclude_lanes: [process] # optional
67
68
  require_cases: [failure] # optional; see Golden-path only below
69
+ require_review: {} # optional; see Independent review below
68
70
  ```
69
71
 
70
72
  - **Unimplemented** — no `EvidenceRecord` carries the scenario's
@@ -89,13 +91,32 @@ spec_coverage:
89
91
  no case, so an unclassified corpus is reported, not passed. Works with
90
92
  `require_evidence: false` too. `canon subject status <id> shipped`
91
93
  applies the same rule to the scenarios the subject owns.
94
+ - **Independent review** (`require_review`) — scenarios whose subject is
95
+ in `require_review.scope` (default `[verifying, shipped]`; `[]` = every
96
+ scenario; independent of `scope` above) need a `Review` record
97
+ (`canon review add`), by a reviewer and actor other than the
98
+ scenario's evidence actor when `distinct_actor` (default `true`);
99
+ gaps are `unreviewed-promotion`. With `block_on_findings` (default
100
+ `true`), an open `blocker` Finding on a change an in-scope subject
101
+ adopted is `open-blocker`. `exclude_lanes` also exempts scenarios
102
+ from review. Full contract: `canon-review`.
103
+
104
+ ```yaml
105
+ spec_coverage:
106
+ require_evidence: true
107
+ require_review: # absent = nothing changes
108
+ scope: [verifying, shipped] # default
109
+ distinct_actor: true # default
110
+ block_on_findings: true # default
111
+ ```
92
112
 
93
113
  Two refusals rather than a silent pass, both surfacing as
94
114
  `uncovered-cell` on the subject `spec_coverage`:
95
115
 
96
- - A **malformed section** (unknown key, unknown `scope` status) refuses
97
- at check time. It is never treated as absent — a typo must not be
98
- silently equivalent to not opting in.
116
+ - A **malformed section** (unknown key, unknown `scope` status, or a
117
+ malformed `require_cases`/`require_review` — unknown key, bad status,
118
+ `null`, non-mapping) refuses at check time. It is never treated as
119
+ absent — a typo must not be silently equivalent to not opting in.
99
120
  - A **corpus kind routed off the gate's rung** (`scenario`,
100
121
  `divergence`, or `subject` sent anywhere but `local`) refuses, because
101
122
  the gate reads one tier and would otherwise pass by seeing nothing.
@@ -104,6 +125,70 @@ Malformed rows in those three kinds do NOT surface as
104
125
  `malformed-evidence`; they are kept off the evidence violation set on
105
126
  purpose, so enabling nothing changes no existing verdict.
106
127
 
128
+ ### Review-waiver advisories
129
+
130
+ A subject moved into a review-scoped status with `canon subject status
131
+ --override-reason` (see `canon-subject`) keeps the `unreviewed-promotion`
132
+ and `open-blocker` gaps its waiver recorded visible without failing the
133
+ gate. Only those exact `(class, subject)` pairs are waived; a gap that
134
+ appears later is a violation. The waived ones print after the
135
+ violations:
136
+
137
+ ```
138
+ review waivers: 1 advisory(ies) — not failing the gate:
139
+ waived unreviewed-promotion world.demo.01 — no review record ... [waiver: subject `demo-subject` moved to verifying by `lead`: reviewer out until Monday]
140
+ ```
141
+
142
+ The waiver lasts only until the subject's next status write.
143
+
144
+ ## Experimental: evidence binding (`experimental.evidence_binding`, off by default)
145
+
146
+ An evidence record is an attestation: canon never runs `--ref`, and it
147
+ never will — it cannot run every language and every agent-driven QA
148
+ tool a team uses. Binding lets a record point at files the team's own
149
+ runner or agent ALREADY produced, so a reviewer (and the gate) can check
150
+ what backed the claim:
151
+
152
+ ```bash
153
+ # any file: a Playwright trace, screenshots, an agent QA log — by sha256
154
+ canon evidence add --scenario-id cart.add.04 --project-id root \
155
+ --kind agent-qa --ref "stagehand run 7" --role implementer \
156
+ --artifact test-results/cart-trace.zip
157
+ # a JUnit XML or Cucumber JSON report: also records the matched case + outcome
158
+ canon evidence add ... --report junit:target/nextest/default/junit.xml
159
+ canon evidence add ... --report cucumber:reports/cucumber.json
160
+ ```
161
+
162
+ The case is found by the scenario id in its name, classname, or Cucumber
163
+ tag — dotted (`cart.add.04`) or underscored (`cart_add_04`, the form a
164
+ test function carries) — or named exactly with `--report-case <name>`
165
+ (a Rust test by its function name). No match refuses (exit 2); a
166
+ `faithful` verdict over a failed or skipped case refuses (exit 1). Files
167
+ must be inside the repository and are recorded by relative path.
168
+
169
+ Strength, weakest first: `attested` (no attachment) < `artifact` (any
170
+ bound file) < `report` (a parsed report whose case passed). The policy
171
+ decides whether the gate cares:
172
+
173
+ ```yaml
174
+ experimental:
175
+ evidence_binding:
176
+ mode: warn # off (default) | warn | require
177
+ strength: artifact # artifact (default) | report
178
+ case: [failure] # optional filters — only these scenarios are held
179
+ lane: [behavior]
180
+ scope: [verifying] # subject statuses
181
+ ```
182
+
183
+ `off` checks nothing. `warn` prints the distribution and lists each
184
+ scenario below `strength` as `warn <id>` after the gate result, never
185
+ failing it. `require` reports each as `uncovered-cell`. The latest
186
+ evidence record per scenario decides; scenarios with no evidence are
187
+ `spec_coverage`'s finding, not this one's. A malformed section is a
188
+ violation in every mode. Canon still cannot know whether the bound test
189
+ actually exercises the scenario — binding narrows the trust gap, it does
190
+ not close it.
191
+
107
192
  ## Effect-aware risk approvals (`risk_tiers`)
108
193
 
109
194
  `risk_tiers` is absent by default and remains a no-op when absent. When it
@@ -121,11 +206,14 @@ binding. This is still attestation input, not proof of the changed paths;
121
206
  run/diff metadata is authoritative when available, and an unavailable
122
207
  binding is a gate violation rather than a clean result.
123
208
 
124
- Approval identity is separate from attestation text. `--approval-by` is
125
- accepted as a verified approval only when it equals a non-empty
126
- `CANON_ACTOR` environment identity and `--approval-role human` is supplied.
127
- The record persists that verification bit for replay; arbitrary caller
128
- strings, empty identities, and `agent` roles never satisfy a risk tier.
209
+ Approval identity is separate from attestation text. An approval counts
210
+ only when it carries a detached SSH signature (`ssh-keygen -Y sign -n
211
+ canon-approval-v1` over `canon evidence approval-payload`'s bytes) that
212
+ verifies against the signers pinned by policy `approval.allowed_signers`,
213
+ with `--approval-role human`. `CANON_ACTOR`, caller-supplied strings, and
214
+ `agent` roles never satisfy a risk tier, and canon never signs on a
215
+ user's behalf.
216
+
129
217
  Malformed `risk_tiers` sections produce a stable `uncovered-cell` policy
130
218
  violation (`risk_tiers policy-invalid: ...`) instead of degrading to an
131
219
  empty clean map.
@@ -192,7 +280,8 @@ Prefer this over hand-editing `settings.json`/`hooks.json`.
192
280
 
193
281
  ## `canon gate selftest`
194
282
 
195
- Runs the shipped fixture corpus — one fixture per failure class, each a
283
+ Runs the shipped fixture corpus — one fixture per failure class (nine,
284
+ including `open-blocker`), each a
196
285
  deliberately broken corpus proving that class fires and ONLY that class
197
286
  (both under-detection and over-triggering fail the run). Takes no
198
287
  `--repo`; self-contained. Run it before trusting any other `canon gate
@@ -113,7 +113,16 @@ not proof that the caller's path is authoritative; configured rules cannot
113
113
  be bypassed by declaring a safe ref.
114
114
 
115
115
  Approval text is also only an attestation unless identity is verified.
116
- `canon evidence add` marks an approval verified only when `--approval-by`
117
- equals non-empty `CANON_ACTOR` and `--approval-role human` is supplied.
118
- The persisted verification bit is what later gates count; arbitrary caller
119
- strings, empty identities, and agent roles do not count.
116
+ An approval counts only with a detached SSH signature that verifies
117
+ against the signers pinned by `approval.allowed_signers`, and
118
+ `--approval-role human`. `CANON_ACTOR`, arbitrary caller strings, and
119
+ agent roles do not count.
120
+
121
+ ## Experimental: `experimental.evidence_binding`
122
+
123
+ Off by default. Holds scenario evidence to a minimum strength —
124
+ `artifact` (a file bound by `canon evidence add --artifact`) or `report`
125
+ (a JUnit/Cucumber report whose matched case passed, `--report`) — in
126
+ `mode: warn` (advisories only) or `mode: require` (gate violations),
127
+ optionally narrowed by `case`, `lane`, and subject-status `scope`. See
128
+ `canon-gate` for the full contract.
@@ -0,0 +1,223 @@
1
+ # canon-review
2
+
3
+ Evidence says a test exists and what verdict it recorded. It does not say
4
+ that anyone other than the author looked at the work. Canon records that
5
+ second look as two ledger kinds — a **Finding** per issue a reviewer
6
+ raises, and a **Review** per scenario a reviewer signs off — and the
7
+ opt-in `spec_coverage.require_review` policy makes `canon gate check` and
8
+ `canon subject status` hold a subject until both are in order.
9
+
10
+ Both records are attestations: canon never checks a finding's summary
11
+ against the code, or that a reviewer actually read the scenario. What the
12
+ gate can check is that the records exist, who authored them, and what
13
+ state they are in.
14
+
15
+ ## Finish a subject
16
+
17
+ The order an agent follows to claim a subject done:
18
+
19
+ 1. **Test-run evidence** per owned scenario (`canon evidence add
20
+ --scenario-id <id> --project-id <root-id> ...`, then `canon gate
21
+ promote`). See `canon-gate`.
22
+ 2. **Independent review** of the change(s) the subject adopted. Record
23
+ each issue the reviewer raises as a Finding, fix it, then close it:
24
+
25
+ ```bash
26
+ canon finding add --change-id c-demo --round 1 --seq 1 \
27
+ --severity blocker --disposition open --reviewer reviewer-2 \
28
+ --summary "unbounded subprocess wait"
29
+ canon gate promote
30
+ # ... fix, commit ...
31
+ canon finding close --change-id c-demo --round 1 --seq 1 \
32
+ --disposition fixed --resolution-sha "$(git rev-parse HEAD)"
33
+ canon gate promote
34
+ ```
35
+
36
+ 3. **One Review per scenario**, by someone other than the evidence actor:
37
+
38
+ ```bash
39
+ canon review add --project-id root --scenario-id world.demo.01 \
40
+ --reviewer reviewer-2 --actor-id reviewer-2 --role reviewer \
41
+ --pin "$(git rev-parse HEAD)" --upstream-ref "pr#41"
42
+ canon gate promote
43
+ ```
44
+
45
+ 4. **Move the subject**: `canon subject status <id> verifying`, later
46
+ `shipped`. See `canon-subject`.
47
+
48
+ ## `canon finding add` / `canon finding close`
49
+
50
+ A Finding's natural key is `(change_id, round, seq)`: the change under
51
+ review, the 1-based review round, and the 1-based index within that
52
+ round. `seq` is never auto-assigned; `finding add` refuses an occupied
53
+ key, because two different findings under one key are two reviewers'
54
+ work collapsed into one identity.
55
+
56
+ ```bash
57
+ canon finding add --change-id <c> --round <n> --seq <n> \
58
+ --severity blocker|should-fix|note --disposition open \
59
+ --reviewer <who> --summary "<one line>" \
60
+ [--reviewed-sha <sha>] [--file-ref path/to/file.rs:120-134] [--actor-id <id>]
61
+ canon finding close --change-id <c> --round <n> --seq <n> \
62
+ --disposition fixed --resolution-sha <sha> # or rejected / deferred / open
63
+ ```
64
+
65
+ Both stage a candidate; `canon gate promote` commits it. `finding close`
66
+ changes only the disposition (and `resolution_sha` with it); severity,
67
+ reviewer, summary, and refs are copied from the committed record. It
68
+ appends a second version at the same natural key — the first stays.
69
+
70
+ ### Severities
71
+
72
+ | Severity | Meaning |
73
+ |---|---|
74
+ | `blocker` | Must be resolved before the work is claimed done. With `require_review`, an open blocker on an adopted change fails the gate (`open-blocker`). |
75
+ | `should-fix` | Worth fixing; may be scheduled for later. Never blocks the gate. |
76
+ | `note` | An observation; no obligation. |
77
+
78
+ Severity is the reviewer's own judgement, never a score canon derives.
79
+
80
+ ### Dispositions
81
+
82
+ | Disposition | Meaning |
83
+ |---|---|
84
+ | `open` | Raised and not yet dealt with. The default on `finding add`; `finding close --disposition open` reopens. |
85
+ | `fixed` | Fixed by a commit. Requires `--resolution-sha <sha>` naming a commit this repo holds; only `fixed` may carry one. |
86
+ | `rejected` | The reviewer's point was considered and declined. |
87
+ | `deferred` | Accepted, but postponed to later work. |
88
+
89
+ ### How findings fold
90
+
91
+ Every reader takes the **latest version per natural key**
92
+ `(change_id, round, seq)` — the same fold the report's finding panel
93
+ uses. A blocker raised `open` and later closed `fixed` counts as fixed; a
94
+ fixed finding reopened with `--disposition open` counts as open again.
95
+
96
+ ## `canon review add`
97
+
98
+ ```bash
99
+ canon review add --project-id <root-id> --scenario-id <id> \
100
+ --reviewer <who> --pin <sha-or-ref> --role <role> \
101
+ (--upstream-ref <ref> | --original-spec-ref <ref>) [--actor-id <id>]
102
+ ```
103
+
104
+ One attributed Review record for one scenario, joined on
105
+ `(project_id, scenario_id)` — both are required because two spec roots
106
+ may carry the same scenario id. Exactly one provenance ref is required.
107
+ `--actor-id` defaults to `canon`. A Review carries no verdict; the
108
+ `verifying → shipped` ship gate still refuses missing or `Divergent`
109
+ evidence verdicts on its own. Commit with `canon gate promote`.
110
+
111
+ ## The `require_review` policy (opt-in)
112
+
113
+ ```yaml
114
+ # .canon/policy.yaml
115
+ spec_coverage:
116
+ require_evidence: true
117
+ require_review: # absent = nothing changes, byte for byte
118
+ scope: [verifying, shipped] # default; [] = every scenario
119
+ distinct_actor: true # default
120
+ block_on_findings: true # default
121
+ ```
122
+
123
+ `require_review: {}` takes every default.
124
+
125
+ - **`scope`** — the subject statuses whose scenarios need a review. A
126
+ scenario is in scope when its `@subject:<id>` subject is in one of
127
+ them; `[]` means every scenario. It is independent of
128
+ `spec_coverage.scope`. `spec_coverage.exclude_lanes` also exempts
129
+ scenarios from review.
130
+ - **`distinct_actor`** — a review only counts when both its `reviewer`
131
+ and its actor (`--actor-id`) differ from every actor on that
132
+ scenario's evidence records. An agent cannot review its own attested
133
+ work.
134
+ - **`block_on_findings`** — fail on open blocker findings (below).
135
+
136
+ A malformed `require_review` (an unknown key, an unknown status, `null`,
137
+ a non-mapping) poisons the whole `spec_coverage` section: `canon gate
138
+ check` reports it invalid rather than treating it as off, the same as a
139
+ malformed `require_cases`.
140
+
141
+ Canon's own `.canon/policy.yaml` does not enable `require_review`.
142
+
143
+ ## How it meets the gate
144
+
145
+ ### `unreviewed-promotion`
146
+
147
+ An in-scope scenario with no qualifying review:
148
+
149
+ ```
150
+ unreviewed-promotion world.demo.01 — no review record for this scenario; spec_coverage.require_review wants an independent review (`canon review add`) once its subject is claimed done
151
+ ```
152
+
153
+ or, when the only reviews are self-reviews:
154
+
155
+ ```
156
+ unreviewed-promotion world.demo.01 — every review of this scenario is by an actor that also attested its evidence (`impl-agent`); spec_coverage.require_review.distinct_actor wants a reviewer other than the evidence actor
157
+ ```
158
+
159
+ ### `open-blocker`
160
+
161
+ With `block_on_findings`, a Finding whose latest version is severity
162
+ `blocker` and disposition `open`, on a change listed in an in-scope
163
+ subject's `change_ids` (`canon subject adopt`). The subject string is
164
+ `<change_id>#<round>.<seq>`:
165
+
166
+ ```
167
+ open-blocker c-demo#1.1 — open blocker finding by `reviewer-2` on change `c-demo` (subject `demo-subject`): unbounded subprocess wait; fix it and `canon finding close --disposition fixed --resolution-sha <sha>`, or close it as rejected or deferred
168
+ ```
169
+
170
+ Findings on changes no in-scope subject has adopted do not block.
171
+
172
+ ### Review-waiver advisories
173
+
174
+ A subject moved with `--override-reason` (below) has the exact gaps its
175
+ waiver recorded printed after the violations as advisories; the gate
176
+ stays green for those. A gap the waiver did not record (a blocker raised
177
+ later, a scenario tagged to the subject later) is a violation as usual:
178
+
179
+ ```
180
+ review waivers: 1 advisory(ies) — not failing the gate:
181
+ waived unreviewed-promotion world.demo.01 — no review record ... [waiver: subject `demo-subject` moved to verifying by `lead`: reviewer out until Monday]
182
+ ```
183
+
184
+ ## The status guard
185
+
186
+ With `require_review` present, `canon subject status <id> <state>`
187
+ prints on stderr which review checks it ran or skipped:
188
+
189
+ ```
190
+ canon subject status: review guard for building → verifying (spec_coverage.require_review, scope: verifying, shipped)
191
+ ran unreviewed-promotion — every owned scenario needs a review by an actor other than its evidence actor
192
+ ran open-blocker — 1 adopted change(s) checked for open blocker findings
193
+ ```
194
+
195
+ A target status outside the scope prints `skipped unreviewed-promotion —
196
+ `specced` is not in require_review.scope`. A transition into an in-scope
197
+ status with gaps prints the violations, exits 1, leaves the record
198
+ unchanged, and names `--override-reason`.
199
+
200
+ `--override-reason "<one line>"` waives ONLY `unreviewed-promotion` and
201
+ `open-blocker` — never ship-gate verdict or case gaps, never unreadable
202
+ routing. The new Subject record lists exactly the violations it let
203
+ through:
204
+ `status_override: {to, reason, waived: [{class, subject}], actor: {agent_id}}`
205
+ (`subject` is the scenario id, or `<change_id>#<round>.<seq>` for a
206
+ blocker; actor from `--actor-id`, default `canon`). The command prints
207
+ the `waived ...` lines and `override recorded by `lead` for the N
208
+ violation(s) above: <reason>`. A blank or multi-line reason exits 2. The
209
+ waiver covers only what it recorded, and clears on the subject's next
210
+ status write, so it never outlives the status it was granted for.
211
+
212
+ Without `require_review`, the guard prints nothing and changes nothing.
213
+
214
+ ## Vocabulary in `canon context`
215
+
216
+ `canon context` (text and `--json`) carries a top-level `review` section:
217
+ `findingSeverities` (`blocker`, `should-fix`, `note`),
218
+ `findingDispositions` (`open`, `fixed`, `rejected`, `deferred`),
219
+ `reviewFields` (the Review kind's fields), and `requireReview` (the
220
+ active policy summary, or absent). The `spec_coverage` summary appends
221
+ ` require_review=(scope=... distinct_actor=... block_on_findings=...)`
222
+ when set. Read it before authoring findings or reviews rather than
223
+ guessing the enums.
@@ -87,8 +87,10 @@ canon retrieve --role dev --domain dev --subject subject-domain-loop --k 5
87
87
  - **Changes**: adopt imported plan changes under the Subject (see
88
88
  [Adopt flow](#adopt-flow)).
89
89
  - **Reviews / evidence / divergences**: authored as usual (`canon review
90
- add`, `canon gate`, `canon divergence …`) against the Subject's
91
- scenarios — the join spine carries them back through `scenario_id`.
90
+ add`, `canon finding add`, `canon gate`, `canon divergence …`) against
91
+ the Subject's scenarios and adopted changes — the join spine carries
92
+ them back through `scenario_id` and `change_id`. See `canon-review`
93
+ for the finish-a-subject order.
92
94
 
93
95
  ### 4. Verdicts / trajectories → learn (knowledge out)
94
96
 
@@ -98,7 +100,8 @@ this subject and domain — the exact memory step 2 retrieves next time.
98
100
 
99
101
  ## Status lifecycle + the shipped evidence gate
100
102
 
101
- `canon subject status <id> <state>` performs a policy-gated transition.
103
+ `canon subject status <id> <state> [--override-reason <text>] [--actor-id <id>]`
104
+ performs a policy-gated transition.
102
105
  The legal chain:
103
106
 
104
107
  ```
@@ -141,6 +144,52 @@ empty set; so is one whose `scenario` records route away from the
141
144
  gate's rung. Each refusal prints by failure class (`uncovered-cell`),
142
145
  exits 1, and the status stays `verifying`. `retired` is not gated.
143
146
 
147
+ ### The review guard (`spec_coverage.require_review`, opt-in)
148
+
149
+ When `.canon/policy.yaml` sets `spec_coverage.require_review` (see
150
+ `canon-review`), every status write also runs the review checks and
151
+ prints on stderr which it ran or skipped:
152
+
153
+ ```
154
+ canon subject status: review guard for building → verifying (spec_coverage.require_review, scope: verifying, shipped)
155
+ ran unreviewed-promotion — every owned scenario needs a review by an actor other than its evidence actor
156
+ ran open-blocker — 1 adopted change(s) checked for open blocker findings
157
+ ```
158
+
159
+ A target outside `require_review.scope` prints `skipped
160
+ unreviewed-promotion — `specced` is not in require_review.scope`. A
161
+ transition INTO an in-scope status with gaps — an owned scenario with no
162
+ qualifying review (`unreviewed-promotion`), or an open blocker finding
163
+ on an adopted change (`open-blocker`) — prints the violations, exits 1,
164
+ leaves the record unchanged, and names `--override-reason`.
165
+
166
+ ```bash
167
+ canon subject status demo-subject verifying \
168
+ --override-reason "reviewer out until Monday" --actor-id lead
169
+ ```
170
+
171
+ `--override-reason "<one line>"` waives ONLY `unreviewed-promotion` and
172
+ `open-blocker` — never a ship-gate verdict or case gap, never unreadable
173
+ routing. It prints the `waived ...` lines and `override recorded by
174
+ `lead` for the N violation(s) above: <reason>`, and records on the new
175
+ Subject record exactly the violations it let through:
176
+
177
+ ```yaml
178
+ status_override:
179
+ to: verifying
180
+ reason: reviewer out until Monday
181
+ waived:
182
+ - { class: unreviewed-promotion, subject: world.demo.01 }
183
+ actor: { agent_id: lead } # from --actor-id, default `canon`
184
+ ```
185
+
186
+ A blank or multi-line reason exits 2. The waiver is cleared on the
187
+ subject's next status write, so it never outlives the status it was
188
+ granted for. While it stands, `canon gate check` lists the recorded gaps
189
+ as advisories instead of failing; a gap it did not record (a blocker
190
+ raised later, a scenario tagged later) still fails the gate. Without
191
+ `require_review`, nothing is printed and nothing changes.
192
+
144
193
  ## Adopt flow
145
194
 
146
195
  Planning docs become change/task records via `canon ingest plans` (see
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@journeykit/canon",
3
- "version": "0.10.0",
3
+ "version": "0.12.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.10.0",
34
- "@journeykit/canon-core-linux-x64": "0.10.0",
35
- "@journeykit/canon-core-win32-x64": "0.10.0"
33
+ "@journeykit/canon-core-darwin-arm64": "0.12.0",
34
+ "@journeykit/canon-core-linux-x64": "0.12.0",
35
+ "@journeykit/canon-core-win32-x64": "0.12.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^20.0.0",