@journeykit/canon 0.17.0 → 0.21.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.
@@ -103,7 +103,7 @@ this subject and domain — the exact memory step 2 retrieves next time.
103
103
 
104
104
  ## Status lifecycle + the shipped evidence gate
105
105
 
106
- `canon subject status <id> <state> [--override-reason <text>] [--actor-id <id>]`
106
+ `canon subject status <id> --to <state> [--override-reason <text>] [--reason <text>] [--actor-id <id>]`
107
107
  performs a policy-gated transition.
108
108
  The legal chain:
109
109
 
@@ -111,15 +111,16 @@ The legal chain:
111
111
  proposed → specced → building → verifying → shipped
112
112
  ```
113
113
 
114
- plus **any state → retired** (a subject can be retired from anywhere).
115
- Any other transition is rejected by failure class, exits 1, and leaves
116
- the record UNCHANGED (fail closed).
114
+ plus **shipped → verifying** to reopen a shipped subject (with
115
+ `--reason`, below) and **any state → retired** (a subject can be retired
116
+ from anywhere). Any other transition is rejected by failure class, exits
117
+ 1, and leaves the record UNCHANGED (fail closed).
117
118
 
118
119
  ```bash
119
- canon subject status subject-domain-loop specced
120
- canon subject status subject-domain-loop building
121
- canon subject status subject-domain-loop verifying
122
- canon subject status subject-domain-loop shipped # gated — see below
120
+ canon subject status subject-domain-loop --to specced
121
+ canon subject status subject-domain-loop --to building
122
+ canon subject status subject-domain-loop --to verifying
123
+ canon subject status subject-domain-loop --to shipped # gated — see below
123
124
  ```
124
125
 
125
126
  On success the status updates in place; `--json` prints the updated
@@ -148,29 +149,34 @@ empty set; so is one whose `scenario` records route away from the
148
149
  gate's rung. Each refusal prints by failure class (`uncovered-cell`),
149
150
  exits 1, and the status stays `verifying`. `retired` is not gated.
150
151
 
151
- **The brief (0.16, 0.17):** `verifying → shipped` also refuses where a brief
152
+ **The brief (0.16, 0.17, 0.19):** `verifying → shipped` also refuses where a brief
152
153
  exists and is not approved, read exactly as `canon status` reads it (one
153
154
  shared check, so status says `ship` only when this passes): the subject's
154
155
  newest adopted change has a canon `.canon/changes/<change>/brief.md`
155
- with no `Approved:` line outside HTML comments and code fences, or has no
156
- canon `brief.md` yet but comes from an external openspec plan with a
157
- `proposal.md` under the plan sources. It runs with or without a policy.
158
- The refusal is an `unreviewed-promotion` naming the subject, and says
159
- what to do:
156
+ with no `Approved:` line outside HTML comments and code fences, whose
157
+ `Approved:` line carries a digest the brief's text no longer matches
158
+ (`BRIEF_CHANGED_SINCE_APPROVAL`; the brief was edited after the human
159
+ answered), or has no canon `brief.md` yet but comes from an external
160
+ openspec plan with a `proposal.md` under the plan sources. It also
161
+ refuses while `canon brief check` has blocker findings for that change,
162
+ which include a flow step with no scenario or no failure scenario (the
163
+ coverage rules gate shipping, never approval). It runs with or without
164
+ a policy. The refusal is an `unreviewed-promotion` naming the subject, and
165
+ says what to do:
160
166
 
161
167
  ```
162
- canon subject status: unreviewed-promotion demo-subject — verifying → shipped: brief not approved — .canon/changes/c-demo/brief.md has no `Approved:` line; a human must answer the brief, and only from that answer the agent will record their words as `Approved: <who> <when> — "<their words>"` in .canon/changes/c-demo/brief.md
163
- canon subject status: refused — get the human's answer to the brief and record it as its `Approved:` line, or pass --override-reason <text> to move anyway; the waiver is recorded on the subject
168
+ canon subject status: unreviewed-promotion demo-subject — verifying → shipped: brief not approved — .canon/changes/c-demo/brief.md has no `Approved:` line; a human must answer the brief, and only from that answer the agent will record their words with `canon brief approve --change c-demo --by <the human> --note "<their words>"`
169
+ canon subject status: refused — get the human's answer to the brief and record it with `canon brief approve`, or pass --override-reason <text> to move anyway; the waiver is recorded on the subject
164
170
  ```
165
171
 
166
172
  For an external change with no canon brief yet, the reason reads
167
173
  ``.canon/changes/<c>/brief.md does not exist yet: change `<c>` comes from
168
174
  the external plan <proposal>, which canon reads and never edits, so its
169
175
  approval lives in that canon file`` and the agent will ``create
170
- .canon/changes/<c>/brief.md holding a `Source: <proposal>` line and
171
- record their words there as `Approved: <who> <when> — "<their words>"` ``.
176
+ .canon/changes/<c>/brief.md holding a `Source: <proposal>` line, then
177
+ record their words with `canon brief approve --change <c> --by <the human> --note "<their words>"` ``.
172
178
 
173
- Never write the line without the human's answer. When there is no brief to
179
+ Never record the approval without the human's answer. When there is no brief to
174
180
  answer (no adopted change, or a newest change with neither a canon
175
181
  `brief.md` nor an external `proposal.md`), the move is not refused: it
176
182
  prints ``canon subject status: advisory: <id>'s brief is not approved —
@@ -184,9 +190,10 @@ the transition, not the check.
184
190
 
185
191
  This refusal is a backstop, and so is the evidence one: shipping is not
186
192
  the first thing an unapproved brief stops. The agent writes the brief,
187
- then stops and reports it, and nothing is built or attested for the
188
- subject until the human answers. `canon evidence add` refuses (exit 1,
189
- nothing staged) a scenario whose `@subject` owner, or a task whose change
193
+ runs `canon brief check` until it has no blockers, then stops and reports
194
+ it, and nothing is built or attested for the subject until the human
195
+ answers. `canon evidence add` refuses (exit 1, nothing staged) a
196
+ scenario whose `@subject` owner, or a task whose change
190
197
  an adopting subject, has an enforced, unapproved brief, through the same
191
198
  check; see `canon-gate`.
192
199
 
@@ -213,7 +220,7 @@ also refuses a `false-success` (a scenario whose latest audit is
213
220
  sampled audit; see `canon-gate`.
214
221
 
215
222
  ```bash
216
- canon subject status demo-subject verifying \
223
+ canon subject status demo-subject --to verifying \
217
224
  --override-reason "reviewer out until Monday" --actor-id lead
218
225
  ```
219
226
 
@@ -242,6 +249,39 @@ raised later, a scenario tagged later) still fails the gate. Without
242
249
  `require_review`, nothing is printed and nothing changes, except that
243
250
  `shipped` still runs the brief check above.
244
251
 
252
+ ### Reopening a shipped subject (0.18)
253
+
254
+ A follow-up on a shipped subject (a fix, a review round, a scenario its
255
+ gate now misses) reopens it: reopen, add tasks to the approved change,
256
+ scenario, evidence, review, ship.
257
+
258
+ ```bash
259
+ canon subject status demo-subject --to verifying \
260
+ --reason "follow-up: the retry path leaks a file handle" --actor-id impl
261
+ ```
262
+
263
+ ```
264
+ canon subject status: reopened `demo-subject` from shipped, recorded by `impl`: follow-up: the retry path leaks a file handle; keep the follow-up in its approved change `c-demo`, then scenario, evidence and review; shipping again runs every guard
265
+ canon subject status: demo-subject → verifying — written directly; nothing to promote
266
+ ```
267
+
268
+ - `--reason "<one line>"` is required for `shipped → verifying` and
269
+ refused on any other transition (exit 2, as is a blank one). The new
270
+ record carries it with the actor and the change the work goes into:
271
+ `reopen: { reason: …, change: c-demo, actor: { agent_id: impl } }`. The
272
+ next status write clears it, as it clears a waiver.
273
+ - Inside the approved scope it needs no human: the subject's newest
274
+ adopted change's brief is approved, or there is no brief to answer.
275
+ Otherwise it refuses, exit 1, naming the brief to approve, and
276
+ `--override-reason` does not waive it:
277
+ ``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; …``.
278
+ - The review guard does not run on a reopen (it prints `skipped … — a
279
+ reopen moves the subject back to fix its gaps; shipping again runs
280
+ it`). `verifying → shipped` afterwards runs every guard above again.
281
+ - `canon status` flags a shipped subject whose gate went red (whatever
282
+ `verifying → shipped` would now refuse it on) and its only step is
283
+ this reopen.
284
+
245
285
  ## Adopt flow
246
286
 
247
287
  Planning docs written outside canon become change/task records via
@@ -255,25 +295,40 @@ canon subject adopt subject-domain-loop-plan \
255
295
  ```
256
296
 
257
297
  A change made with `canon change new` (below) is recorded and adopted
258
- already, and editing its `tasks.md` needs no import either:
298
+ already. A `tasks.md` in it is an optional work note nothing gates on
299
+ (`canon change new` no longer writes one); an existing one is still read:
259
300
  `canon evidence add --task` and `canon gate task` read the file as it is,
260
301
  and a flip re-imports that change itself. Run `canon ingest plans` for a
261
302
  plan written outside canon, or when `canon query --kind task` must list
262
303
  rows nobody has flipped yet.
263
304
 
264
- `adopt` stamps the change's `subject_id` and adds it to the Subject's
265
- `change_ids`, so `canon query --kind change --change-id …` and `canon
266
- query --kind subject` agree on the link from both ends. It writes only
267
- the side that lacks the link, so rerunning it on a linked pair writes
268
- nothing and exits `0`.
305
+ `adopt` adds the subject to the change's `subject_ids` and the change to
306
+ the Subject's `change_ids`, so `canon query --kind change --change-id …`
307
+ and `canon query --kind subject` agree on the link from both ends. It
308
+ writes only the side that lacks the link, so rerunning it on a linked
309
+ pair writes nothing and exits `0`.
310
+
311
+ A change may belong to several subjects (0.18). Adopting it under a
312
+ second subject adds that subject, never re-points the change, and says
313
+ so:
314
+
315
+ ```
316
+ canon subject adopt: linked change `refactor-core` to subject `editing` as well; the change now belongs to `perception` and `editing` — written directly; nothing to promote
317
+ ```
318
+
319
+ Every reader joins a subject to its changes from both sides (the
320
+ subject's `change_ids`, and every change whose `subject_ids` names it):
321
+ the open-blocker rule, the status guard, `canon status`, the brief and
322
+ the evidence refusal. An open blocker on a shared change gates each
323
+ subject it belongs to. A change written before 0.18 names one subject as
324
+ `subject_id`; it reads, and `canon query --kind change --json` shows it,
325
+ as a one-element `subject_ids`.
269
326
 
270
327
  The two records cannot be written atomically, so the subject is written
271
- first: its `change_ids` is what the gate reads a subject's adopted
272
- changes from (`open-blocker`, the status guard), so a half-done adoption
273
- still has that change's open blockers counted. If the change write then
274
- fails, `adopt` exits `2`, names the subject that now lists the change,
275
- and prints `canon subject adopt <change_id> --subject <id>` to complete
276
- it.
328
+ first: a half-done adoption still has that change's open blockers
329
+ counted. If the change write then fails, `adopt` exits `2`, names the
330
+ subject that now lists the change, and prints `canon subject adopt
331
+ <change_id> --subject <id>` to complete it.
277
332
 
278
333
  ### Starting a new change: `canon change new`
279
334
 
@@ -286,16 +341,21 @@ canon change new add-login --subject auth --title "Add login"
286
341
 
287
342
  ```
288
343
  canon change new: wrote .canon/changes/add-login/brief.md
289
- canon change new: wrote .canon/changes/add-login/tasks.md
290
344
  canon change new: recorded change `add-login` adopted into subject `auth` — written directly; nothing to promote
291
345
  ```
292
346
 
293
- - **Writes** canon's own change dir, `.canon/changes/<slug>/`:
294
- `brief.md` (`# <title>`, `## Why` holding the title, which imports as
295
- the change summary, `## What Changes` with a comment saying what goes
296
- there, and a comment naming where the human's `Approved:` line goes,
297
- so the file starts unapproved) and `tasks.md` (no rows yet, so the
298
- change imports as `proposed`; the openspec checkbox row grammar).
347
+ - **Writes** canon's own change dir, `.canon/changes/<slug>/`, holding
348
+ `brief.md` only: the PRD skeleton (`# <title>`, then the sections intent,
349
+ desired outcome, user flow, step detail, acceptance, questions and
350
+ assumptions, out of scope, each opened by a
351
+ `<!-- canon:section <key> role=… -->` marker and a hint comment, with
352
+ the headings in the repo's `language:`), and a comment naming
353
+ `canon brief approve` as where the human's answer is recorded, so the
354
+ file starts unapproved. The intent section's first paragraph holds the
355
+ title, which imports as the change summary. `--kind chore` writes the
356
+ short chore brief instead (intent, category, invariants, checks, out of
357
+ scope; see `canon brief` below). No `tasks.md` is written:
358
+ `canon status` lists each flow step's scenarios instead.
299
359
  `.canon/changes/` is canon's native plan source: every plan reader
300
360
  reads it with no `plans:` entry, so `canon.yaml` needs no edit and no
301
361
  external plans source is needed (see `canon-plan-import`).
@@ -318,9 +378,10 @@ canon change new: recorded change `add-login` adopted into subject `auth` — wr
318
378
  exist, or when the slug already has a `.canon/changes/<slug>` dir
319
379
  (active or under `.canon/changes/archive/`) or a `change` record.
320
380
 
321
- Add task rows to `tasks.md` as `- [ ] <n> <title>`, and flip each with
322
- `canon gate task <slug>#<n>` once evidence exists. `canon evidence add
323
- --task` and the flip read `tasks.md` as it is, so a new row needs no
381
+ A `tasks.md` is an optional work note canon never writes and nothing
382
+ gates on. If one exists, rows added as `- [ ] <n> <title>` are flipped with
383
+ `canon gate task <slug>#<n>` once evidence exists; `canon evidence add
384
+ --task` and the flip read the file as it is, so a new row needs no
324
385
  `canon ingest plans` run.
325
386
 
326
387
  A change from an external plan (an openspec change listed under
@@ -328,10 +389,59 @@ A change from an external plan (an openspec change listed under
328
389
  `canon subject adopt`) gets its brief as a canon file too:
329
390
  `.canon/changes/<change>/brief.md` holding a `Source: <path>` line that
330
391
  points at the external document and, once the human answers, the
331
- `Approved:` line. Canon never edits the external document. Such a
392
+ `Approved:` line that `canon brief approve` writes. Canon never edits the external document. Such a
332
393
  pointer `brief.md` is not a change of its own: the native reader skips
333
394
  it, so the external source keeps the change, its summary and its tasks.
334
395
 
396
+ ### The brief: `canon brief`, `canon brief check`, `canon brief approve`
397
+
398
+ ```bash
399
+ canon brief [--change <slug>] # state and the next one to three steps; never edits
400
+ canon brief check [--change <slug>] [--json] # findings; exit 0 clean, 2 blockers, 1 error
401
+ canon brief approve --change <slug> --by <the human> --note "<their words>"
402
+ ```
403
+
404
+ Without `--change`, the repo's only live change is read. The brief is the
405
+ PRD `canon change new` writes; sections are found by their
406
+ `<!-- canon:section <key> role=… -->` marker, never by heading text. A
407
+ brief without markers (written before 0.19) checks as `brief.legacy`, an
408
+ advisory, and is read as before.
409
+
410
+ - **Blockers (structure, `brief.*`)**: a missing, duplicated or
411
+ out-of-order section; a flow with no steps or a duplicate step id;
412
+ step detail that does not match the flow ids in order; leftover
413
+ placeholders (a hint comment still there, an empty section,
414
+ `TODO`/`TBD`); a malformed waiver; for a chore, a flow/detail/acceptance
415
+ section, a category outside `refactor|dependency|build|ci|infra`, or
416
+ invariants/checks that say only "none". Once the subject is `building`
417
+ or later, a flow step no scenario points at (`brief.flow-uncovered`) or
418
+ one with no failure scenario (`brief.flow-no-failure`): these two
419
+ **gate shipping, never approval**.
420
+ - **Advisories (prose, `prose.*`)**: phrase lists, sentence and
421
+ paragraph length, and key-sentence marking in `role=normative`
422
+ sections, for `language: en` and `ko`. Never an authorship verdict.
423
+ - **Waivers**: `<!-- canon:waive <rule-id> <section-key> "<reason>" -->`,
424
+ one rule in one section, reason required; listed in the output.
425
+ - **`approve`** records the human's answer: it writes
426
+ `Approved: <who> <when> — "<words>" (brief sha256:<digest>)` into the
427
+ brief and records the policy approval in the same call. It refuses
428
+ (exit 2) while the check has structure blockers. Edit the brief
429
+ afterwards and every reader says `BRIEF_CHANGED_SINCE_APPROVAL` until a
430
+ human answers again and `approve` runs again. Run it only from the
431
+ human's answer, with their words as `--note`.
432
+
433
+ The optional refinement passes that fix findings before the human stop are
434
+ listed in `role-planner.md` in this directory.
435
+
436
+ A **chore** (`canon change new --kind chore`) changes how the subject is
437
+ built or kept, never what it does. Its brief is intent, category,
438
+ invariants, checks, out of scope; it adds no scenarios, and its evidence
439
+ is the subject's suite run again at the chore commit with
440
+ `canon evidence add --all-scenarios --report <kind>:<path> …`. That
441
+ supersedes the old evidence, which makes the pinned reviews stale, so an
442
+ independent re-review follows. Work that changes behavior is a feature
443
+ change, never a chore.
444
+
335
445
  ## Where are we: `canon status`
336
446
 
337
447
  ```bash
@@ -365,7 +475,7 @@ uses, so status and the gate cannot disagree about a gap:
365
475
  `require_review` on, a `warning:` line counts the in-effect reviews that
366
476
  predate review pinning (0.15), checked against the commit each pinned:
367
477
  current, stale (named) and undecidable.
368
- - **Brief** (0.16, 0.17) — for each `building`, `verifying` or `shipped`
478
+ - **Brief** (0.16, 0.17, 0.19) — for each `building`, `verifying` or `shipped`
369
479
  subject, and each `proposed` subject whose newest adopted change has a
370
480
  brief (a canon `brief.md`, or an external `proposal.md`), a `brief:`
371
481
  line under it says whether the brief is approved:
@@ -375,10 +485,16 @@ uses, so status and the gate cannot disagree about a gap:
375
485
  change (the last one adopted; a live dir before an archived one), and
376
486
  only there: a line that starts with `Approved:` (indent allowed), says
377
487
  something after it, and sits outside HTML comments (`<!-- … -->`,
378
- multi-line included) and fenced code blocks (```` ``` ```` or `~~~`),
379
- written by the agent once the human answers, as
380
- `Approved: <who> <when> — "<the human's words>"`. Fixes and follow-ups
381
- live as tasks in the approved change, so a newly adopted change is a
488
+ multi-line included) and fenced code blocks (```` ``` ```` or `~~~`).
489
+ `canon brief approve` writes it from the human's answer, as
490
+ `Approved: <who> <when> — "<the human's words>" (brief sha256:<digest>)`,
491
+ the digest covering the brief without its `Approved:` lines. A brief
492
+ edited after that reads `brief: not approved (<brief> changed after its
493
+ approval (BRIEF_CHANGED_SINCE_APPROVAL))` and names the re-approval
494
+ command: the changed brief needs a new human answer. A line with no
495
+ digest (written before 0.19) stays approved, with an `advisory:` to
496
+ re-approve so it binds to the text. Fixes and follow-ups
497
+ stay in the approved change, so a newly adopted change is a
382
498
  new scope: an older change's approval does not cover it. A change from
383
499
  an external plan has its brief in the same canon file, with a
384
500
  `Source: <path>` line pointing at the external document; an `Approved:`
@@ -392,7 +508,21 @@ uses, so status and the gate cannot disagree about a gap:
392
508
  and never edits, so its approval lives in that canon file)``. With a
393
509
  canon brief and no line it reads
394
510
  ``brief: not approved (<brief> has no `Approved:` line)``.
395
- There is no record kind for it; the file is the record.
511
+ There is no record kind for it; the file is the record. For a brief in
512
+ the PRD format, `brief check: <n> blocker(s), <n> advisory(ies)` follows
513
+ (`canon brief` names the next step), then, per flow step of the
514
+ change, `flow F1` (with ` — <gaps>` when it has no scenario or no
515
+ failure scenario) and under it each scenario that carries
516
+ `@flow:<change>.F1` with its title, `[failure]` when it is, its
517
+ evidence (`yes`/`none`) and its review state.
518
+ - **Red shipped subject** (0.18) — a `shipped` subject that the
519
+ `verifying → shipped` guard would now refuse, by the guard's own checks
520
+ (owned scenarios, verdicts, required cases, audits, and, where
521
+ `require_review` covers `shipped`, reviews and open blockers under
522
+ `block_on_findings`; a gap its waiver recorded is not counted), prints
523
+ ``gate: red — <n> <class> (first: <subject>), …; reopen with `canon subject status <id> --to verifying --reason "<why it reopens>" --actor-id <unit>` ``,
524
+ carries `red: [...]` in `--json`, and that reopen is its only `next:`
525
+ step. Open blockers count against every subject a change belongs to.
396
526
  - **Unowned** — scenarios with no `@subject:` tag.
397
527
  - **Audits** — how many scenarios are sampled for
398
528
  `spec_coverage.require_audit`, confirmed, owed (no audit or
@@ -425,7 +555,7 @@ uses, so status and the gate cannot disagree about a gap:
425
555
  …` (its reason adds "then `canon gate promote`" only under
426
556
  `write.promote: manual`); an open blocker → `canon finding close …`; a
427
557
  due, unreviewed, evidenced scenario → `canon review add …` from another
428
- session; none of these → `canon subject status <id> <next status>`.
558
+ session; none of these → `canon subject status <id> --to <next status>`.
429
559
  Last, unowned scenarios → `canon status`, after tagging them in their
430
560
  `.feature` files (status re-indexes the edit; no sync step). With more
431
561
  than one `specs.roots[]` entry, every suggested `canon scenario new`
@@ -441,15 +571,17 @@ uses, so status and the gate cannot disagree about a gap:
441
571
  unanswered> (an older change's approval does not cover a new scope),
442
572
  and the approval is never assumed: `canon evidence add` refuses <id>'s
443
573
  scenarios and tasks until it is recorded; once the human answers,
444
- <record> and run `canon policy approve --by <the human> --note "<their
445
- words>"` ``, where `<record>` is ``record their words as `Approved: <who>
446
- <when> — "<their words>"` in <brief>``, or, for an external change,
447
- ``create <brief> holding a `Source: <proposal>` line and record their
448
- words there as `Approved: <who> <when> — "<their words>"` ``.
574
+ <record> (one call records the brief and the policy approval)``, where
575
+ `<record>` is ``record their words with `canon brief approve --change
576
+ <c> --by <the human> --note "<their words>"` ``, or, for an external
577
+ change, ``create <brief> holding a `Source: <proposal>` line, then
578
+ record their words with `canon brief approve --change <c> --by <the
579
+ human> --note "<their words>"` ``; for a brief edited after its
580
+ approval, ``re-approve with `canon brief approve …` ``.
449
581
  It is the subject's only step, whatever its status: nothing suggests
450
582
  `canon feature new`, `canon scenario new`, `canon evidence add`, a
451
583
  review or a move-on for it before the human answers, and
452
- `canon subject status <id> shipped` refuses on the same check. The
584
+ `canon subject status <id> --to shipped` refuses on the same check. The
453
585
  approval is never assumed. A subject with no brief to answer gets no human step;
454
586
  its `brief:` line says
455
587
  `not approved (no adopted change; advisory, nothing to approve yet)` or
@@ -457,7 +589,7 @@ uses, so status and the gate cannot disagree about a gap:
457
589
  approve yet)`` and nothing is withheld (a `proposed` subject with no
458
590
  brief, and a `specced` subject, show no `brief:` line at all). Once nothing blocks, a
459
591
  ship-ready verifying subject gets the agent's own
460
- `canon subject status <id> shipped`; status never asks a human to ship.
592
+ `canon subject status <id> --to shipped`; status never asks a human to ship.
461
593
 
462
594
  Status fails closed rather than suggest progress the gate would refuse:
463
595
 
@@ -489,7 +621,7 @@ Status fails closed rather than suggest progress the gate would refuse:
489
621
  ],
490
622
  "unowned": ["cart.promo.01"],
491
623
  "next": [
492
- { "command": "stop and report the brief in openspec/changes/c-promo/proposal.md; build and attest nothing for promo until the human answers (one answer approves brief and policy)", "why": "promo: .canon/changes/c-promo/brief.md does not exist yet: change `c-promo` comes from the external plan openspec/changes/c-promo/proposal.md, which canon reads and never edits, so its approval lives in that canon file (an older change's approval does not cover a new scope), and the approval is never assumed: … once the human answers, create .canon/changes/c-promo/brief.md holding a `Source: openspec/changes/c-promo/proposal.md` line and record their words there as `Approved: <who> <when> — \"<their words>\"` and run `canon policy approve --by <the human> --note \"<their words>\"`", "actor": "human" },
624
+ { "command": "stop and report the brief in openspec/changes/c-promo/proposal.md; build and attest nothing for promo until the human answers (one answer approves brief and policy)", "why": "promo: .canon/changes/c-promo/brief.md does not exist yet: change `c-promo` comes from the external plan openspec/changes/c-promo/proposal.md, which canon reads and never edits, so its approval lives in that canon file (an older change's approval does not cover a new scope), and the approval is never assumed: … once the human answers, create .canon/changes/c-promo/brief.md holding a `Source: openspec/changes/c-promo/proposal.md` line, then record their words with `canon brief approve --change c-promo --by <the human> --note \"<their words>\"` (one call records the brief and the policy approval)", "actor": "human" },
493
625
  { "command": "canon finding close --change-id c-cart --round 1 --seq 1 --disposition fixed --resolution-sha <sha> --actor-id <unit> --session-id <session>", "why": "cart: 1 open blocker finding(s) (first: …); fix it, commit, then close it", "actor": "agent" },
494
626
  { "command": "canon review add --project-id root --scenario-id cart.add.02 --reviewer <reviewer> --actor-id <reviewer> --session-id <review-session> --role reviewer --pin <sha> --original-spec-ref \"<feature file>\"", "why": "cart: 1 of 2 scenario(s) lack a qualifying review; …", "actor": "agent" },
495
627
  { "command": "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" }
@@ -513,7 +645,12 @@ plan document the change comes from (the canon brief's `Source:` line,
513
645
  or, before that file exists, the external `proposal.md`; omitted for a
514
646
  canon-written change); `approval` the text after `Approved:`; and
515
647
  `enforced` whether the missing approval blocks (a brief exists to
516
- answer). The 0.16 `proposal` field is gone.
648
+ answer). The 0.16 `proposal` field is gone. `changed` (omitted when
649
+ false) says the brief was edited after its approval, `legacyApproval`
650
+ that the `Approved:` line carries no digest, `findings` is
651
+ `{ "blockers", "advisories" }` from `canon brief check` (PRD briefs
652
+ only), and `flow` lists each flow step as `{ "id", "change",
653
+ "scenarios": [{ id, title, failure, evidenced, review }], "gaps" }`.
517
654
 
518
655
  Each `next` entry's `actor` is `agent` (its `command` is a canon command
519
656
  line) or `human` (its `command` says what to get from the human).
@@ -0,0 +1,80 @@
1
+ # Role: implementer
2
+
3
+ You build inside an approved scope: scenarios, code, tests and evidence.
4
+ Declare the role with `CANON_ROLE=implementer` (or `--as implementer`).
5
+ Build nothing before the brief is approved: `canon evidence add` refuses an
6
+ unapproved brief's scenarios. If it is not approved, say so and wait; the
7
+ planner and the human own that.
8
+
9
+ ## Commands
10
+
11
+ Every line is complete; fill the `<placeholders>`. `<root-id>` is the `id` of
12
+ a `specs.roots[]` entry in `canon.yaml` (`root` by default).
13
+
14
+ ```bash
15
+ canon status --as implementer --json # once, at the start
16
+ canon feature new <area>.<surface> --title "<title>" --as implementer --json
17
+ canon scenario new <area>.<surface>.<nn> --subject <subject-id> --flow <F-id> --case <happy|edge|failure> --title "<title>" --as implementer --json
18
+ canon scenario new --from - --as implementer --json # many at once: NDJSON on stdin, one {"id","title","subject","flow","case",…} per line
19
+ canon evidence add --project-id <root-id> --all-scenarios --report <junit|cucumber|swift-testing>:<path> --kind test-run --role implementer --ref "<test command>" --actor-id <unit> --session-id <session> --as implementer --json
20
+ canon evidence add --project-id <root-id> --scenario-id <id> --artifact <path> --kind test-run --role implementer --ref "<what it shows>" --actor-id <unit> --session-id <session> --as implementer --json
21
+ canon finding add --change-id <slug> --round <n> --seq <n> --severity <blocker|should-fix|note> --reviewer <unit> --actor-id <unit> --session-id <session> --summary "<defect>" --as implementer --json
22
+ canon inventory sync --as implementer --json # only after a write printed NOT refreshed
23
+ canon gate promote --as implementer --json # only with write.promote: manual
24
+ ```
25
+
26
+ After the first `status`, follow `meta.next` of each write; do not call
27
+ `canon status` again to learn the next step.
28
+
29
+ ## Steps
30
+
31
+ 1. **Scenarios.** One `feature new` per feature surface, then one
32
+ `scenario new` per behavior (several at once with `scenario new --from -`:
33
+ one JSON object per line, validated all-or-nothing), each pointing at the brief's flow step it
34
+ proves, with failure paths (`--case failure`) as well as `happy` and
35
+ `edge`. When the gate asks for a missing case (`require_cases`), add a new
36
+ scenario; never retag an existing one. Every bug fix adds or extends a
37
+ scenario. Canon indexes added, edited or deleted `.feature` files itself;
38
+ run `inventory sync` only when a command prints `NOT refreshed` (a format
39
+ violation or a duplicate id: it names each problem). Specs live at
40
+ `<root>/kind=feature/area=<area>/<surface>.feature` under a `specs.roots[]`
41
+ root (default `.canon/specs`); `reference/canon-authoring.md` has the
42
+ scaffold.
43
+ 2. **Units.** Give each unit its own actor id and session id and pass them on
44
+ everything it records. A delegated unit attests its own work; never attest
45
+ another unit's work under your id.
46
+ 3. **Code and tests.** Name tests by scenario id (`game.run.01: …`) so a test
47
+ report binds to its scenario. Record a defect you find in your own work
48
+ with `finding add` the moment you find it, before review.
49
+ 4. **Evidence.** One test run that proves several scenarios is bound once with
50
+ `--all-scenarios`: one record per indexed scenario owning a case, each with
51
+ the verdict its own cases show; any refusal writes none. For one scenario
52
+ use `--scenario-id <id>` (repeatable) with `--report` or `--artifact`.
53
+ canon stores the bytes in `.canon/artifacts/sha256/` (commit it with the
54
+ ledger) and `canon gate check` re-checks them, so the working-tree report
55
+ may be gitignored. For a scenario about what users see, bind the screenshot
56
+ or recording (`--artifact <path>`). An external evaluator's audit is
57
+ `--kind audit`; canon never generates one. Evidence for a task of an
58
+ imported plan (`--task <id>`) must bind the artifact or report that proves
59
+ the task; a canon command is never that proof.
60
+ 5. **Promote.** Under `write: { promote: auto }` (what `canon init` writes)
61
+ the record is final when `evidence add` returns. Only with
62
+ `write.promote: manual` (or no `write:` key) follow it with
63
+ `gate promote`; `meta.next` names it.
64
+ 6. **A new record makes a signed review stale.** The add prints the reviewer
65
+ and the `canon review add … --pin <sha>` that re-pins it; that is the
66
+ reviewer's call. Do not run it yourself.
67
+
68
+ ## Retry safety
69
+
70
+ Repeating `evidence add` with identical input returns the existing record
71
+ with `meta.unchanged: true` instead of a duplicate. After a timeout or a lost
72
+ response, run it again.
73
+
74
+ ## Next
75
+
76
+ When every scenario has evidence, the review is the reviewer's session, in a
77
+ separate process: do not review your own change. A chore adds no scenarios:
78
+ run the suite again and bind it with `--all-scenarios`, which supersedes the
79
+ old evidence and makes pinned reviews stale. Reference:
80
+ `reference/canon-gate.md`.
@@ -0,0 +1,76 @@
1
+ # Role: orchestrator
2
+
3
+ You run the loop across agents and move the subject. You write no brief,
4
+ code or review yourself. Declare the role with `CANON_ROLE=orchestrator` (or
5
+ `--as orchestrator`); your `canon status` also lists the human's steps.
6
+
7
+ ## Commands
8
+
9
+ Every line is complete; fill the `<placeholders>`.
10
+
11
+ ```bash
12
+ canon status --as orchestrator --json --fields next,waitingOn # at the start and when resuming
13
+ canon brief approve --change <slug> --by "<the human>" --note "<their words>" --as orchestrator --json
14
+ canon subject status <subject-id> --to <proposed|specced|building|verifying|shipped> --actor-id <unit> --as orchestrator --json
15
+ canon subject status <subject-id> --to verifying --reason "<why>" --actor-id <unit> --as orchestrator --json # follow-up on a shipped subject
16
+ canon gate check --as orchestrator --json
17
+ ```
18
+
19
+ ## Read the state
20
+
21
+ `canon status` lists the next steps, each with the `role` that takes it
22
+ (`null` for the human), and `waitingOn` counts the steps held by each other
23
+ owner, `human` included. Call it once at the start and when resuming, not
24
+ after a write: after `subject status --to` or `brief approve`, the write's
25
+ `meta.next` and `meta.waitingOn` are the status for what it touched.
26
+
27
+ ## Spawn each role
28
+
29
+ Start one agent per role with `CANON_ROLE` set in its environment, and tell
30
+ it to load only `reference/role-<role>.md`:
31
+
32
+ ```bash
33
+ CANON_ROLE=planner <harness> "<task: the request, the subject, the brief to write>"
34
+ CANON_ROLE=implementer <harness> "<task: change <slug>, scenarios and evidence>"
35
+ CANON_ROLE=reviewer <harness> "<task: change <slug>; review it>"
36
+ ```
37
+
38
+ A subagent tool that cannot set its child's environment (most in-harness
39
+ subagents share yours) passes the role in the task instead: tell the child
40
+ its role, to load only `reference/role-<role>.md`, and to pass `--as <role>`
41
+ on every canon call (or run `export CANON_ROLE=<role>` first in its own
42
+ shell).
43
+
44
+ Order: planner, then (after the human's answer) implementer, then reviewer.
45
+ Give each a unit: its own `--actor-id` and a session id.
46
+
47
+ The reviewer is a separate process with its own session id, never your
48
+ session or the implementer's under another label. Start it as a new process
49
+ (a new `codex exec` run, or the harness's subagent), pass nothing of the
50
+ implementer's context, and have it record its real session id as
51
+ `--session-id` (#39). A reviewer that cannot run the tests says so and does
52
+ not sign off.
53
+
54
+ ## Relay the human stop
55
+
56
+ When `status` or `meta.next` shows a step with `actor: human` (the brief's
57
+ approval), stop everything for that scope, report the brief to the human in
58
+ its language, and wait. Do not start the implementer before the answer.
59
+ Record the answer with `brief approve`. Never approve it yourself and never
60
+ edit the policy to pass the gate. Inside an approved scope never stop for a
61
+ human: fixes, follow-ups and re-reviews go straight to the implementer and
62
+ reviewer in the same change.
63
+
64
+ ## Move the subject
65
+
66
+ Move along `proposed → specced → building → verifying → shipped` with
67
+ `subject status --to`. It refuses a move the policy does not allow (missing
68
+ evidence or reviews, open blockers, an unapproved brief); fix the gap rather
69
+ than overriding. Move to `shipped` yourself once its guard passes, unless the
70
+ brief says the human ships. A follow-up on a shipped subject goes back to
71
+ `verifying` with `--reason`, then the implementer and reviewer in the same
72
+ change, then `--to shipped` again. `canon status` flags a shipped subject
73
+ whose gate went red. A bad `--to` fails with `error.field` `--to` and
74
+ `error.expected` listing the states.
75
+
76
+ Reference: `reference/canon-subject.md`.