@journeykit/canon 0.24.1 → 0.25.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.
@@ -23,7 +23,8 @@ a feature, a one-line request included. A human is needed once per scope:
23
23
  the one thing you never assume, even when the human said they cannot
24
24
  answer until you report: the brief is that report. The one answer
25
25
  approves the brief and the policy, `.canon/policy.yaml`, which is what
26
- the gate grades the work against. Record it only from the human's answer:
26
+ the gate grades the work against. The human may also approve it on the `canon dashboard` briefs page; you never use that form.
27
+ Record it only from the human's answer:
27
28
  `canon brief approve --change-id <slug> --by <the human> --note "<their words>"`.
28
29
  Never edit the policy to make the gate pass: a policy that differs from
29
30
  the approved one fails closed (`POLICY_CHANGED_SINCE_APPROVAL`, not
@@ -31,6 +32,16 @@ a feature, a one-line request included. A human is needed once per scope:
31
32
  approval is not approved (`BRIEF_CHANGED_SINCE_APPROVAL`): it needs a new
32
33
  answer. The ship guard and `canon evidence add` refuse an unapproved or
33
34
  edited brief, but those are backstops: the stop itself is at step 1.
35
+ - **Approval is scoped.** Evidence for a scenario needs the approval of the
36
+ changes its `@flow:<change>.<F-id>` tags name, so a new unapproved change
37
+ on a live subject waits on its own scenarios only; `change new` and
38
+ `subject adopt` warn when the subject is live. `canon subject unadopt
39
+ <change> --subject-id <id>` takes a draft change back off (refused once
40
+ approved). The subject's scope is its newest adopted change, walking back
41
+ past pending drafts (brief not approved, no scenario of the subject tagged
42
+ `@flow:<change>.*`): reopen, evidence and ship read the newest approved or
43
+ tagged change, and the transition prints one line naming every draft set
44
+ aside; a draft a scenario is tagged for blocks until it is approved. → `reference/canon-subject.md`
34
45
  - **Inside an approved scope, never stop for a human.** Fixes, follow-ups,
35
46
  review rounds and re-reviews stay in the approved change (never
36
47
  `canon change new`: a new change is a new scope) and go straight on: scenario,
@@ -50,7 +61,7 @@ a feature, a one-line request included. A human is needed once per scope:
50
61
  1. **Brief.** The planner writes the PRD brief (`.canon/changes/<slug>/brief.md`), checks it with `canon brief check`, and stops for the human. → `reference/role-planner.md`
51
62
  2. **Subject.** `canon subject new`, then `canon change new` (`--kind chore` for work that alters no behavior); the change writes the brief skeleton. → `reference/role-planner.md`
52
63
  3. **Scenarios.** `canon scenario new --flow <F-id> --case <case>` per behavior of a flow step, failure paths (`--case failure`) included. → `reference/role-implementer.md`
53
- 4. **Units.** Split the work; each unit has its own actor id and session id, passed on everything it records. A unit never attests another unit's work.
64
+ 4. **Units.** Split the work; one unit is one flow step's slice with a named verify command, and each unit has its own actor id and session id, passed on everything it records. A unit never attests another unit's work.
54
65
  5. **Implement.** Code with tests named by scenario id (`game.run.01: …`). → `reference/role-implementer.md`
55
66
  6. **Evidence.** `canon evidence add` binds the test run to its scenarios. → `reference/role-implementer.md`
56
67
  7. **Independent review.** A different session, a separate process with its own session id, that can run the checks it signs off, records `canon finding add` and `canon review add`. Every change. → `reference/role-reviewer.md`
@@ -135,10 +146,10 @@ Load only the reference the current step needs.
135
146
  ```bash
136
147
  canon init # canon.yaml, starter policy (--policy light|standard|strict, default strict; see canon-policy), AGENTS.md block, commented plans examples
137
148
  canon init --providers=claude,codex # harnesses for the canon skill; without it init asks on a terminal, else takes the detected .claude/.codex/.omp/.pi
138
- canon skills install # follows canon.yaml skills.providers (else the install lock's, else the detected ones; none detected installs nothing)
149
+ canon skills install # follows canon.yaml skills.providers (else the install lock's, else the detected ones; none detected installs nothing); prunes projected files the bundle no longer ships when their bytes match the lock, keeps a modified one and reports `kept:`
139
150
  canon skills install --providers=claude,codex,omp,pi # any subset or none; rewrites skills.providers
140
- canon skills check # read-only drift check
141
- canon skills doctor # diagnostics, incl. a stray canon projection for an unselected provider and a projection a formatter rewrote; never deletes user files
151
+ canon skills check # read-only drift check; exit 1 (SKILLS_DRIFT) names each orphan, a projected file the bundle no longer ships
152
+ canon skills doctor # diagnostics, incl. a stray canon projection for an unselected provider and a projection a formatter rewrote, and `orphan:` lines for projected files the bundle no longer ships; never deletes user files
142
153
  ```
143
154
 
144
155
  `install` and `doctor` end with `owned-paths:`, the globs of every path
@@ -155,6 +166,13 @@ never by assuming a default. The projection is a read-only copy: Claude
155
166
 
156
167
  ### Route by task
157
168
 
169
+ - **Fix a bug:** the approved change if there is one, else the planner (`canon change new`); first a failing run of the scenario (`canon scenario new --case failure`, then `canon evidence add`), then the fix. `reference/role-implementer.md`.
170
+ - **Add or change a feature:** `canon subject new`, `canon change new`, then the brief. `reference/role-planner.md`.
171
+ - **Pure refactor, dependency, build, CI or infra:** `canon change new <slug> --subject-id <id> --kind chore`; data or interface changes are not chores. `reference/role-planner.md`.
172
+ - **Review someone's change:** `CANON_ROLE=reviewer`, `canon brief check --change-id <slug>`. `reference/role-reviewer.md`.
173
+ - **Resume:** `canon status --json`, then follow `meta.next`. `reference/role-orchestrator.md`.
174
+ - **Plan more work on a live subject:** `canon change new <slug> --subject-id <id>` (it warns until the brief is approved; `canon subject unadopt` parks a draft). `reference/canon-subject.md`, `reference/role-planner.md`.
175
+ - **Upgrade canon in a repo:** `canon migrate --dry-run`. `reference/role-orchestrator.md`.
158
176
  - **Your role's steps:** `reference/role-planner.md`, `role-implementer.md`, `role-reviewer.md`, `role-orchestrator.md`.
159
177
  - **Where the repo stands, subjects and changes:** `reference/canon-subject.md` (`canon status`, `canon subject new|adopt|status`, `canon change new`).
160
178
  - **Write or reorganize `.feature` specs:** `reference/canon-authoring.md` before editing; use the scaffold.
@@ -15,6 +15,8 @@ brief has none of them: harden its Invariants and Checks instead.
15
15
  - Each step reads sees → does → becomes, and names who and what state it starts from.
16
16
  - Acceptance states, per step, what must be observable and which failure path is covered (invalid input, empty state, interrupted, repeated, denied).
17
17
  - Step detail holds rules, states and edge cases of that step only; a rule that spans steps is stated once, where it first applies.
18
+ - Intent states current behavior and Outcome the desired behavior; interfaces are described by behavior (inputs, outputs, errors); no file path, line number or function name stands in a section that decides the work (a `Source:` line may).
19
+ - A flow step is one unit an implementer can verify with one named command; split a step that needs two.
18
20
  - Acceptance writes no scenario ids: scenarios do not exist yet and point at steps.
19
21
 
20
22
  ## NEVER
@@ -20,6 +20,7 @@ marker, never by heading text.
20
20
  - `canon brief check --json`: each finding names the rule, section key, line and hint.
21
21
  - Every section present once, in order, with its marker; no `<!-- canon:hint … -->` comment left; no `TODO`/`TBD`; no empty section (write `None.` where nothing applies).
22
22
  - The flow is a numbered list with ids `F1`, `F2`, … each reading sees → does → becomes.
23
+ - Intent describes what the product does today and Outcome what is true after; leftover implementation paths belong in a `Source:` line or nowhere.
23
24
  - A chore's category is exactly one of `refactor`, `dependency`, `build`, `ci`, `infra`; its invariants and checks say something.
24
25
  - A `canon:waive <rule-id> <section-key> "<reason>"` comment is well formed with a real reason.
25
26
 
@@ -293,7 +293,7 @@ line, was edited after its approval (`BRIEF_CHANGED_SINCE_APPROVAL`), or
293
293
  there is no canon `brief.md` yet but the change comes from an
294
294
  external openspec plan with a `proposal.md` under the plan sources (the
295
295
  check `canon status` and the ship guard use; see
296
- `canon-subject`). The subjects are the scenario's `@subject` owner for
296
+ `canon-subject`; which changes a scenario answers to is scoped by its `@flow:<change>.<F-id>` tags, described there). The subjects are the scenario's `@subject` owner for
297
297
  `--scenario-id`, and every subject adopting the task's change for
298
298
  `--task`:
299
299
 
@@ -354,7 +354,8 @@ review waivers: 1 advisory(ies) — not failing the gate:
354
354
  The waiver lasts only until the subject's next status write.
355
355
 
356
356
  `canon subject status <id> --to shipped` also refuses where a brief exists and
357
- is not approved (the subject's newest adopted change has a canon
357
+ is not approved (the subject's scope, its newest adopted change walking back past pending drafts
358
+ no scenario is tagged `@flow:<change>.*` for, has a canon
358
359
  `brief.md` with no `Approved:` line or one the brief's text no longer
359
360
  matches, or an external `proposal.md` and no canon `brief.md` yet;
360
361
  see `canon-subject`), as an `unreviewed-promotion` naming
@@ -115,9 +115,6 @@ canon report --snapshot /tmp/canon-snap
115
115
  Serves the dashboard web app locally, pointed at a real snapshot:
116
116
 
117
117
  ```bash
118
- # build the app once
119
- bun install && bun run --cwd packages/dashboard build
120
-
121
118
  canon dashboard # regenerates a fresh snapshot every run, serves it
122
119
  canon dashboard --snapshot /tmp/canon-snap --port 8080
123
120
  ```
@@ -129,8 +126,32 @@ canon dashboard --snapshot /tmp/canon-snap --port 8080
129
126
  `manifest.json` (a separately generated snapshot, or a downloaded CI
130
127
  artifact) — never silently regenerated over it. If it has no
131
128
  `manifest.json`, one is generated there once.
132
- - Prints the URL to open, then serves until Ctrl-C. If the app is not
133
- built, `canon dashboard` fails with the exact build command to run.
129
+ - Runs in any repo: the released binary carries the app (in canon's own
130
+ checkout a built `packages/dashboard/dist` is used instead). It prints the
131
+ panels URL and a briefs URL (`/briefs.html`), then serves until Ctrl-C.
132
+ A taken port fails with `DASHBOARD_PORT_IN_USE`: pass `--port <n>` or
133
+ `--port 0`.
134
+ - Without the `duckdb` CLI on `PATH` the server still runs; the panels page
135
+ says why there is no snapshot and how to get one (it never shows demo
136
+ data), and the briefs page works regardless.
137
+
138
+ ### The briefs page (where a human approves briefs)
139
+
140
+ `/briefs.html` lists every live change whose brief awaits the human (no
141
+ `Approved:` line, or edited since) with its intent, outcome, flow steps,
142
+ acceptance, questions and assumptions, and its `canon brief check`
143
+ blockers and advisories. The human types their name and their answer and
144
+ clicks Approve: that records exactly what `canon brief approve --change-id
145
+ <id> --by <name> --note <text>` records, the `Approved:` line bound to the
146
+ brief digest and the policy approval. It is refused when the brief changed
147
+ after the page loaded (`BRIEF_CHANGED_SINCE_PAGE_LOAD`: reload) or while it
148
+ has blockers (`BRIEF_HAS_BLOCKERS`). The server is localhost only and
149
+ refuses requests from other origins (`DASHBOARD_ORIGIN_REFUSED`).
150
+
151
+ For an agent the human stop is unchanged: when clean briefs are waiting,
152
+ tell the human they can approve them in `canon dashboard` (briefs page) or
153
+ answer in chat. An agent never clicks Approve or fills the form, and runs
154
+ `canon brief approve` only from the human's own words.
134
155
 
135
156
  The dashboard renders SEVEN panels — trust matrix, session costs, role
136
157
  memory, flywheel funnel, review burn-down, review rounds, review totals.
@@ -73,8 +73,16 @@ canon finding add --change-id <c> --round <n> --seq <n> \
73
73
  [--reviewed-sha <sha>] [--file-ref path/to/file.rs:120-134] [--scenario-id <id> …] [--actor-id <id>] [--session-id <id>]
74
74
  canon finding close --change-id <c> --round <n> --seq <n> \
75
75
  --disposition fixed --resolution-sha <sha> [--session-id <id>] # or rejected / deferred / open
76
+ canon finding close --change-id <c> --round <n> --seq <n> \
77
+ --disposition rejected|deferred --reason "<why, one line>"
76
78
  ```
77
79
 
80
+ `--reason` is required for `rejected` and `deferred` (refused with
81
+ `FINDING_REASON_REQUIRED` otherwise) and optional for `fixed`. It is stored
82
+ on the closing version, shown by `finding list` as `[reason: …]` and carried
83
+ in the JSON. Give the reason when you reject or defer: it is the only record
84
+ of why the finding was not fixed.
85
+
78
86
  `--scenario-id` (repeatable, each a scenario the index knows; refused with
79
87
  `FINDING_UNKNOWN_SCENARIO` otherwise) names the scenarios the finding is
80
88
  about. After a `fixed` close, fresh evidence for those scenarios alone
@@ -122,14 +130,18 @@ found it: `--introduced-by self` is a usage error (exit 2) that points at
122
130
 
123
131
  Severity is the reviewer's own judgement, never a score canon derives.
124
132
 
133
+ Write a review's or finding's `--summary` as prose that also names the blast
134
+ radius: what else the change touches that the checks do not exercise. It is
135
+ text in the summary, not a field.
136
+
125
137
  ### Dispositions
126
138
 
127
139
  | Disposition | Meaning |
128
140
  |---|---|
129
141
  | `open` | Raised and not yet dealt with. The default on `finding add`; `finding close --disposition open` reopens. |
130
142
  | `fixed` | Fixed by a commit. Requires `--resolution-sha <sha>` naming a commit this repo holds; only `fixed` may carry one. |
131
- | `rejected` | The reviewer's point was considered and declined. |
132
- | `deferred` | Accepted, but postponed to later work. |
143
+ | `rejected` | The reviewer's point was considered and declined. Needs `--reason`. |
144
+ | `deferred` | Accepted, but postponed to later work. Needs `--reason`. |
133
145
 
134
146
  ### How findings fold
135
147
 
@@ -260,7 +260,7 @@ skills:
260
260
  `--providers a,b` (or `none`) answers without asking; with neither it takes
261
261
  the detected ones, and with none detected it writes only a commented example
262
262
  and installs nothing. `.agents` alone never selects Codex. `canon skills
263
- install|check|doctor` follow the section; `canon skills install
263
+ install|check|doctor` follow the section; `install` prunes projected files the bundle no longer ships when their bytes match the lock (a modified one stays, reported as `kept:`), `check` exits 1 (`SKILLS_DRIFT`) naming each orphan and `doctor` prints `orphan:` lines; `canon skills install
264
264
  --providers=<list>` rewrites it in place. Without the section they use the
265
265
  install lock's providers, then the detected ones; nothing undetected is ever
266
266
  added. A present section that is not this shape, or names another harness,
@@ -159,7 +159,7 @@ exits 1, and the status stays `verifying`. `retired` is not gated.
159
159
  **The brief (0.16, 0.17, 0.19):** `verifying → shipped` also refuses where a brief
160
160
  exists and is not approved, read exactly as `canon status` reads it (one
161
161
  shared check, so status says `ship` only when this passes): the subject's
162
- newest adopted change has a canon `.canon/changes/<change>/brief.md`
162
+ scope (its newest adopted change, except pending drafts, see below) has a canon `.canon/changes/<change>/brief.md`
163
163
  with no `Approved:` line outside HTML comments and code fences, whose
164
164
  `Approved:` line carries a digest the brief's text no longer matches
165
165
  (`BRIEF_CHANGED_SINCE_APPROVAL`; the brief was edited after the human
@@ -204,6 +204,30 @@ scenario whose `@subject` owner, or a task whose change
204
204
  an adopting subject, has an enforced, unapproved brief, through the same
205
205
  check; see `canon-gate`.
206
206
 
207
+ **Approval is scoped to the scenarios a change names.** The evidence
208
+ refusal asks the approval of each change the scenario's `@flow:<change>.<F-id>`
209
+ tags name. An untagged scenario answers to the subject's newest adopted
210
+ change whose brief carries an `Approved:` line; if no adopted change ever
211
+ did, a scenario with evidence recorded before the subject first adopted a
212
+ change (and without a brief override) needs no brief, and one without
213
+ answers to the newest adopted change. A task answers to its own
214
+ change. So planning the next change for a `building`, `verifying` or
215
+ `shipped` subject does not freeze re-verification of its existing
216
+ scenarios; scenarios written for the new change (`canon scenario new
217
+ --flow <F-id>` tags them) wait for its approval. The ship guard above is
218
+ unchanged, except for a pending draft: the subject cannot ship until its
219
+ scope is approved, where the scope is found walking the adopted changes from
220
+ the newest back: a change whose brief was never approved and that no scenario
221
+ of the subject is tagged `@flow:<change>.*` for is a pending draft and is
222
+ skipped; the first approved or tagged change is the scope. A tagged unapproved
223
+ draft anywhere above the newest approved change therefore blocks. The reopen
224
+ and ship transitions print one line naming every change set aside (stderr, and
225
+ `meta.warnings` with `--json`): ``<change> is a pending draft (brief not
226
+ approved, no scenario tagged `@flow:<change>.*`): not part of this transition,
227
+ which reads the subject's newest approved or tagged change``, and `canon status`
228
+ marks them "not yet part of the subject's scope" (`pendingDrafts` in JSON)
229
+ while still asking the human to answer a draft's brief.
230
+
207
231
  ### The review guard (`spec_coverage.require_review`, opt-in)
208
232
 
209
233
  When `.canon/policy.yaml` sets `spec_coverage.require_review` (see
@@ -279,8 +303,10 @@ canon subject status: demo-subject → verifying — written directly; nothing t
279
303
  records the change the work goes into:
280
304
  `reopen: { reason: …, change: c-demo, actor: { agent_id: impl } }`. The
281
305
  next status write clears both, as it clears a waiver.
282
- - Inside the approved scope it needs no human: the subject's newest
283
- adopted change's brief is approved, or there is no brief to answer.
306
+ - Inside the approved scope it needs no human: the subject's scope (its
307
+ newest adopted change, walking back past pending drafts no scenario is
308
+ tagged for) has an approved brief, or there
309
+ is no brief to answer.
284
310
  Otherwise it refuses, exit 1, naming the brief to approve, and
285
311
  `--override-reason` does not waive it:
286
312
  ``unreviewed-promotion demo-subject — shipped → verifying: reopening needs the approved scope, and the brief is not approved — .canon/changes/c-next/brief.md has no `Approved:` line; …``.
@@ -345,6 +371,31 @@ counted. If the change write then fails, `adopt` exits `2`, names the
345
371
  subject that now lists the change, and prints `canon subject adopt
346
372
  <change_id> --subject-id <id>` to complete it.
347
373
 
374
+ `canon change new <slug> --subject-id <S>` and `canon subject adopt
375
+ <change> --subject-id <S>` print a warning on stderr, and in JSON
376
+ `meta.warnings`, when `<S>` is `building`, `verifying` or `shipped` and the
377
+ change's brief is not approved. The warning says the subject's existing
378
+ scenarios keep accepting evidence and the subject can still be reopened and
379
+ shipped for its approved scope while no scenario is tagged `@flow:<change>.*`;
380
+ once one is, the subject cannot ship until the brief is approved. The draft is
381
+ pending until then: the reopen and ship guards read the newest adopted change
382
+ with an `Approved:` line instead, so a shipped subject can reopen, re-verify
383
+ and ship again while the draft waits; `canon status` still lists it.
384
+
385
+ ### Taking a draft change back out: `canon subject unadopt`
386
+
387
+ ```bash
388
+ canon subject unadopt <change> --subject-id <S> --as planner --json
389
+ ```
390
+
391
+ Removes the link on both records and keeps the change and its brief, so the
392
+ draft can be adopted elsewhere (a `proposed` holding subject, say) while the
393
+ live subject keeps shipping. It is refused with `SUBJECT_UNADOPT_REFUSED`
394
+ for a change whose brief carries an `Approved:` line, and while a scenario
395
+ the subject owns carries a `@flow:<change>.*` tag (it would fall back to an
396
+ older approval; remove or retag those scenarios first, the hint names them);
397
+ an unlinked pair is a no-op.
398
+
348
399
  ### Starting a new change: `canon change new`
349
400
 
350
401
  For a change that does not exist yet, one command writes it and records
@@ -101,6 +101,10 @@ NOT an attr value) and satisfies that directive's attrs:
101
101
  - "cargo test"
102
102
  ```
103
103
 
104
+ When the body carries a continuation for another session, `summary` holds
105
+ the next command and the claims already verified; reference artifacts and
106
+ record ids instead of pasting them, and redact secrets.
107
+
104
108
  It's validated and compiled to a canon handoff body. Each
105
109
  `handoff-<domain>` directive declares its own attrs (e.g. `design` /
106
110
  `content` domains may require `acceptance-criteria` instead of
@@ -45,12 +45,27 @@ After the first `status`, follow `meta.next` of each write; do not call
45
45
  root (default `.canon/specs`); `reference/canon-authoring.md` has the
46
46
  scaffold. When you fill in a scenario, keep its `# canon: {...}` provenance
47
47
  lines: dropping one aborts `inventory sync` on `missing-provenance`.
48
- 2. **Units.** Give each unit its own actor id and session id and pass them on
49
- everything it records. A delegated unit attests its own work; never attest
50
- another unit's work under your id.
48
+ 2. **Units.** One unit is one flow step's complete path (sees, does,
49
+ becomes) that can be verified on its own, sized to one session. Name the
50
+ command that proves it (a test filter, a script) before you start it, and
51
+ run that command before you call the unit done. Give each unit its own
52
+ actor id and session id and pass them on everything it records. A
53
+ delegated unit attests its own work; never attest another unit's work
54
+ under your id. Units stay inside the one approved change; `tasks.md` is a
55
+ note, not a record.
51
56
  3. **Code and tests.** Name tests by scenario id (`game.run.01: …`) so a test
52
57
  report binds to its scenario. Record a defect you find in your own work
53
58
  with `finding add` the moment you find it, before review.
59
+ **A fix fails first.** For a bug fix, write or extend the scenario's test
60
+ and run it red on the unfixed code, then bind that failing run before you
61
+ change the code (`evidence add --scenario-id <id> --report <fmt>:<path> …`
62
+ with `--verdict divergent`; a failed case never records `faithful`, and
63
+ `--all-scenarios` derives `divergent` from it).
64
+ Fix the code, run again and bind the passing run: the green record
65
+ supersedes the red one, and `canon query --kind evidence --history
66
+ --scenario-id <id>` shows the red-then-green pair to the reviewer. A red
67
+ run is never an accepted gap. When no executable reproduction is
68
+ possible, say in `--ref` what the closest check is and why.
54
69
  4. **Evidence.** One test run that proves several scenarios is bound once with
55
70
  `--all-scenarios`: one record per indexed scenario owning a case, each with
56
71
  the verdict its own cases show; any refusal writes none. For one scenario
@@ -99,6 +114,16 @@ After the first `status`, follow `meta.next` of each write; do not call
99
114
  was accepted is accepted again by the reviewer once new evidence says
100
115
  something different.
101
116
 
117
+ ## Excuses that fail
118
+
119
+ | The thought | The reality |
120
+ |---|---|
121
+ | "The suite is slow; record `faithful` to pass." | `faithful` over a failed case is refused; a limit outside this change is recorded `divergent` with the reason in `--ref`. |
122
+ | "I fixed it, the test would pass." | Run the test red first and bind it, then green. Evidence is a claim; the history is what the reviewer reads. |
123
+ | "The brief is as good as approved." | `evidence add` refuses an unapproved brief's scenarios. Say so and wait. |
124
+ | "I will attest the other unit's work under my id." | Each unit records under its own actor id and session id. |
125
+ | "I was told this twice already." | A correction repeated across runs belongs in a scenario, the policy or a gate; tell the orchestrator. |
126
+
102
127
  ## Retry safety
103
128
 
104
129
  Repeating `evidence add` with identical input returns the existing record
@@ -48,6 +48,36 @@ shell).
48
48
  Order: planner, then (after the human's answer) implementer, then reviewer.
49
49
  Give each a unit: its own `--actor-id` and a session id.
50
50
 
51
+ ### Spawn contract
52
+
53
+ Every spawn prompt names, in this order: the goal (the change slug and the
54
+ one flow step or review it serves); the scope it owns (the scenarios or
55
+ paths it may write); what it must not touch (the brief, the policy, other
56
+ units' scenarios, anything outside its scope); the command that proves it is
57
+ done (the unit's verify command, or `canon gate check` for the reviewer); and
58
+ its own `--actor-id` and `--session-id`. Two agents never write the same
59
+ paths: give parallel implementers disjoint scopes, and where the harness can
60
+ isolate a child in its own worktree, use it. The child reports what it
61
+ verified apart from what it only claims.
62
+
63
+ ### What each harness gives you
64
+
65
+ Last reviewed 2026-10-21. A cell marked `verify` is unconfirmed here: check
66
+ the harness before relying on it; a harness version moves faster than this
67
+ text.
68
+
69
+ | Harness | In-harness subagents | Isolation | Ask the human | Hooks | Skipped |
70
+ |---|---|---|---|---|---|
71
+ | claude (`.claude`) | yes, a subagent tool; a child shares your environment, so pass the role in the task | a subagent can run in its own git worktree | yes, a question tool | yes, in the harness settings; `scripts/canon-retrieve-pre-dispatch.sh` is a sidecar, not installed | none |
72
+ | codex (`.agents`) | `verify`: none when #39 was filed; start the reviewer as a new `codex exec` run | none from canon: use a separate worktree you create | no in `codex exec`; stop and report | `verify` | in-harness spawn, unless a newer version provides it |
73
+ | omp (`.omp`) | yes, the task tool | `verify` | yes, an ask tool | yes | none |
74
+ | pi (`.pi`) | `verify`: assume none; start a new `pi` process | none from canon | `verify` | `verify` | in-harness spawn, isolation |
75
+
76
+ Where a harness has no subagent tool, the reviewer is still a separate
77
+ process with its own session id: start it from the shell with its own
78
+ `CANON_ROLE=reviewer`. A harness that cannot ask the human means you stop
79
+ and report in your final message; you never assume the answer.
80
+
51
81
  The reviewer is a separate process with its own session id, never your
52
82
  session or the implementer's under another label. Start it as a new process
53
83
  (a new `codex exec` run, or the harness's subagent), pass nothing of the
@@ -59,7 +89,9 @@ not sign off.
59
89
 
60
90
  When `status` or `meta.next` shows a step with `actor: human` (the brief's
61
91
  approval), stop everything for that scope, report the brief to the human in
62
- its language, and wait. Do not start the implementer before the answer.
92
+ its language, and wait. Tell the human they can approve the clean briefs in
93
+ `canon dashboard` (the briefs page) or answer in chat; you never click
94
+ Approve or fill the form, and you run `brief approve` only from their words. Do not start the implementer before the answer.
63
95
  Record the answer with `brief approve`. Never approve it yourself and never
64
96
  edit the policy to pass the gate. A policy that no longer matches the
65
97
  approval fails closed (`POLICY_CHANGED_SINCE_APPROVAL`, or
@@ -70,6 +102,27 @@ the approved policy from git; never edit the policy yourself. Inside an
70
102
  approved scope never stop for a human: fixes, follow-ups and re-reviews go
71
103
  straight to the implementer and reviewer in the same change.
72
104
 
105
+ ## Hand off a note, not a copy
106
+
107
+ A note one session leaves for another (a resume note, a spawn prompt, a
108
+ `Handoff` body) points to artifacts and record ids instead of pasting them:
109
+ name the change slug, the scenario ids, the evidence or finding keys and the
110
+ files, and the next command. Say what was verified (a command that ran, with
111
+ its result) apart from what is only claimed. Redact secrets: tokens, keys,
112
+ passwords and private URLs never go into a note, a `--ref`, a `--summary`
113
+ or a brief. When a `Handoff` body carries a continuation, its `summary`
114
+ holds the next command and the verified claims (`reference/canon-vocab.md`).
115
+
116
+ ## Excuses that fail
117
+
118
+ | The thought | The reality |
119
+ |---|---|
120
+ | "Edit the policy so the gate passes." | A changed policy fails closed (`POLICY_CHANGED_SINCE_APPROVAL`); ask the human to re-approve or restore it from git. |
121
+ | "The reviewer can be me under another label." | The reviewer is a separate process with its own real session id (#39). |
122
+ | "Approve the brief; the human said they are busy." | Never approve for the human; report the brief and wait. |
123
+ | "Override the refusal to ship." | Fix the gap the refusal names; `--override-reason` does not waive an approval. |
124
+ | "I have corrected this agent before." | A correction repeated across runs belongs in a scenario, the policy or a gate, not more prose. Tell the human. |
125
+
73
126
  ## Move the subject
74
127
 
75
128
  Move along `proposed → specced → building → verifying → shipped` with
@@ -15,6 +15,7 @@ canon subject new <subject-id> --domain <domain> --title "<title>" --actor-id <u
15
15
  canon change new <slug> --subject-id <subject-id> --title "<title>" --as planner --json
16
16
  canon change new <slug> --subject-id <subject-id> --kind chore --title "<title>" --as planner --json
17
17
  canon subject adopt <slug> --subject-id <other-subject-id> --as planner --json
18
+ canon subject unadopt <slug> --subject-id <subject-id> --as planner --json # a draft change off a live subject; refused once its brief is approved
18
19
  canon brief check --change-id <slug> --as planner --json
19
20
  canon brief approve --change-id <slug> --by "<the human>" --note "<their words>" --as planner --json
20
21
  ```
@@ -34,9 +35,24 @@ unapproved. A policy that no longer matches its approval fails closed
34
35
  `tasks.md` (`tasks.md` is an optional work note nothing gates on). A
35
36
  change that also serves another subject is adopted there too
36
37
  (`subject adopt`): it then belongs to both,
37
- and its open blockers gate both. Inside an approved scope there is no new
38
+ and its open blockers gate both. Planning the next change for a
39
+ `building`, `verifying` or `shipped` subject warns (stderr and JSON
40
+ `meta.warnings`) until its brief is approved: evidence for its existing scenarios is not
41
+ frozen and the subject can still be reopened and shipped for its approved
42
+ scope while no scenario is tagged for the draft (a pending draft); once one
43
+ is, the subject cannot ship before the approval.
44
+ A scenario answers to the changes its `@flow:<change>.<F-id>`
45
+ tags name (`canon scenario new --flow <F-id>` tags one for this change).
46
+ To park a draft that should not gate a live subject, `subject unadopt`
47
+ it and adopt it into a `proposed` subject. Inside an approved scope there is no new
38
48
  subject or change: a fix or follow-up stays in the existing change.
39
- 2. **Write the brief.** A PRD: intent, desired outcome, the user flow as
49
+ 2. **Write the brief.** Briefs outlive the code that exists today and are
50
+ read by sessions that did not write them. Intent says what the product
51
+ does today, in behavior words, and why that is not enough; Desired outcome
52
+ says what is true afterwards. Describe an interface by its behavior
53
+ (inputs, outputs, errors), and keep file paths, line numbers and function
54
+ names out of the sections that decide the work; a `Source: <path>`
55
+ provenance line is the one exception. A PRD: intent, desired outcome, the user flow as
40
56
  numbered steps `F1`, `F2`, … (sees → does → becomes), step detail per
41
57
  step, acceptance per step with its failure path, questions and
42
58
  assumptions, out of scope. Write it in the `language:` of `canon.yaml`;
@@ -55,7 +71,9 @@ unapproved. A policy that no longer matches its approval fails closed
55
71
  - `reference/brief-align.md`: scenarios cover the flow.
56
72
  - `reference/brief-polish.md`: prose.
57
73
  4. **Stop for the human.** Report the brief to the human, in the brief's
58
- language, asking once. Build nothing for that scope (no scenarios, code
74
+ language, asking once, and say they can approve it in `canon dashboard`
75
+ (the briefs page, `/briefs.html`) or answer in chat. You never click
76
+ Approve or fill that form yourself. Build nothing for that scope (no scenarios, code
59
77
  or evidence) until the human answers. The approval is never assumed, even
60
78
  when the human said they cannot answer until you report: the brief is
61
79
  that report. The one answer approves the brief and the policy
@@ -86,6 +104,28 @@ shipped subject `change new` prints the reopen command for the orchestrator
86
104
  ship again until the suite is evidenced after the chore's approval. Work that
87
105
  changes behavior is never a chore: it is a feature change.
88
106
 
107
+ ### A migration is cut in phases
108
+
109
+ A migration (a column, a format, a dependency swap) is cut as expand,
110
+ migrate, contract: one change per phase, each adopting only after the
111
+ previous one shipped. A phase that alters nothing a user or a caller can
112
+ observe (a behavior-preserving internal rename, a build step) is a chore. A
113
+ phase that changes data, a public interface or output is never a chore: it
114
+ is a feature change with a PRD brief, whose Acceptance names the down path
115
+ (how it is undone), the safety condition of any backfill (idempotent,
116
+ resumable, checked before and after), and the destructive cleanup (drop the
117
+ old column, delete the old format) in its own later change.
118
+
119
+ ## Excuses that fail
120
+
121
+ | The thought | The reality |
122
+ |---|---|
123
+ | "The human is unavailable; I will assume the approval." | Never. The brief is the report; stop, and put open questions into Questions and assumptions. |
124
+ | "This could change policy, but I will decide it." | Anything that could change policy goes to the questions. |
125
+ | "A small follow-up needs no new answer." | A follow-up that adds or changes a flow step is a scope change and needs a new `brief approve`. |
126
+ | "This migration is just a chore." | Data, interface or output changes are feature changes. |
127
+ | "Put the file and line in the brief so nobody has to look." | Paths rot; describe behavior. |
128
+
89
129
  ## Hand-off
90
130
 
91
131
  After the approval the implementer takes the scenarios, then the reviewer.
@@ -15,12 +15,14 @@ canon gate check --as reviewer --json # once, before review add
15
15
  canon finding add --change-id <slug> --round <n> --seq <n> --severity <blocker|should-fix|note> --reviewer <me> --actor-id <me> --session-id <session> --summary "<defect>" --scenario-id <id> --scenario-id <id> --as reviewer --json # one --scenario-id per scenario the defect is about
16
16
  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
+ canon finding close --change-id <slug> --round <n> --seq <n> --disposition rejected|deferred --reason "<why, one line>" --as reviewer --json # --reason is required here (FINDING_REASON_REQUIRED); optional for fixed
18
19
  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
20
  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
21
  canon review add --project-id <root-id> --change-id <chore-slug> --reviewer <me> --actor-id <me> --role reviewer --session-id <session> --pin <sha> --upstream-ref <ref> --as reviewer --json # a chore: one change-level review of its invariants and checks against the diff
21
22
  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
22
23
  canon artifacts verify --full --as reviewer --json # the files the evidence and reviews bind are in the artifact store; refuses when an object store is unreachable
23
24
  canon query --kind evidence --history --as reviewer --json # every version, not only the current record
25
+ canon query --kind evidence --history --scenario-id <id> --as reviewer --json # one scenario's red-then-green pair
24
26
  ```
25
27
 
26
28
  `review add` needs `--upstream-ref <ref>` or `--original-spec-ref <path>`;
@@ -70,10 +72,28 @@ does not sign off; record that instead (`--outcome indeterminate`, or
70
72
 
71
73
  ## Check the checks
72
74
 
73
- Passing is not enough: read each bound test against its scenario's Then
74
- steps. Record a finding (`finding add`) when a check pins behavior the
75
- scenario does not require, or skips a Then step the scenario states: such a
76
- test passes without proving the scenario. Do this before you sign.
75
+ Passing is not enough. Read each bound test against its scenario's Then
76
+ steps, in two separate passes, and say which pass a finding came from:
77
+
78
+ 1. **Spec fidelity.** The brief's step and the scenario against the test
79
+ (the PRD read above). Finding: the check pins behavior the scenario does
80
+ not require, or skips a Then step it states.
81
+ 2. **Standards.** The diff and the test against the code it claims to prove.
82
+ Finding: the test passes without proving anything. The usual ways:
83
+ - the expected value is computed by the code under test (tautology);
84
+ - it asserts an implementation detail (a call count, a private name), not behavior;
85
+ - it never reaches the real path or the real side effect (a file written, a record stored, a request sent);
86
+ - it asserts only that no error happened;
87
+ - it mocks the unit it is meant to test.
88
+
89
+ When a check looks weak, you may name the one-line change to the code that
90
+ should make it fail and run it if it is cheap; if the test stays green, record
91
+ the finding. For a fix, read `canon query --kind evidence --history
92
+ --scenario-id <id>`: expect an earlier non-faithful version, or a named
93
+ closest reproduction in `--ref`; record a finding when neither exists. Put
94
+ one blast-radius fact in the finding or review `--summary`: what else the
95
+ change touches that the checks do not exercise. Do all of this before you
96
+ sign.
77
97
 
78
98
  ## Accept a known gap
79
99
 
@@ -93,7 +113,10 @@ ship guard and stays listed as `accepted-divergence` in `status` and
93
113
 
94
114
  - Every defect is a finding; `finding list` shows round, seq, severity and
95
115
  disposition, so the next seq and the open blockers are one command away.
96
- Close a finding once it is fixed.
116
+ Close a finding once it is dealt with. When you close it `rejected` or
117
+ `deferred`, give the reason in one line with `--reason "<why>"`: the close
118
+ is refused without it (`FINDING_REASON_REQUIRED`), and `finding list` shows
119
+ it as `[reason: …]` so the next reader sees why it was not fixed.
97
120
  - Sign off in one batch with `review add`, all or nothing. It writes
98
121
  directly; nothing to promote. canon stores and re-checks attached bytes
99
122
  and never compares images.
@@ -114,4 +137,14 @@ ship guard and stays listed as `accepted-divergence` in `status` and
114
137
  brief stales it. The chore also needs evidence recorded after its approval
115
138
  (the implementer's `--all-scenarios` run) before it ships.
116
139
 
140
+ ## Excuses that fail
141
+
142
+ | The thought | The reality |
143
+ |---|---|
144
+ | "I will review this under a new session label." | A review is a separate process with its own real session id; a relabelled self-review is not independent (#39). |
145
+ | "The tests pass, so I can sign." | Read the test against the Then steps first; a green test can prove nothing. |
146
+ | "I cannot run the tests, but the evidence looks right." | Say so and record `indeterminate` or `unavailable`; never sign what you could not run. |
147
+ | "This is a minor defect; I will skip the finding." | Every defect is a finding; severity says how much it matters. |
148
+ | "I will edit the brief to fix it." | A brief defect is a finding, not an edit. |
149
+
117
150
  Reference: `reference/canon-review.md`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@journeykit/canon",
3
- "version": "0.24.1",
3
+ "version": "0.25.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.24.1",
34
- "@journeykit/canon-core-linux-x64": "0.24.1",
35
- "@journeykit/canon-core-win32-x64": "0.24.1"
33
+ "@journeykit/canon-core-darwin-arm64": "0.25.0",
34
+ "@journeykit/canon-core-linux-x64": "0.25.0",
35
+ "@journeykit/canon-core-win32-x64": "0.25.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^20.0.0",