@journeykit/canon 0.31.0 → 0.33.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.
Files changed (125) hide show
  1. package/dist/index.js +4 -0
  2. package/dist/skills/SKILL.src.md +52 -25
  3. package/dist/skills/reference/brief-align.md +2 -2
  4. package/dist/skills/reference/brief-clarify.md +2 -2
  5. package/dist/skills/reference/brief-harden.md +2 -2
  6. package/dist/skills/reference/brief-polish.md +4 -4
  7. package/dist/skills/reference/brief-shape.md +2 -2
  8. package/dist/skills/reference/canon-context.md +1 -1
  9. package/dist/skills/reference/canon-gate.md +5 -5
  10. package/dist/skills/reference/canon-policy.md +7 -4
  11. package/dist/skills/reference/canon-report-dashboard.md +7 -4
  12. package/dist/skills/reference/canon-review.md +2 -2
  13. package/dist/skills/reference/canon-storage.md +4 -3
  14. package/dist/skills/reference/canon-subject.md +18 -16
  15. package/dist/skills/reference/role-implementer.md +12 -10
  16. package/dist/skills/reference/role-orchestrator.md +44 -29
  17. package/dist/skills/reference/role-planner.md +68 -29
  18. package/dist/skills/reference/role-reviewer.md +24 -16
  19. package/man/canon-adapter-authorize.1 +2 -2
  20. package/man/canon-adapter-validate.1 +2 -2
  21. package/man/canon-adapter.1 +2 -2
  22. package/man/canon-artifacts-move.1 +3 -3
  23. package/man/canon-artifacts-store.1 +2 -2
  24. package/man/canon-artifacts-verify.1 +2 -2
  25. package/man/canon-artifacts.1 +2 -2
  26. package/man/canon-brief-approve.1 +12 -3
  27. package/man/canon-brief-check.1 +2 -2
  28. package/man/canon-brief-show.1 +6 -3
  29. package/man/canon-brief.1 +2 -2
  30. package/man/canon-change-new.1 +2 -2
  31. package/man/canon-change.1 +2 -2
  32. package/man/canon-completion.1 +2 -2
  33. package/man/canon-context-pack-add.1 +2 -2
  34. package/man/canon-context-pack-show.1 +2 -2
  35. package/man/canon-context-pack-verify.1 +2 -2
  36. package/man/canon-context-pack.1 +2 -2
  37. package/man/canon-context.1 +2 -2
  38. package/man/canon-dashboard.1 +2 -2
  39. package/man/canon-demo-init.1 +2 -2
  40. package/man/canon-demo-review.1 +2 -2
  41. package/man/canon-demo.1 +2 -2
  42. package/man/canon-dispatch-begin.1 +2 -2
  43. package/man/canon-dispatch-diff.1 +2 -2
  44. package/man/canon-dispatch-end.1 +2 -2
  45. package/man/canon-dispatch.1 +2 -2
  46. package/man/canon-divergence-accept.1 +12 -3
  47. package/man/canon-divergence-add.1 +2 -2
  48. package/man/canon-divergence-defer.1 +2 -2
  49. package/man/canon-divergence-list.1 +2 -2
  50. package/man/canon-divergence-resolve.1 +2 -2
  51. package/man/canon-divergence.1 +2 -2
  52. package/man/canon-evidence-add.1 +2 -2
  53. package/man/canon-evidence-approval-payload.1 +2 -2
  54. package/man/canon-evidence-audit.1 +2 -2
  55. package/man/canon-evidence.1 +2 -2
  56. package/man/canon-export.1 +2 -2
  57. package/man/canon-feature-new.1 +2 -2
  58. package/man/canon-feature.1 +2 -2
  59. package/man/canon-finding-add.1 +2 -2
  60. package/man/canon-finding-close.1 +2 -2
  61. package/man/canon-finding-list.1 +2 -2
  62. package/man/canon-finding.1 +2 -2
  63. package/man/canon-format.1 +2 -2
  64. package/man/canon-gate-check.1 +2 -2
  65. package/man/canon-gate-install-hooks.1 +2 -2
  66. package/man/canon-gate-promote.1 +2 -2
  67. package/man/canon-gate.1 +2 -2
  68. package/man/canon-ingest-plans.1 +2 -2
  69. package/man/canon-ingest-records.1 +2 -2
  70. package/man/canon-ingest-sessions.1 +2 -2
  71. package/man/canon-ingest.1 +2 -2
  72. package/man/canon-init.1 +2 -2
  73. package/man/canon-inventory-sync.1 +2 -2
  74. package/man/canon-inventory.1 +2 -2
  75. package/man/canon-ledger-compact.1 +2 -2
  76. package/man/canon-ledger.1 +2 -2
  77. package/man/canon-migrate.1 +15 -3
  78. package/man/canon-plugin-sync.1 +2 -2
  79. package/man/canon-plugin.1 +2 -2
  80. package/man/canon-policy-approve.1 +9 -3
  81. package/man/canon-policy-show.1 +2 -2
  82. package/man/canon-policy.1 +2 -2
  83. package/man/canon-prompt-add.1 +2 -2
  84. package/man/canon-prompt-show.1 +2 -2
  85. package/man/canon-prompt.1 +2 -2
  86. package/man/canon-purge.1 +2 -2
  87. package/man/canon-query.1 +2 -2
  88. package/man/canon-regime-show.1 +2 -2
  89. package/man/canon-regime.1 +2 -2
  90. package/man/canon-report-build.1 +2 -2
  91. package/man/canon-report-check.1 +2 -2
  92. package/man/canon-report-snapshot.1 +2 -2
  93. package/man/canon-report.1 +2 -2
  94. package/man/canon-retrieve.1 +2 -2
  95. package/man/canon-review-change.1 +2 -2
  96. package/man/canon-review-scenario.1 +2 -2
  97. package/man/canon-review.1 +2 -2
  98. package/man/canon-scenario-new.1 +2 -2
  99. package/man/canon-scenario.1 +2 -2
  100. package/man/canon-schema.1 +2 -2
  101. package/man/canon-search.1 +2 -2
  102. package/man/canon-selftest.1 +2 -2
  103. package/man/canon-skills-check.1 +2 -2
  104. package/man/canon-skills-doctor.1 +2 -2
  105. package/man/canon-skills-install.1 +2 -2
  106. package/man/canon-skills-show.1 +2 -2
  107. package/man/canon-skills.1 +2 -2
  108. package/man/canon-status.1 +2 -2
  109. package/man/canon-strategy-approval-payload.1 +2 -2
  110. package/man/canon-strategy-promote.1 +2 -2
  111. package/man/canon-strategy-rollback.1 +2 -2
  112. package/man/canon-strategy.1 +2 -2
  113. package/man/canon-subject-adopt.1 +2 -2
  114. package/man/canon-subject-move.1 +2 -2
  115. package/man/canon-subject-new.1 +2 -2
  116. package/man/canon-subject-show.1 +2 -2
  117. package/man/canon-subject-unadopt.1 +2 -2
  118. package/man/canon-subject.1 +2 -2
  119. package/man/canon-task-done.1 +2 -2
  120. package/man/canon-task.1 +2 -2
  121. package/man/canon-tier-age.1 +2 -2
  122. package/man/canon-tier.1 +2 -2
  123. package/man/canon-trace.1 +2 -2
  124. package/man/canon.1 +2 -2
  125. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  // src/index.ts
4
4
  import { spawnSync, execSync } from "node:child_process";
5
5
  import { existsSync, readdirSync, realpathSync } from "node:fs";
6
+ import { constants as osConstants } from "node:os";
6
7
  import { resolve, join, basename } from "node:path";
7
8
  import { fileURLToPath } from "node:url";
8
9
  var binaryName = process.platform === "win32" ? "canon.exe" : "canon";
@@ -165,4 +166,7 @@ var result = spawnSync(binary, process.argv.slice(2), {
165
166
  if (result.error) {
166
167
  console.error(`canon: failed to run ${binary}: ${result.error.message}`);
167
168
  }
169
+ if (result.status === null && result.signal) {
170
+ process.exit(128 + (osConstants.signals[result.signal] ?? 0));
171
+ }
168
172
  process.exit(result.status ?? 1);
@@ -26,18 +26,34 @@ a feature, a one-line request included. A human is needed once per scope:
26
26
 
27
27
  - **The approval** (step 1). For a new subject or a scope change, the
28
28
  human must approve the brief. Write the brief,
29
- run `canon brief check` until it reports no blockers, then stop and
30
- report the brief to the human: build nothing for that scope (no
29
+ run `canon brief check` until it reports no blockers, then show it, ask and
30
+ record the answer: build nothing for that scope (no
31
31
  scenarios, code or evidence) before the human answers. The approval is
32
32
  the one thing you never assume, even when the human said they cannot
33
- answer until you report: the brief is that report. The one answer
33
+ answer until you show it. The one answer
34
34
  approves the brief and the policy, `.canon/policy.yaml`, which is what
35
35
  the gate grades the work against. The human may also approve it on the `canon dashboard` briefs page; you never use that form.
36
- The human records the answer, never you:
37
- `canon brief approve <slug> <slug> --approver <their name> --note "<their words>"`
38
- (one or more slugs, one answer for all, nothing written if any is refused)
39
- refuses every agent role (`HUMAN_ONLY`). Report that exact call, every slug filled in, and
40
- wait; never remove or unset your role (`env -u CANON_ROLE`, dropping `--as`) to run it yourself.
36
+ 1. **Show** with `canon brief show <slug> --body`: the brief file's bytes and,
37
+ last, `digest: sha256:<hex>`. Send it to the human's own view without
38
+ retyping it (omp: redirect it to `out://`, which is off for subagents and
39
+ non-TUI sessions and caps at 64 KiB; there, and in a harness with no such
40
+ channel, leave the tool output visible). A subagent has no question tool:
41
+ the orchestrator shows, asks and records.
42
+ 2. **Ask** with the harness question tool (`ask` in omp, `AskUserQuestion` in
43
+ Claude Code, otherwise in the chat): approve or request changes, several
44
+ briefs in one question.
45
+ 3. **Count only an explicit approval.** A question or a condition is a
46
+ revision request (change the brief, check, show and ask again); no answer
47
+ records nothing.
48
+ 4. **Record it** with the relay option:
49
+ `canon brief approve <slug> <slug> --approver <their name> --note "<their words>" --relayed-via <ask|AskUserQuestion|chat> --actor <your actor id> --digest <slug>=<digest shown>`
50
+ (one or more slugs, each with its own `--digest`, one answer for all, nothing written if any is refused).
51
+ The `Approved:` line then carries `(relayed by <role> <actor> via <tool>)`.
52
+ A digest that no longer matches the brief is `RELAY_DIGEST_MISMATCH`: show
53
+ it again. Without `--relayed-via` an agent role gets `HUMAN_ONLY`, and so
54
+ does a repo whose `canon.yaml` says `approval: { relay: deny }`: then the
55
+ human runs the call themselves. Never remove or unset your role
56
+ (`env -u CANON_ROLE`, dropping `--as`) to run it yourself.
41
57
  Never edit the policy to make the gate pass: a policy that differs from
42
58
  the approved one fails closed (`POLICY_CHANGED_SINCE_APPROVAL`, not
43
59
  waivable); stop and ask the human. A brief edited after its
@@ -74,7 +90,7 @@ a feature, a one-line request included. A human is needed once per scope:
74
90
  so in your report. That covers the brief's questions and work inside an
75
91
  approved scope, never the approval itself.
76
92
 
77
- 1. **Brief.** The planner writes the PRD brief (`.canon/changes/<slug>/brief.md`), checks it with `canon brief check`, and stops for the human. `canon skills show role-planner`
93
+ 1. **Brief.** The planner writes the PRD brief (`.canon/changes/<slug>/brief.md`), checks it with `canon brief check`, shows it to the human, asks, and records their explicit approval. `canon skills show role-planner`
78
94
  2. **Subject.** `canon subject new`, then `canon change new` (`--change-kind chore` for work that alters no behavior); the change writes the brief skeleton. `canon skills show role-planner`
79
95
  3. **Scenarios.** `canon scenario new --flow <F-id> --case <case>` per behavior of a flow step, failure paths (`--case failure`) included; many at once with `--input` (below). `canon skills show role-implementer`
80
96
  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.
@@ -170,7 +186,7 @@ variable, and an unknown value is refused (`UNKNOWN_ROLE`).
170
186
  - **A role is set, or you were spawned as one:** load only
171
187
  `canon skills show role-<role>` and do that role's steps. `canon status`
172
188
  and `meta.next` then show only your role's steps, and count the others in
173
- `waitingOn`; `canon schema --as <role>` lists only your role's commands.
189
+ `data.waitingOn`; `canon schema --as <role>` lists only your role's commands.
174
190
  Do not load another role's guide.
175
191
  - **No role (one agent doing everything):** follow `canon status` and load
176
192
  each role guide as its phase starts: planner for steps 1–2,
@@ -185,8 +201,11 @@ A role is guidance, not enforcement: `--role` on `evidence add` and
185
201
 
186
202
  - Start a session with `canon status`: where each subject stands and the next
187
203
  commands. After that, do not call it only to learn the next step: every
188
- write returns `meta.next` (`[{command, argv, why, actor, role}]`), the same
189
- steps `status` would give, narrowed to what the write touched. `actor: human`
204
+ write returns `meta.next` (`[{command, argv, reason, x-canon: {actor, role}}]`), the same
205
+ steps `status` would give, narrowed to what the write touched. Each entry is
206
+ runnable: `argv` is the command without `canon` (with `--json` when you
207
+ called in JSON mode). Guidance that needs a value you must fill in is not a
208
+ next entry; it is in the error `hint` or the report text. `x-canon.actor: human`
190
209
  marks a step only the human takes.
191
210
  - Your role guide's **Commands** block (`canon skills show role-<role>`) is
192
211
  your complete command set, every option of every leaf included. Run `canon schema <command> --json` only for a
@@ -195,20 +214,25 @@ A role is guidance, not enforcement: `--role` on `evidence add` and
195
214
  - Pass `--json` and read stdout as one document, nothing on stderr. Success is
196
215
  `{"ok":true,"data":…,"meta":…}`; failure is
197
216
  `{"ok":false,"error":{…},"meta":…}` (a check that fails keeps its report in
198
- `data`). `meta` carries `command`, `schemaVersion`, `canonVersion`, `next`,
199
- `warnings` and, for a repeated write that changed nothing, `unchanged: true`.
217
+ `data`). `meta` carries `tool`, `toolVersion`, `contract` (`atip/1`), `schemaVersion`,
218
+ `command`, and when present `next`, `warnings` (`[{code, message}]`) and
219
+ `page`; a repeated write that changed nothing reports `data.unchanged: true`.
220
+ Absent members are omitted, never `null` or `[]`. A paged list puts
221
+ `meta.page.nextCursor` on the call's `meta.next` as `--cursor <nextCursor>`;
222
+ no `nextCursor` means no more.
200
223
  On failure branch on `error.code`, fix the option `error.field` names
201
224
  using the `error.expected` values (`field` is an option name, or a JSON
202
225
  pointer such as `/items/2/scenario` into an `--input` document), and
203
226
  follow `error.hint`: every refusal carries a working invocation. The codes
204
227
  are stable; `canon schema --errors --json` lists them and
205
228
  `canon schema --error <CODE>` explains one.
206
- - **Exit codes:** 0 ok; 1 the command's own finding (a failed check, a
207
- violation, or `unverifiable` when nothing was examined); 2 refused (usage,
229
+ - **Exit codes:** only 0, 1 and 2 exist: 0 ok; 1 the command ran and its answer
230
+ is negative or incomplete (a failed check, a violation, `unverifiable` when
231
+ nothing was examined, a partly failed batch); 2 refused to run (usage,
208
232
  `CONFIG_MISSING`, an id or file that does not exist, an invalid argument).
209
233
  A refusal leaves the tree unchanged.
210
234
  - Repeating a write is safe: a record that already exists with identical
211
- content is reported `unchanged` (`meta.unchanged`), not duplicated.
235
+ content is reported `unchanged` (`data.unchanged`), not duplicated.
212
236
  - An option that takes an id is the bare noun (`--change add-login`,
213
237
  `--scenario auth.login.01`, `--actor reviewer-1`, `--project root`,
214
238
  `--session s-1`), and an `--input` document key is that name without `Id`
@@ -217,16 +241,19 @@ A role is guidance, not enforcement: `--role` on `evidence add` and
217
241
  (`canon subject move <id> --to verifying`, `canon change new <slug>`), and
218
242
  `--kind` values carry no `_record` suffix (`--kind evidence`).
219
243
  - `canon status` sends the planner to write the brief while `brief check`
220
- has blockers; the human stop appears only when the brief is clean and
244
+ has blockers; the approval step appears only when the brief is clean and
221
245
  unapproved.
222
246
  - Page long lists with `--limit <n>` and `--cursor <nextCursor>` (`--all`
223
247
  for everything), and project fields with `--fields a,b` (`status`, `export`,
224
248
  `query`, `finding list`, `divergence list`, `context`).
225
249
  - `--override-reason <text>` is the only way to proceed past a waivable
226
250
  refusal; it is recorded, echoed in `data.overrides[]` and listed by `status`
227
- for the human. There is no `--force`. `brief approve`, `policy approve` and
228
- `divergence accept` are human-only and refuse every agent role: report the
229
- exact call, ids filled in, and wait; never unset your role to run one.
251
+ for the human. There is no `--force`. `brief approve`, `policy approve`,
252
+ `divergence accept` and the `migrate --commit` that re-binds `Approved:`
253
+ lines record a human's decision and refuse every agent role (`HUMAN_ONLY`)
254
+ unless they record the explicit answer the human gave you, with
255
+ `--relayed-via <tool>` (show, ask, record, as for the brief; `canon skills
256
+ show role-planner`); never unset your role to run one.
230
257
 
231
258
  ### Many records at once: `--input`
232
259
 
@@ -238,8 +265,8 @@ from a file or stdin) is a batch, run in order and answered with
238
265
  `skipped`, and that element's `data` or `error`). A batch is validated whole
239
266
  before anything is written: an invalid element writes nothing and exits 2,
240
267
  `error.field` pointing at `/items/<i>/<key>`; a write that fails stops there
241
- (exit 1) and re-running the same batch reports the finished elements
242
- `unchanged`. A typo in a key is refused (`INPUT_UNKNOWN_FIELD`), never dropped.
268
+ (exit 1, `BATCH_INCOMPLETE`) and re-running the same batch reports the finished elements
269
+ `unchanged`. A typo in a key is refused (`INPUT_INVALID`, `field` naming the key), never dropped.
243
270
  `canon schema <command> --json` shows the leaf's input schema and an example.
244
271
 
245
272
  ```text
@@ -257,7 +284,7 @@ The five commands that delete or rewrite existing data (`canon purge`,
257
284
  `canon tier age`, `canon ledger compact`, `canon artifacts move`,
258
285
  `canon migrate`) act in two steps. The bare run previews: it writes nothing,
259
286
  exits 0 and prints the effects and a `planDigest`. Read the effects, then act
260
- with `--commit --plan-digest <planDigest>`. It refuses (`PLAN_CHANGED`) when
287
+ with `--commit --plan-digest <planDigest>`. It refuses (`PLAN_STALE`) when
261
288
  the plan differs from the one you previewed, so run the preview again. Appends
262
289
  and state transitions (`gate promote`, `subject move`) run directly. Where a
263
290
  preview helps elsewhere (`init`, `skills install`, `export`, `inventory sync`,
@@ -343,7 +370,7 @@ current step needs.
343
370
  | Name | What it is for |
344
371
  | --- | --- |
345
372
  | `canon` | This core guide: the loop, driving the CLI, routing. |
346
- | `role-planner` | Planner steps 1–2: subject, change, PRD brief, the human stop. |
373
+ | `role-planner` | Planner steps 1–2: subject, change, PRD brief, the approval. |
347
374
  | `role-implementer` | Implementer steps 3–6: scenarios, units, code, evidence. |
348
375
  | `role-reviewer` | Reviewer step 7: findings and signed reviews in a separate session. |
349
376
  | `role-orchestrator` | Orchestrator step 8: dispatch, transitions, resuming, upgrading. |
@@ -33,7 +33,7 @@ A chore adds no scenarios and has no flow: this pass does not apply to it.
33
33
 
34
34
  ## Next
35
35
 
36
- Implement, evidence and review (loop steps 5 to 7). If the brief had to change, the human answers again with `canon brief approve` (theirs; agent roles are refused).
36
+ Implement, evidence and review (loop steps 5 to 7). If the brief had to change, the human answers again: show the brief, ask, and record an explicit approval with `--relayed-via` (`canon skills show role-planner`).
37
37
 
38
38
  ## Invariants (every brief pass)
39
39
 
@@ -41,4 +41,4 @@ Implement, evidence and review (loop steps 5 to 7). If the brief had to change,
41
41
  - Explaining writes nothing. Asked what a finding means or what a pass would do, answer in chat and leave the brief untouched.
42
42
  - No pass requires another as a prerequisite. Run the one the findings or the human point at, in any order.
43
43
  - A pass closes only the findings it rechecked: rerun `canon brief check` after the edit and report a finding as closed only when it is gone from that run. Never claim a rule you did not rerun.
44
- - Write in the brief's language (detected from the text; `language:` in `canon.yaml` overrides it). Edits touch `.canon/changes/<slug>/brief.md` only. A brief edited after approval is not approved: it needs a new human answer and the human's `canon brief approve` again (agent roles are refused).
44
+ - Write in the brief's language (detected from the text; `language:` in `canon.yaml` overrides it). Edits touch `.canon/changes/<slug>/brief.md` only. A brief edited after approval is not approved: it needs a new human answer, shown and asked again, and a new `canon brief approve` (recorded with `--relayed-via` from that answer).
@@ -33,7 +33,7 @@ Every open line is a question with a proposed default or a stated assumption. `c
33
33
 
34
34
  ## Next
35
35
 
36
- `harden` if the flow changed; otherwise report to the human, who answers the questions and approves (`canon brief approve` is theirs; agent roles are refused).
36
+ `harden` if the flow changed; otherwise put the questions and the brief to the human and ask; record an explicit approval with `brief approve` with `--relayed-via <tool>` (`canon skills show role-planner`).
37
37
 
38
38
  ## Invariants (every brief pass)
39
39
 
@@ -41,4 +41,4 @@ Every open line is a question with a proposed default or a stated assumption. `c
41
41
  - Explaining writes nothing. Asked what a finding means or what a pass would do, answer in chat and leave the brief untouched.
42
42
  - No pass requires another as a prerequisite. Run the one the findings or the human point at, in any order.
43
43
  - A pass closes only the findings it rechecked: rerun `canon brief check` after the edit and report a finding as closed only when it is gone from that run. Never claim a rule you did not rerun.
44
- - Write in the brief's language (detected from the text; `language:` in `canon.yaml` overrides it). Edits touch `.canon/changes/<slug>/brief.md` only. A brief edited after approval is not approved: it needs a new human answer and the human's `canon brief approve` again (agent roles are refused).
44
+ - Write in the brief's language (detected from the text; `language:` in `canon.yaml` overrides it). Edits touch `.canon/changes/<slug>/brief.md` only. A brief edited after approval is not approved: it needs a new human answer, shown and asked again, and a new `canon brief approve` (recorded with `--relayed-via` from that answer).
@@ -34,7 +34,7 @@ brief has none of them: harden its Invariants and Checks instead.
34
34
 
35
35
  ## Next
36
36
 
37
- `align` (`canon skills show brief-align`) once scenarios exist; `polish` (`canon skills show brief-polish`) for prose; otherwise report to the human.
37
+ `align` (`canon skills show brief-align`) once scenarios exist; `polish` (`canon skills show brief-polish`) for prose; otherwise show the brief to the human and ask (`canon skills show role-planner`).
38
38
 
39
39
  ## Invariants (every brief pass)
40
40
 
@@ -42,4 +42,4 @@ brief has none of them: harden its Invariants and Checks instead.
42
42
  - Explaining writes nothing. Asked what a finding means or what a pass would do, answer in chat and leave the brief untouched.
43
43
  - No pass requires another as a prerequisite. Run the one the findings or the human point at, in any order.
44
44
  - A pass closes only the findings it rechecked: rerun `canon brief check` after the edit and report a finding as closed only when it is gone from that run. Never claim a rule you did not rerun.
45
- - Write in the brief's language (detected from the text; `language:` in `canon.yaml` overrides it). Edits touch `.canon/changes/<slug>/brief.md` only. A brief edited after approval is not approved: it needs a new human answer and the human's `canon brief approve` again (agent roles are refused).
45
+ - Write in the brief's language (detected from the text; `language:` in `canon.yaml` overrides it). Edits touch `.canon/changes/<slug>/brief.md` only. A brief edited after approval is not approved: it needs a new human answer, shown and asked again, and a new `canon brief approve` (recorded with `--relayed-via` from that answer).
@@ -2,7 +2,7 @@
2
2
 
3
3
  Make the brief something a human reads once and understands. Run it whenever
4
4
  `canon brief check` reports `prose.*` advisories; `canon status` and
5
- `meta.next` name it before the human stop. It never blocks approval, and it
5
+ `meta.next` name it before the approval step. It never blocks approval, and it
6
6
  is not finished by silencing rules: it is finished when the brief reads
7
7
  plainly and each advisory is gone or waived with a reason.
8
8
 
@@ -218,7 +218,7 @@ uses the section key `brief`.
218
218
  1. Fix the mode problems above, then the findings, section by section.
219
219
  2. Run `canon brief check <slug> --json`: the findings are in `data.findings[]`.
220
220
  3. Repeat until no advisory remains, or each remaining one is waived with a reason.
221
- 4. Stop and report to the human, in the brief's language. Do not loop further: a clean run says nothing about whether the brief is good, only that these counts are within budget.
221
+ 4. Stop polishing and show the brief to the human (`canon brief show <slug> --body`), in the brief's language, and ask. Do not loop further: a clean run says nothing about whether the brief is good, only that these counts are within budget.
222
222
 
223
223
  ## NEVER
224
224
 
@@ -236,7 +236,7 @@ uses the section key `brief`.
236
236
 
237
237
  ## Next
238
238
 
239
- Report to the human, who approves with `canon brief approve` (theirs; agent roles are refused).
239
+ Show the brief to the human and ask (`canon brief show <slug> --body`, then the harness question tool); record an explicit approval with `brief approve` with `--relayed-via <tool>` (`canon skills show role-planner`).
240
240
 
241
241
  ## Invariants (every brief pass)
242
242
 
@@ -244,4 +244,4 @@ Report to the human, who approves with `canon brief approve` (theirs; agent role
244
244
  - Explaining writes nothing. Asked what a finding means or what a pass would do, answer in chat and leave the brief untouched.
245
245
  - No pass requires another as a prerequisite. Run the one the findings or the human point at, in any order.
246
246
  - A pass closes only the findings it rechecked: rerun `canon brief check` after the edit and report a finding as closed only when it is gone from that run. Never claim a rule you did not rerun.
247
- - Write in the brief's language (detected from the text, or `language:` in `canon.yaml` when set). Edits touch `.canon/changes/<slug>/brief.md` only. A brief edited after approval is not approved: it needs a new human answer and the human's `canon brief approve` again (agent roles are refused).
247
+ - Write in the brief's language (detected from the text, or `language:` in `canon.yaml` when set). Edits touch `.canon/changes/<slug>/brief.md` only. A brief edited after approval is not approved: it needs a new human answer, shown and asked again, and a new `canon brief approve` (recorded with `--relayed-via` from that answer).
@@ -69,7 +69,7 @@ prints the guidance of each empty one (in `language:`). A wrong heading
69
69
 
70
70
  ## Next
71
71
 
72
- `clarify` if the Questions section still has open lines; `harden` if flow and detail disagree; otherwise report to the human, who approves with `canon brief approve` (theirs; agent roles are refused).
72
+ `clarify` if the Questions section still has open lines; `harden` if flow and detail disagree; otherwise show the brief to the human and ask; record an explicit approval with `brief approve` with `--relayed-via <tool>` (`canon skills show role-planner`).
73
73
 
74
74
  ## Invariants (every brief pass)
75
75
 
@@ -77,4 +77,4 @@ prints the guidance of each empty one (in `language:`). A wrong heading
77
77
  - Explaining writes nothing. Asked what a finding means or what a pass would do, answer in chat and leave the brief untouched.
78
78
  - No pass requires another as a prerequisite. Run the one the findings or the human point at, in any order.
79
79
  - A pass closes only the findings it rechecked: rerun `canon brief check` after the edit and report a finding as closed only when it is gone from that run. Never claim a rule you did not rerun.
80
- - Write in the brief's language (detected from the text; `language:` in `canon.yaml` overrides it). Edits touch `.canon/changes/<slug>/brief.md` only. A brief edited after approval is not approved: it needs a new human answer and the human's `canon brief approve` again (agent roles are refused).
80
+ - Write in the brief's language (detected from the text; `language:` in `canon.yaml` overrides it). Edits touch `.canon/changes/<slug>/brief.md` only. A brief edited after approval is not approved: it needs a new human answer, shown and asked again, and a new `canon brief approve` (recorded with `--relayed-via` from that answer).
@@ -35,7 +35,7 @@ and what to run next. `--json` prints the `{ok, data, meta}` envelope: the
35
35
  header adds no field, and `policy.spec_coverage`/`policy.diagnostics`
36
36
  already carry the same fact. `--fields <list>` keeps only the named
37
37
  top-level fields (`kinds`, `enums`, `policy`, `review`, …); an unknown name is
38
- `UNKNOWN_FIELD` (exit 2).
38
+ `USAGE` (exit 2; `field` is `--fields`, `expected` lists the selectable fields).
39
39
 
40
40
  On a repository that still holds 0.27 canon state, `canon context` warns
41
41
  and puts `canon migrate --json` first in `meta.next`: run `canon migrate`
@@ -299,9 +299,9 @@ never rides along into a confirming audit. `--drop-attachment` and
299
299
 
300
300
  ### Nothing is attested before the brief is approved (`--override-reason`)
301
301
 
302
- The approval is never assumed: the agent writes the brief, stops and
303
- reports it, and builds nothing for that scope until the human answers.
304
- `canon evidence add` is the backstop for that stop (0.16 M7). It
302
+ The approval is never assumed: the agent writes the brief, shows it to the
303
+ human and asks, and builds nothing for that scope until the human answers.
304
+ `canon evidence add` is the backstop for that rule (0.16 M7). It
305
305
  refuses (exit `1`, nothing staged or stored) while a subject the record
306
306
  attests for has an enforced, unapproved brief: a change the record
307
307
  answers to (below) has a canon `.canon/changes/<change>/brief.md` with no `Approved:`
@@ -315,7 +315,7 @@ check `canon status` and the ship guard use; see
315
315
 
316
316
  ```stderr
317
317
  canon evidence add: unreviewed-promotion demo-subject — evidence for world.demo.01: brief not approved — .canon/changes/c-demo/brief.md has no `Approved:` line, so the human has not answered it; once they do, record their words with `canon brief approve c-demo --approver <the human> --note "<their words>"`
318
- canon evidence add: refused — stop, report the brief to the human and wait for their answer: nothing is built or attested for a subject before its brief is approved. Record their answer with `canon brief approve --approver <the human> --note "<their words>"` only from that answer; to attest anyway, pass --override-reason <text>, which is recorded on the evidence record
318
+ canon evidence add: refused — stop and get the human's answer to the brief: nothing is built or attested for a subject before its brief is approved. Show it (`canon brief show --body`), ask with the harness question tool, and on their explicit approval record it with `canon brief approve --approver <their name> --note "<their words>" --relayed-via <the question tool> --actor <your actor id> --digest <change>=<the digest shown>` (or they approve on `canon dashboard`); never unset your role to run it; to attest anyway, pass --override-reason <text>, which is recorded on the evidence record
319
319
  ```
320
320
 
321
321
  For a change from an external plan with no canon brief yet, the first
@@ -347,7 +347,7 @@ brief_override:
347
347
  Under `--json` the same waiver is `data.overrides[]` (`code` of the waived
348
348
  refusal, `reason` verbatim), so a caller sees what its `--override-reason`
349
349
  let through; a repeat of an identical record is `unchanged`
350
- (`meta.unchanged`) and records no second override. `--override-reason`
350
+ (`data.unchanged`) and records no second override. `--override-reason`
351
351
  exists on `subject move` too (see `canon-subject`).
352
352
 
353
353
  A blank or multi-line reason exits 2; an override when nothing refused is
@@ -75,10 +75,13 @@ canon policy approve --approver <who> [--note <text>] [--repo <dir>]
75
75
  A human approves the policy after reviewing `.canon/policy.yaml`, in the
76
76
  same answer that approves a subject's brief (0.16; see the skill's working
77
77
  loop). `canon brief approve` records both in one call. `canon policy
78
- approve` is a human-only leaf: with a role declared (`--as` or `CANON_ROLE`)
79
- it is refused with `HUMAN_ONLY` (exit 2, not retryable), and no flag grants
80
- it to an agent. The human runs it themselves, for a policy-only change or
81
- after the brief answer, supplying `--approver` and `--note`. It
78
+ approve` is a human's decision: with a role declared (`--as` or `CANON_ROLE`)
79
+ it is refused with `HUMAN_ONLY` (exit 2, not retryable) unless it records the
80
+ answer the human gave in the conversation: show them `.canon/policy.yaml`, ask,
81
+ and on an explicit approval run it (the steps are in `canon skills show role-planner`) with `--relayed-via <tool>`, `--actor`,
82
+ `--approver` and `--note` (their name and words); the file then carries `relay`
83
+ (role, actor, tool). The human may run it themselves, for a policy-only change
84
+ or after the brief answer, supplying `--approver` and `--note`. It
82
85
  writes `.canon/policy.approval.json` directly (nothing to promote):
83
86
 
84
87
  ```json
@@ -163,10 +163,13 @@ refused brief): nothing is written and the page says to read it again. A request
163
163
  naming one brief the old way (`changeId`, `sha256`) is still accepted. The server is localhost only and
164
164
  refuses requests from other origins (`DASHBOARD_ORIGIN_REFUSED`).
165
165
 
166
- For an agent the human stop is unchanged: when clean briefs are waiting,
167
- tell the human they can approve them in `canon dashboard` (briefs page) or
168
- answer in chat. An agent never clicks Approve or fills the form, and runs
169
- `canon brief approve` only from the human's own words.
166
+ For an agent: when clean briefs are waiting, show them (`canon brief show
167
+ <slug> --body`), ask with the harness question tool, and tell the human they
168
+ can approve them in `canon dashboard` (briefs page) instead. An agent never
169
+ clicks Approve or fills the form; it runs `canon brief approve` only from the
170
+ human's explicit answer, with `--relayed-via`. The page lists the approvals an
171
+ agent recorded for the human under "Approved through an agent" (`relayed` in
172
+ `GET /api/briefs`), with the role, actor and tool of each.
170
173
 
171
174
  The dashboard renders SEVEN panels — trust matrix, session costs, role
172
175
  memory, flywheel funnel, review burn-down, review rounds, review totals.
@@ -122,7 +122,7 @@ Many findings at once: `--input` takes inline JSON, a file or `-`; an array
122
122
  (or NDJSON) is a batch validated before any write. Keys are the option
123
123
  names in camelCase (`change`, `actor`, `resolutionRev`); `canon schema
124
124
  finding add --json` shows the input schema and an example. An identical
125
- repeat is `unchanged` (`meta.unchanged`) and writes nothing; a different
125
+ repeat is `unchanged` (`data.unchanged`) and writes nothing; a different
126
126
  finding under an occupied key is `FINDING_SEQ_OCCUPIED`.
127
127
 
128
128
  ### A defect the author found in its own work
@@ -198,7 +198,7 @@ allowed, and the commit made from it has the same code tree, so committing it
198
198
  afterwards leaves the review as it was: a review is judged by its pins (the
199
199
  scenario and what its evidence says), not by the commit. Outside git, or
200
200
  with no commit, the write is refused with `NO_COMMIT`. An identical review
201
- written again is `unchanged` (`meta.unchanged` under `--json`) and adds no
201
+ written again is `unchanged` (`data.unchanged` under `--json`) and adds no
202
202
  record.
203
203
 
204
204
  **Done when:** `canon gate check` reports no `unreviewed-promotion` for the
@@ -104,7 +104,8 @@ canon query --kind handoff --since 2026-06-01T00:00:00Z
104
104
  canon query --kind trajectory --json
105
105
 
106
106
  # Page a long result and keep only the fields you need; `meta` carries
107
- # returned, total, hasMore and nextCursor (`--all` returns everything).
107
+ # `page` = {returned, total, nextCursor}; a present `nextCursor` means more, and
108
+ # `meta.next` repeats the call with `--cursor <nextCursor>` (`--all` returns everything).
108
109
  canon query --kind trajectory --json --limit 50 --fields at,kind
109
110
  canon query --kind trajectory --json --limit 50 --cursor <nextCursor>
110
111
  ```
@@ -189,7 +190,7 @@ section listing them and emits a matching stderr `WARN`.
189
190
  `canon purge --kind <kind> --before <RFC3339> [--commit --plan-digest <planDigest>] [--repo <dir>] [--json]`
190
191
  is the canonical data-lifecycle boundary: the bare run previews: it prints the removal set and
191
192
  its `planDigest` and deletes nothing; `--commit --plan-digest <planDigest>` deletes only if
192
- the plan recomputed now has the same digest (`PLAN_CHANGED`, exit 2, when the
193
+ the plan recomputed now has the same digest (`PLAN_STALE`, exit 2, when the
193
194
  data moved). The only purgeable kinds are
194
195
  `session`, `run`, `event`, and `trajectory`; the cutoff applies to every
195
196
  routed rung and every configured aging destination, including retained
@@ -210,7 +211,7 @@ arguments previews an empty removal set.
210
211
  With `--json`, `purge` and `export` answer `{ "data", "meta" }` like every other
211
212
  command: `data` is the plan or manifest, `meta.next` the step after (a plan's is the
212
213
  human's review and `--commit --plan-digest`), and an export's page state (`total`, `returned`,
213
- `nextCursor`) sits in `meta`. `export --json --output <file>` writes the manifest to the
214
+ `nextCursor`) sits in `meta.page`. `export --json --output <file>` writes the manifest to the
214
215
  file and answers with `data: { output, kind, recordCount }`; `--dry-run`
215
216
  answers with what it would write and writes nothing.
216
217
 
@@ -71,7 +71,7 @@ canon subject new subject-domain-loop \
71
71
  NDJSON) is a batch validated before any write, with `operands` holding the
72
72
  id and the other keys the option names in camelCase (`canon schema subject
73
73
  new --json` shows the schema and an example). Writing an identical subject
74
- again is a no-op reported `unchanged` (`meta.unchanged`).
74
+ again is a no-op reported `unchanged` (`data.unchanged`).
75
75
 
76
76
  ### 2. Retrieve subject-scoped strategy memory BEFORE working
77
77
 
@@ -207,8 +207,8 @@ the transition, not the check.
207
207
 
208
208
  This refusal is a backstop, and so is the evidence one: shipping is not
209
209
  the first thing an unapproved brief stops. The agent writes the brief,
210
- runs `canon brief check` until it has no blockers, then stops and reports
211
- it, and nothing is built or attested for the subject until the human
210
+ runs `canon brief check` until it has no blockers, then shows it to the
211
+ human and asks, and nothing is built or attested for the subject until the human
212
212
  answers. `canon evidence add` refuses (exit 1, nothing staged) a
213
213
  scenario whose `@subject` owner, or a task whose change
214
214
  an adopting subject, has an enforced, unapproved brief, through the same
@@ -547,7 +547,7 @@ warn, and a command that writes and judges it is refused
547
547
  human answers again and `approve` runs again. Run it only from the
548
548
  human's answer, with their words as `--note`.
549
549
 
550
- The optional refinement passes that fix findings before the human stop are
550
+ The optional refinement passes that fix findings before the approval step are
551
551
  listed in `role-planner.md` in this directory.
552
552
 
553
553
  A **chore** (`canon change new --change-kind chore`) changes how the subject is
@@ -688,7 +688,7 @@ uses, so status and the gate cannot disagree about a gap:
688
688
  newest change's canon
689
689
  `brief.md` has no `Approved:` line, or it has no canon `brief.md` yet
690
690
  and comes from an external openspec `proposal.md`) and clean gets
691
- `human: stop and report the brief in <brief>; build and attest nothing for <id> until the human answers (one answer approves brief and policy)`,
691
+ `human: show the brief in <brief> to the human and ask; build and attest nothing for <id> until the human answers (one answer approves brief and policy)`,
692
692
  where `<brief>` is the canon `brief.md`, or the external `proposal.md`
693
693
  when no canon brief exists yet. Its reason reads ``<id>: <why
694
694
  unanswered> (an older change's approval does not cover a new scope),
@@ -747,21 +747,23 @@ Status fails closed rather than suggest progress the gate would refuse:
747
747
  ```
748
748
 
749
749
  The steps that follow are not in `data`: they are `meta.next`, most useful
750
- first, with `meta.nextOmitted` counting what was cut and `meta.waitingOn`
750
+ first, with `data.nextOmitted` counting what was cut and `data.waitingOn`
751
751
  (with a declared role, `--as`) counting the steps each other owner holds:
752
752
 
753
753
  ```json
754
754
  "next": [
755
- { "command": "stop and report the brief in openspec/changes/c-promo/proposal.md; build and attest nothing for promo until the human answers; then record their words with `canon brief approve c-promo --approver <the human> --note \"<their words>\"`", "argv": [], "why": "promo: the brief is not approved", "actor": "human", "role": null },
756
- { "command": "canon finding close c-cart --round 1 --seq 1 --disposition fixed --actor <unit> --session <session>", "argv": [], "why": "cart: 1 open blocker finding(s); fix it, commit, then close it", "actor": "agent", "role": "implementer" },
757
- { "command": "canon status", "argv": ["canon", "status"], "why": "1 scenario(s) carry no @subject tag (first: cart.promo.01); add `@subject:<id>` above each in its .feature file, then check again", "actor": "agent", "role": "planner" }
755
+ { "command": "canon brief show c-promo --json", "argv": ["brief", "show", "c-promo", "--json"], "reason": "promo: the brief is not approved; build and attest nothing for promo until the human answers", "x-canon": { "actor": "human" } },
756
+ { "command": "canon gate check --json", "argv": ["gate", "check", "--json"], "reason": "cart: 1 open blocker finding(s); fix it, commit, then close it with `canon finding close`", "x-canon": { "actor": "agent", "role": "implementer" } },
757
+ { "command": "canon status --json", "argv": ["status", "--json"], "reason": "1 scenario(s) carry no @subject tag (first: cart.promo.01); add `@subject:<id>` above each in its .feature file, then check again", "x-canon": { "actor": "agent", "role": "planner" } }
758
758
  ]
759
759
  ```
760
760
 
761
- `argv` is the command split into words, and is empty when the command still
762
- holds a `<placeholder>` for what only the agent knows (or is not a canon
763
- command at all). On a repository that still holds 0.27 canon state,
764
- `canon status` warns (`data.warnings`, `meta.warnings`) and puts `canon
761
+ `argv` is the command without the executable (`canon` + `argv` is the call),
762
+ and carries `--json` when you called in JSON mode. Every next entry is
763
+ runnable as it stands: guidance that needs a value only you know (an actor id,
764
+ a session, the human's words) is not a next entry, it is in the error `hint`
765
+ or the report text. On a repository that still holds 0.27 canon state,
766
+ `canon status` warns (`data.warnings`, `meta.warnings` as `[{code, message}]`) and puts `canon
765
767
  migrate --json` first in `meta.next`: run `canon migrate`, then `canon
766
768
  migrate --commit --plan-digest <digest from the preview>`, then `canon
767
769
  skills install`.
@@ -792,14 +794,14 @@ only the pending drafts, and `flow` lists each flow step of every tagged
792
794
  change as `{ "id", "change", "scenarios": [{ id, title, failure, evidenced,
793
795
  review }], "gaps" }`.
794
796
 
795
- Each `meta.next` entry's `actor` is `agent` (its `command` is a canon command
796
- line) or `human` (its `command` says what to get from the human).
797
+ Each `meta.next` entry's `x-canon.actor` is `agent` or `human` (a step only the
798
+ human takes; its `reason` says what to get from them).
797
799
 
798
800
  `policy.spec_coverage` has the shape `canon context --json` prints
799
801
  (`null` when absent, `{"invalid": …}` when unusable). `statusVersion`
800
802
  changes only when a field changes meaning or is removed. It became `2`
801
803
  in 0.16, when `next[].command` stopped always being a command line
802
- (`actor` tells which), and `brief` was added alongside. It is `3` since
804
+ (`actor` told which), and `brief` was added alongside. It is `3` since
803
805
  0.17, when `brief.proposal` was removed: the canon `brief.md` is
804
806
  `brief.brief`, with `briefExists` and `source`. It is `4` since 0.31, when the
805
807
  `brief` object became the `briefs` list.
@@ -4,15 +4,17 @@ You build inside an approved scope: scenarios, code, tests and evidence.
4
4
  Declare the role with `CANON_ROLE=implementer` (or `--as implementer`); `--role`
5
5
  on what you record then defaults to `implementer`.
6
6
  Build nothing before the brief is approved: `canon evidence add` refuses an
7
- unapproved brief's scenarios. If it is not approved, say so and wait; the
8
- planner and the human own that. The only waiver is `--override-reason
7
+ unapproved brief's scenarios. If it is not approved, tell the orchestrator or planner, who show it to the human and ask;
8
+ build nothing meanwhile. The only waiver is `--override-reason
9
9
  "<why>"`, for the case where you were told to attest while the brief waits for
10
10
  the human's answer: canon records it on the evidence, echoes it in
11
11
  `data.overrides[]` and lists it in `status` for the human. Never use it to
12
12
  skip the wait yourself. `brief approve`, `policy approve` and `divergence
13
- accept` are the human's; they refuse you (`HUMAN_ONLY`). You never run them, and never remove or unset your role (`env -u CANON_ROLE`,
14
- dropping `--as`) to get past the refusal: report the exact call, slug filled in, and
15
- wait; the human runs it (or uses `canon dashboard`).
13
+ accept` are the human's; they refuse you (`HUMAN_ONLY`) unless they record an
14
+ answer the human gave in the conversation (`--relayed-via`, the planner's
15
+ procedure: `canon skills show role-planner`). You never run them without that
16
+ answer, and never remove or unset your role (`env -u CANON_ROLE`, dropping
17
+ `--as`) to get past the refusal.
16
18
 
17
19
  ## Commands
18
20
 
@@ -218,7 +220,7 @@ canon trace add-login_1_1
218
220
  <!-- canon:generated commands-implementer end -->
219
221
 
220
222
  After the first `status`, follow `meta.next` of each write; do not call
221
- `canon status` again to learn the next step. Every record you write takes
223
+ `canon status` again to learn the next step. Each `meta.next` entry is runnable: run `canon` + its `argv`; guidance that needs a value you must fill in is in the error `hint` or the report text, not in `meta.next`. Every record you write takes
222
224
  the commit it is about from `--rev` (default `HEAD`, any git revision). You
223
225
  need not commit first: code that is not committed yet is recorded with its
224
226
  `codeTree` and `worktreeDirty: true`, never refused. Outside git, or in a
@@ -318,7 +320,7 @@ canon evidence add --input '[{"scenario":"auth.login.01","class":"test-run","ref
318
320
  match the scenario because of something outside this change, record the
319
321
  evidence `divergent` and say why in `--ref`; never record `faithful` to
320
322
  pass. The human accepts the tracked gap (`canon divergence accept`
321
- refuses agent roles); fixing the behavior and recording again is the
323
+ refuses agent roles unless it records their explicit answer); fixing the behavior and recording again is the
322
324
  other way out.
323
325
  5. **Promote.** Under `write: { promote: auto }` (what `canon init` writes)
324
326
  the record is final when `evidence add` returns. Only with
@@ -361,15 +363,15 @@ not another evidence step.
361
363
  |---|---|
362
364
  | "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`. |
363
365
  | "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. |
364
- | "The brief is as good as approved." | `evidence add` refuses an unapproved brief's scenarios. Say so and wait. |
366
+ | "The brief is as good as approved." | `evidence add` refuses an unapproved brief's scenarios. Tell the orchestrator or planner, who show it to the human and ask; build nothing meanwhile. |
365
367
  | "I will attest the other unit's work under my id." | Each unit records under its own actor id and session id. |
366
368
  | "I was told this twice already." | A correction repeated across runs belongs in a scenario, the policy or a gate; tell the orchestrator. |
367
- | "I will accept the divergence myself." | `divergence accept` refuses agent roles (`HUMAN_ONLY`); report the scenario, the reason and the tracking reference to the human. |
369
+ | "I will accept the divergence myself." | The human accepts a gap: report the scenario, the reason and the tracking reference so they are asked, and never record an answer they did not give. |
368
370
 
369
371
  ## Retry safety
370
372
 
371
373
  `evidence add` is content-keyed: repeating it with identical input returns
372
- the existing record with `meta.unchanged: true` instead of a duplicate (a
374
+ the existing record with `data.unchanged: true` instead of a duplicate (a
373
375
  batch reports that element `unchanged`). After a timeout or a lost
374
376
  response, run the same command again. `scenario new` is not: an existing
375
377
  scenario id is refused (`SCENARIO_TAG_EXISTS`) and nothing changes, so check