@journeykit/canon 0.13.0 → 0.14.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.
@@ -1,47 +1,113 @@
1
1
  ---
2
2
  name: canon
3
- description: The provider-neutral Canon companion skill for agents using the canon CLI. Routes setup, authoring, validation, storage, ingestion, learning, retrieval, reporting, and provider-specific lazy references.
3
+ description: Use for every build, implement, add-a-feature, change-a-feature or fix request in a repo that has canon.yaml, including sites, apps and games; prefer it there over generic site, app or game builder skills. Runs canon's working loop (brief, subject, scenarios with failure paths, units, implement, evidence, independent review, status transition) and routes to the canon CLI reference for setup, specs, gates, storage, ingestion, retrieval and reports.
4
4
  ---
5
5
 
6
6
  # Canon
7
7
 
8
- Use this single skill when working with Canon in a consumer repository. Canon's CLI remains explicitly modular; choose the command that matches the operation rather than asking an implicit router to guess.
8
+ Features in this repo are managed with canon. This skill's job is the
9
+ working loop below; the CLI manual lives in `reference/*.md` and
10
+ `canon <command> --help`. Start every session with `canon status`: it says
11
+ where each subject stands and lists the next commands.
9
12
 
10
- ## Setup and provider projection
13
+ ## The working loop
14
+
15
+ Follow these steps in order for every request that builds, changes or fixes
16
+ a feature, a one-line request included. Two points stop for a human:
17
+
18
+ - **The brief** (step 1): the request, your assumptions, your open questions.
19
+ - **The policy**: `.canon/policy.yaml` (written by `canon init`) is what the
20
+ gate grades the work against. A human approves it; never edit it to make
21
+ the gate pass.
22
+
23
+ When no human can answer, write the question into the brief, proceed on the
24
+ assumption you state there, and say so in your final report.
25
+
26
+ 1. **Brief.** Write down the request verbatim, your assumptions, and the
27
+ questions you could not ask. It becomes the change's
28
+ `openspec/changes/<slug>/proposal.md`, which
29
+ `canon change new <slug> --subject <id> --title "…"` scaffolds once the
30
+ subject exists (step 2). A human approves the brief.
31
+ 2. **Subject.** `canon subject new <id> --domain <domain> --title "…"`, then
32
+ `canon change new <slug> --subject <id> --title "…"`, and put the brief in
33
+ the change's `proposal.md`.
34
+ 3. **Scenarios.** `canon feature new <area>.<surface> --title "…"`, then one
35
+ `canon scenario new <area>.<surface>.<nn> --subject <id> --case <case> --title "…"`
36
+ per behavior, with failure paths (`--case failure`) as well as `happy`
37
+ and `edge`. When the gate asks for a missing case (`require_cases`), add
38
+ a new scenario; never retag an existing one to satisfy it. Check the
39
+ corpus with `canon format specs` and index it with `canon inventory sync`.
40
+ 4. **Units.** Split the work into units (for example specs, domain code,
41
+ assets, review). Give each unit its own actor id and session id, and pass
42
+ them on everything the unit records (`--actor-id <unit> --session-id <session>`),
43
+ so the ledger shows which unit did what. A delegated unit attests its own
44
+ work; never attest another unit's work under your id.
45
+ 5. **Implement.** Write the code with tests whose names carry the scenario
46
+ id (`game.run.01: …`), so a test report binds to its scenario.
47
+ 6. **Evidence.** Per scenario:
48
+ `canon evidence add --project-id <root-id> --scenario-id <id> --kind test-run --role implementer --ref "<command>" --actor-id <unit> --session-id <session>`,
49
+ with `--report junit:<path>` or `--artifact <path>` when files prove it.
50
+ canon stores those bytes in `.canon/artifacts/sha256/` (commit it with the
51
+ ledger) and `canon gate check` re-checks them. Then `canon gate promote`.
52
+ 7. **Independent review.** A different session from the one that wrote the
53
+ code and evidence reviews the change. Record every defect as a finding,
54
+ including the ones the author found and fixed during implementation:
55
+ `canon finding add --change-id <slug> --round <n> --seq <n> --severity <blocker|should-fix|note> --reviewer <me> --actor-id <me> --session-id <session> --summary "…"`,
56
+ then `canon gate promote`; once fixed,
57
+ `canon finding close --change-id <slug> --round <n> --seq <n> --disposition fixed --resolution-sha <sha>`
58
+ and `canon gate promote`. Every bug fix adds or extends a scenario. The
59
+ reviewer signs off each scenario:
60
+ `canon review add --project-id <root-id> --scenario-id <id> --reviewer <who> --actor-id <who> --role reviewer --session-id <review-session> --pin <sha> --upstream-ref <ref>`
61
+ (written directly; nothing to promote).
62
+ 8. **Transition.** `canon subject status <id> <state>` along
63
+ `proposed → specced → building → verifying → shipped`. It refuses a move
64
+ the policy does not yet allow (missing evidence, missing reviews, open
65
+ blocker findings); fix the gap rather than overriding, and finish with
66
+ `canon gate check` clean.
67
+
68
+ Every write command says whether it staged the record (then
69
+ `canon gate promote`) or wrote it directly.
70
+
71
+ ## Command reference
72
+
73
+ Load only the reference the current step needs.
74
+
75
+ ### Setup and provider projection
11
76
 
12
77
  ```bash
13
- canon init
14
- canon skills install # detects .claude/.codex/.omp/.pi; if none exists, installs Claude+Codex
78
+ canon init # canon.yaml, starter policy, AGENTS.md block, plans home
79
+ canon skills install # detects .claude/.agents|.codex/.omp/.pi; if none exists, installs Claude+Codex
15
80
  canon skills install --providers=claude,codex,omp,pi
16
81
  canon skills check # read-only drift check
17
82
  canon skills doctor # diagnostics; never deletes user files
18
83
  ```
19
84
 
20
85
  The canonical bundle is read-only. Claude receives
21
- `.claude/skills/canon/SKILL.md`; Codex receives the flattened
22
- `.codex/skills/canon.md`; OMP receives `.omp/skills/canon/SKILL.md`; and Pi
23
- receives `.pi/skills/canon/SKILL.md`. Every selected provider receives matching
24
- `reference/**` and `scripts/**` sidecars under its `canon` bundle. OMP and Pi
86
+ `.claude/skills/canon/SKILL.md`; Codex receives
87
+ `.agents/skills/canon/SKILL.md`; OMP receives `.omp/skills/canon/SKILL.md`; and
88
+ Pi receives `.pi/skills/canon/SKILL.md`. Every selected provider receives
89
+ matching `reference/**` and `scripts/**` sidecars under its `canon` bundle. A
90
+ legacy `.codex/skills/canon*` projection from canon 0.13.0 or earlier is
91
+ migrated by `canon skills install` and reported by `check`/`doctor`. OMP and Pi
25
92
  projections are project-local passive skill bundles: the
26
93
  `scripts/canon-retrieve-pre-dispatch.sh` file is available as a sidecar, not
27
- installed as a native hook. Load only the reference needed for the current
28
- task. `canon/skills-dev` is a separate developer-only legacy source and is
29
- installed explicitly with `--source canon/skills-dev`.
94
+ installed as a native hook. `canon/skills-dev` is a separate developer-only
95
+ legacy source and is installed explicitly with `--source canon/skills-dev`.
30
96
 
31
- ## Route by task
97
+ ### Route by task
32
98
 
33
- - **Start product work / pin a durable unit:** read `reference/canon-subject.md`, then run `canon context` and `canon subject`.
34
- - **Write or reorganize `.feature` specs:** read `reference/canon-authoring.md` before editing; use the scaffold.
99
+ - **Where the repo stands, subjects and changes (loop steps 1, 2, 8):** read `reference/canon-subject.md`; it covers `canon status`, `canon subject new|adopt|status` and `canon change new`.
100
+ - **Write or reorganize `.feature` specs (step 3):** read `reference/canon-authoring.md` before editing; use the scaffold.
35
101
  - **Understand available fields/enums/policy bindings:** read `reference/canon-context.md` and `reference/canon-vocab.md`.
36
102
  - **Capture replayable run inputs / version prompts:** read `reference/canon-context.md`, then use `canon context-pack create|show|verify` and `canon prompt register|show` with explicit repository-relative JSON manifests.
37
103
  - **Validate or index specs:** read `reference/canon-fmt.md` and `reference/canon-inventory.md`.
38
- - **Run evidence gates, flip tasks, install hooks:** read `reference/canon-gate.md`.
39
- - **Finish a subject (claim work done):** read `reference/canon-review.md`. Order: test-run evidence (`canon evidence add`) → independent review of the adopted change (`canon finding add` per issue, fix, `canon finding close --disposition fixed --resolution-sha <sha>`) → `canon review add` per scenario by a reviewer other than the evidence actor → `canon subject status`. Commit each record with `canon gate promote`.
40
- - **Ingest artifacts or sessions:** read `reference/canon-artifact-ingest.md`, `reference/canon-session-ingest.md`, or `reference/canon-plan-import.md`.
104
+ - **Evidence, gates, task checkboxes, hooks (step 6):** read `reference/canon-gate.md`.
105
+ - **Findings and reviews (step 7):** read `reference/canon-review.md`.
106
+ - **Ingest artifacts, sessions or plans:** read `reference/canon-artifact-ingest.md`, `reference/canon-session-ingest.md`, or `reference/canon-plan-import.md`.
41
107
  - **Configure storage, policy, plugins, or strategy learning:** read `reference/canon-storage.md`, `reference/canon-policy.md`, `reference/canon-plugins.md`, or `reference/canon-learn.md`.
42
108
  - **Retrieve dispatch guidance:** read `reference/canon-retrieve.md` and load `scripts/canon-retrieve-pre-dispatch.sh` when wiring hooks.
43
109
  - **Read generated reports and dashboards:** read `reference/canon-report-dashboard.md`.
44
110
 
45
- ## Safety contract
111
+ ### Safety contract
46
112
 
47
- `canon format --check`, `canon skills check`, and `canon gate check` are read-only. Do not hand-edit generated reports, provenance lines, locks, or task evidence. Inspect `canon context` before authoring typed artifacts, and treat every failure class as actionable evidence rather than bypassing the gate.
113
+ `canon status`, `canon format <root>`, `canon skills check`, and `canon gate check` are read-only. Do not hand-edit generated reports, provenance lines, locks, or task evidence. Inspect `canon context` before authoring typed artifacts, and treat every failure class as actionable evidence rather than bypassing the gate.
@@ -18,6 +18,23 @@ canon context --json # the full machine-readable surface
18
18
  canon context --repo ../other # resolve a specific repo's surface
19
19
  ```
20
20
 
21
+ The outline opens with a short header, then a blank line, then the
22
+ schema outline starting at `capabilityVersion:`:
23
+
24
+ ```text
25
+ status: run `canon status` for this repo's subjects, gaps and next commands
26
+ warning: no policy: .canon/policy.yaml not found, so `canon gate check` requires no evidence, failure cases or review here
27
+ ```
28
+
29
+ The `warning:` line appears when the policy file is absent or unreadable
30
+ or has no `spec_coverage` section (the gate then requires no evidence,
31
+ cases or review), or when that section is invalid (the gate refuses until
32
+ it is fixed); it is omitted when coverage is in force. `context` describes what CAN be
33
+ authored; `canon status` (see `canon-subject`) says where the repo stands
34
+ and what to run next. `--json` is unchanged: the header adds no field,
35
+ and `policy.spec_coverage`/`policy.diagnostics` already carry the same
36
+ fact.
37
+
21
38
  ## Reproducible context packs and prompt bundles
22
39
 
23
40
  Use an explicit, repository-relative JSON manifest when a run needs a
@@ -24,7 +24,7 @@ Every violation carries one of these stable, grep-able strings:
24
24
  | `uncovered-cell` | Either a policy-required evidence cell (role × artifact) with no matching record, or — with `spec_coverage` enabled — a spec scenario that is unimplemented or mismatched, or a feature surface missing a `require_cases` case. The detail string distinguishes them. Coverage means "a test exists", not "a test passed": even a `Divergent` verdict satisfies the role-cell form. |
25
25
  | `unreviewed-promotion` | An artifact tagged `reviewed` has no matching ledger review record — or, with `spec_coverage.require_review` set, an in-scope scenario has no qualifying `Review` (none at all, or only reviews by its own evidence actor). See `canon-review`. |
26
26
  | `trust-below-required` | Achieved trust level is below `policy.yaml`'s `trust_required` for its class — RELEASE-scoped only (`canon gate check --release`); never fires on an ordinary run. |
27
- | `stale-evidence` | A passing record degraded to stale: its declared surface changed since its `evidence_sha`, or HEAD moved past `staleness.max_commits_behind`. Only degrades an already-green record. |
27
+ | `stale-evidence` | A passing record degraded to stale: its declared surface changed since its `evidence_sha`, or HEAD moved past `staleness.max_commits_behind`. Only degrades an already-green record. Also: a file bound to a latest evidence record whose bytes are gone — no intact blob in `.canon/artifacts/sha256/` and no working-tree copy with the recorded digest (see Bound evidence stays provable below). |
28
28
  | `malformed-evidence` | A candidate record doesn't parse, is misfiled, or carries an unparseable interim tag. Malformed evidence is no evidence. |
29
29
  | `flagged` | The human-only `flagged` overlay is set — never green regardless of passing evidence. |
30
30
  | `unevidenced-flip` | `canon gate task <task_id>` was asked to flip a checkbox with no matching, non-`Divergent` evidence record. |
@@ -34,7 +34,7 @@ Every violation carries one of these stable, grep-able strings:
34
34
  ## `canon gate check [--repo <dir>] [--release]`
35
35
 
36
36
  Assembles coverage + ledger + staleness + the always-on trust-ladder
37
- check, plus the opt-in spec-coverage check, over the resolved repo's
37
+ check + the bound-artifact check, plus the opt-in spec-coverage check, over the resolved repo's
38
38
  corpus and runs them, printing every violation grouped by failure class.
39
39
  `--release` additionally engages the release-scoped
40
40
  `trust-below-required` check; the trust-ladder check is never dropped
@@ -60,7 +60,7 @@ adds the other direction: it starts from the Scenario corpus
60
60
  an unimplemented or mismatched spec becomes a reported violation.
61
61
 
62
62
  ```yaml
63
- # .canon/policy.yaml — ABSENT by default; absent means zero violations
63
+ # .canon/policy.yaml — absent means zero violations; `canon init` writes a starter
64
64
  spec_coverage:
65
65
  require_evidence: true
66
66
  scope: [building, verifying] # optional; omit for the whole corpus
@@ -69,11 +69,28 @@ spec_coverage:
69
69
  require_review: {} # optional; see Independent review below
70
70
  ```
71
71
 
72
+ `canon init` writes a starter `.canon/policy.yaml` with this section on
73
+ (`require_evidence: true`, `require_cases: [failure]`,
74
+ `require_review: {}`; see `canon-policy`). When the section is absent and
75
+ the indexed corpus has scenarios, the gate says so after its result, on
76
+ stdout like its other advisories, and the exit code is unchanged:
77
+
78
+ ```
79
+ advisory: spec_coverage is off — 12 scenario(s) are not checked for evidence; see .canon/policy.yaml
80
+ ```
81
+
82
+ A present section, even `require_evidence: false` or a malformed one
83
+ (already a violation), and an empty corpus print no advisory.
84
+
72
85
  - **Unimplemented** — no `EvidenceRecord` carries the scenario's
73
86
  `(project_id, scenario_id)`. Author one with
74
87
  `canon evidence add --scenario-id <id> --project-id <root-id>`; both
75
88
  flags are required together, because two spec roots may carry the same
76
- scenario id.
89
+ scenario id. `--summary` is allowed here too (scanned for fabrication
90
+ markers like a task's); it is a row suffix only when the record also
91
+ names a `--task`. A record carrying both `--task` and `--scenario-id`
92
+ is one attestation answering both joins: it counts for the task cell
93
+ and for the scenario cell (verdicts, the shipped gate, the review gate).
77
94
  - **Mismatched** — the folded divergence state is `open`,
78
95
  `still-divergent`, or `resolved-invalid` (a resolution whose app sha
79
96
  has moved), or the latest ledger verdict is `divergent`.
@@ -95,7 +112,9 @@ spec_coverage:
95
112
  in `require_review.scope` (default `[verifying, shipped]`; `[]` = every
96
113
  scenario; independent of `scope` above) need a `Review` record
97
114
  (`canon review add`), by a reviewer and actor other than the
98
- scenario's evidence actor when `distinct_actor` (default `true`);
115
+ scenario's evidence actor, and from a session other than any evidence
116
+ session when both carry one (`--session-id`), when `distinct_actor`
117
+ (default `true`);
99
118
  gaps are `unreviewed-promotion`. With `block_on_findings` (default
100
119
  `true`), an open `blocker` Finding on a change an in-scope subject
101
120
  adopted is `open-blocker`. `exclude_lanes` also exempts scenarios
@@ -159,12 +178,20 @@ canon evidence add ... --report junit:target/nextest/default/junit.xml
159
178
  canon evidence add ... --report cucumber:reports/cucumber.json
160
179
  ```
161
180
 
162
- The case is found by the scenario id in its name, classname, or Cucumber
163
- tag — dotted (`cart.add.04`) or underscored (`cart_add_04`, the form a
164
- test function carries) — or named exactly with `--report-case <name>`
165
- (a Rust test by its function name). No match refuses (exit 2); a
166
- `faithful` verdict over a failed or skipped case refuses (exit 1). Files
167
- must be inside the repository and are recorded by relative path.
181
+ A case is bound when the scenario id is its OWN: the first
182
+ scenario-id-shaped token (`<area>.<surface>.<nn>`) in its full name —
183
+ classname (a describe path) first, then name — dotted (`cart.add.04`) or
184
+ underscored (`cart_add_04`, the form a test function carries), equals
185
+ the scenario id; a Cucumber tag equal to it binds too. Start each test
186
+ name with its scenario id: `game.session.04: restarting as in
187
+ game.session.01` binds to `game.session.04` only, never to the id it
188
+ mentions. Each bound case is its own attachment with its name and
189
+ outcome. `--report-case <name>` (repeatable; a Rust test by its
190
+ function name) ADDS cases; it never hides the scenario's own. No match,
191
+ or a `--report-case` naming no case, refuses (exit 2); a `faithful`
192
+ verdict when ANY bound case failed or was skipped refuses (exit 1). Files
193
+ must be inside the repository, are recorded by relative path, and are
194
+ stored (see Bound evidence stays provable).
168
195
 
169
196
  Strength, weakest first: `attested` (no attachment) < `artifact` (any
170
197
  bound file) < `report` (a parsed report whose case passed). The policy
@@ -189,6 +216,49 @@ violation in every mode. Canon still cannot know whether the bound test
189
216
  actually exercises the scenario — binding narrows the trust gap, it does
190
217
  not close it.
191
218
 
219
+ ## Bound evidence stays provable (the artifact store)
220
+
221
+ `canon evidence add --artifact/--report` copies each bound file into
222
+ `.canon/artifacts/sha256/<hex>`, named by its sha256. Writes go through a
223
+ temp file and a rename, and a digest already stored intact is not written
224
+ again. The directory is tracked in git (`canon init` never ignores it):
225
+ commit it with the ledger. A file larger than 25 MiB is refused (exit 2);
226
+ raise the limit with `--max-artifact-mib <N>`.
227
+
228
+ `canon gate check` re-checks every file bound to a LATEST evidence record
229
+ (the latest per task and per `(project_id, scenario_id)`):
230
+
231
+ - the stored blob exists and hashes to the recorded digest → clean, even
232
+ after the working-tree file is rewritten (a smoke script that rewrites
233
+ `reports/smoke.json` on every run no longer orphans the record);
234
+ - no blob, but the working-tree path still hashes to the recorded digest
235
+ → clean, with an advisory naming `canon evidence vault` (records from
236
+ before the store existed):
237
+
238
+ ```
239
+ evidence artifacts: 1 bound file(s) not in the artifact store — not failing the gate; run `canon evidence vault` to store them before the working tree changes:
240
+ unstored cart.add.01 reports/junit.xml (sha256 b7cb…) is verified against the working tree only
241
+ ```
242
+ - anything else → `stale-evidence` naming the path, the recorded digest,
243
+ and what the store and the working tree hold now (`missing` or a
244
+ digest), ending with why: a stored blob that no longer hashes to its
245
+ name "does not match its name, so its bytes were altered"; a missing
246
+ one means "the bound bytes are gone". Re-run the test and re-attest
247
+ with `canon evidence add`.
248
+
249
+ The check applies to every latest record, not only subjects in
250
+ `spec_coverage.scope`: a binding is a claim about bytes whatever the
251
+ subject's status, and a repo with no `spec_coverage` would otherwise never
252
+ have its bindings checked. A superseded record is not checked —
253
+ re-attesting is the fix.
254
+
255
+ `canon evidence vault [--max-artifact-mib <N>]` stores the bytes for
256
+ existing records: every attachment whose working-tree file still matches
257
+ its digest is copied in, and everything else is listed as not stored,
258
+ with both digests. It exits `0` whenever it ran (the gate decides whether
259
+ an unstored file matters), `2` when the ledger cannot be read or a write
260
+ fails.
261
+
192
262
  ## Effect-aware risk approvals (`risk_tiers`)
193
263
 
194
264
  `risk_tiers` is absent by default and remains a no-op when absent. When it
@@ -229,10 +299,18 @@ the compat default `[{ dialect: openspec, root: <repo> }]`; no source
229
299
  locating the task at all is a loud failure naming every source consulted.
230
300
  It requires
231
301
  a matching non-`Divergent` evidence record, and flips `- [ ]` → `- [x]`
232
- with an appended evidence note ONLY on a clean check. Every other path —
233
- missing evidence, a `Divergent` verdict, a fabricated note — leaves the
234
- row byte-unchanged and exits `1` with the blocking violation on stderr.
235
- An already-`[x]` row is an idempotent no-op (exit `0`). An unknown
302
+ with an appended evidence note ONLY on a clean check. The note
303
+ aggregates every record bound to the task, on one line:
304
+ `— ✅ 3 evidence records (2 faithful, 1 not-applicable); latest: <summary>`
305
+ (the latest `--summary`, or a default naming the latest passing record).
306
+ Every other path — missing evidence, a `Divergent` verdict, a fabricated
307
+ note on any of the task's records — leaves the row byte-unchanged and
308
+ exits `1` with the blocking violation on stderr. After a flip, the plan
309
+ source is re-ingested so the record store's task status agrees with the
310
+ checkbox (`canon query --kind task` reads `done`); a failed re-ingest is
311
+ a stderr warning naming `canon ingest plans`, never a failed flip.
312
+ The success line is `canon gate task: <task_id> flipped — written
313
+ directly; nothing to promote`. An already-`[x]` row is an idempotent no-op (exit `0`). An unknown
236
314
  `task_id` is reported (exit `1`).
237
315
 
238
316
  ```bash
@@ -258,6 +336,15 @@ canon gate promote --repo . # land every staged candidate
258
336
  canon gate promote --repo . --dry-run # preview only
259
337
  ```
260
338
 
339
+ Which writes need this step: `evidence add`, `finding add` and `finding
340
+ close` stage, and their success line ends ``— run `canon gate promote`
341
+ to commit it``. `divergence stage` stages for `canon divergence promote`
342
+ and names it. Every other write — `review add`, `divergence
343
+ resolve|defer`, `subject new|adopt|status`, `change new`, `scenario new`, `feature new`,
344
+ `gate task` (it edits the plan document and re-ingests it)
345
+ — is final when it returns, and its success line ends `— written
346
+ directly; nothing to promote`.
347
+
261
348
  ## `canon gate install-hooks [--repo] [--event] [--matcher] [--command] [--timeout]`
262
349
 
263
350
  Idempotent, diff-only hook-seam installation: merges one
@@ -17,6 +17,8 @@ plans:
17
17
  root: . # resolved relative to canon.yaml's directory
18
18
  ```
19
19
 
20
+ - `canon init` writes exactly this source, plus an empty
21
+ `openspec/changes/` (so the adapter never scans the repo root itself).
20
22
  - An absent `plans:` section = zero sources (a clean no-op).
21
23
  - A present section parses STRICTLY: a typo'd key, an unregistered
22
24
  `dialect`, or a nonexistent `root` fails the command loud, naming the
@@ -135,8 +137,8 @@ downstream reader's job.
135
137
  ## What this skill does NOT cover
136
138
 
137
139
  - **Authoring an openspec change dir** (`proposal.md`/`tasks.md`
138
- conventions) — see the openspec CLI's own docs; this skill covers
139
- IMPORTING that shape, not producing it.
140
+ conventions) — `canon change new` scaffolds one and adopts it into a
141
+ subject (see `canon-subject`); this skill covers IMPORTING that shape.
140
142
  - **`canon gate check`/`canon gate task`** (the hand-authored one-task-at-
141
143
  a-time path this complements at bulk) — see `canon-gate`.
142
144
  - **Wiring an imported `Task`/`Change` into a `canon gate` decision** —
@@ -23,6 +23,25 @@ uncovered-cell spec_coverag — `spec_coverag` is not a known top-level policy.y
23
23
  The known keys still resolve normally, and `canon context` lists the
24
24
  diagnostic and reports `clean: false`.
25
25
 
26
+ ## The starter policy `canon init` writes
27
+
28
+ A fresh `canon init` writes `.canon/policy.yaml` (skip it with
29
+ `--no-policy`; an existing file is never overwritten):
30
+
31
+ ```yaml
32
+ spec_coverage:
33
+ require_evidence: true # every scenario needs evidence
34
+ require_cases: [failure] # every feature surface needs a @case:failure scenario
35
+ require_review: {} # verifying/shipped subjects need an independent review
36
+ ```
37
+
38
+ The file carries comments explaining each field. It loads with zero
39
+ diagnostics. It is what `canon gate check` grades the work against, so a
40
+ human should review and approve it, and every later change to it. The
41
+ agent doing the work should not write its own grading policy. Narrow it
42
+ with `scope: [building, verifying]` to check evidence only for subjects
43
+ in those statuses (see `canon-gate`).
44
+
26
45
  ## The closed CEL profile
27
46
 
28
47
  Every expression is evaluated against exactly one variable, `record` (the
@@ -17,15 +17,15 @@ state they are in.
17
17
  The order an agent follows to claim a subject done:
18
18
 
19
19
  1. **Test-run evidence** per owned scenario (`canon evidence add
20
- --scenario-id <id> --project-id <root-id> ...`, then `canon gate
21
- promote`). See `canon-gate`.
20
+ --scenario-id <id> --project-id <root-id> ... --session-id <session>`,
21
+ then `canon gate promote`). See `canon-gate`.
22
22
  2. **Independent review** of the change(s) the subject adopted. Record
23
23
  each issue the reviewer raises as a Finding, fix it, then close it:
24
24
 
25
25
  ```bash
26
26
  canon finding add --change-id c-demo --round 1 --seq 1 \
27
27
  --severity blocker --disposition open --reviewer reviewer-2 \
28
- --summary "unbounded subprocess wait"
28
+ --summary "unbounded subprocess wait" --session-id review-session-1
29
29
  canon gate promote
30
30
  # ... fix, commit ...
31
31
  canon finding close --change-id c-demo --round 1 --seq 1 \
@@ -33,15 +33,18 @@ The order an agent follows to claim a subject done:
33
33
  canon gate promote
34
34
  ```
35
35
 
36
- 3. **One Review per scenario**, by someone other than the evidence actor:
36
+ 3. **One Review per scenario**, by someone other than the evidence actor,
37
+ from a session other than the one that attested the evidence:
37
38
 
38
39
  ```bash
39
40
  canon review add --project-id root --scenario-id world.demo.01 \
40
41
  --reviewer reviewer-2 --actor-id reviewer-2 --role reviewer \
42
+ --session-id review-session-1 \
41
43
  --pin "$(git rev-parse HEAD)" --upstream-ref "pr#41"
42
- canon gate promote
43
44
  ```
44
45
 
46
+ `review add` writes directly; there is nothing to promote.
47
+
45
48
  4. **Move the subject**: `canon subject status <id> verifying`, later
46
49
  `shipped`. See `canon-subject`.
47
50
 
@@ -57,15 +60,27 @@ work collapsed into one identity.
57
60
  canon finding add --change-id <c> --round <n> --seq <n> \
58
61
  --severity blocker|should-fix|note --disposition open \
59
62
  --reviewer <who> --summary "<one line>" \
60
- [--reviewed-sha <sha>] [--file-ref path/to/file.rs:120-134] [--actor-id <id>]
63
+ [--reviewed-sha <sha>] [--file-ref path/to/file.rs:120-134] [--actor-id <id>] [--session-id <id>]
61
64
  canon finding close --change-id <c> --round <n> --seq <n> \
62
- --disposition fixed --resolution-sha <sha> # or rejected / deferred / open
65
+ --disposition fixed --resolution-sha <sha> [--session-id <id>] # or rejected / deferred / open
63
66
  ```
64
67
 
65
- Both stage a candidate; `canon gate promote` commits it. `finding close`
68
+ Both stage a candidate; `canon gate promote` commits it, and both say so
69
+ (``… — run `canon gate promote` to commit it``). `finding close`
66
70
  changes only the disposition (and `resolution_sha` with it); severity,
67
71
  reviewer, summary, and refs are copied from the committed record. It
68
- appends a second version at the same natural key — the first stays.
72
+ appends a second version at the same natural key — the first stays. Its
73
+ `--actor-id` and `--session-id` name who authored the transition; the
74
+ raiser's stay on the first version.
75
+
76
+ ### A defect the author found in its own work
77
+
78
+ Record it like any other finding, with `--reviewer` and `--actor-id` set
79
+ to the author's own id. Nothing refuses a finding whose reviewer is also
80
+ the implementer, and an open `blocker` the author raised blocks under
81
+ `block_on_findings` exactly like a reviewer's. `--introduced-by` names the
82
+ commit that introduced the defect, not who found it: `--introduced-by self`
83
+ is a usage error (exit 2) that points at `--reviewer`.
69
84
 
70
85
  ### Severities
71
86
 
@@ -98,15 +113,23 @@ fixed finding reopened with `--disposition open` counts as open again.
98
113
  ```bash
99
114
  canon review add --project-id <root-id> --scenario-id <id> \
100
115
  --reviewer <who> --pin <sha-or-ref> --role <role> \
101
- (--upstream-ref <ref> | --original-spec-ref <ref>) [--actor-id <id>]
116
+ (--upstream-ref <ref> | --original-spec-ref <ref>) [--actor-id <id>] [--session-id <id>]
102
117
  ```
103
118
 
104
119
  One attributed Review record for one scenario, joined on
105
120
  `(project_id, scenario_id)` — both are required because two spec roots
106
121
  may carry the same scenario id. Exactly one provenance ref is required.
107
- `--actor-id` defaults to `canon`. A Review carries no verdict; the
108
- `verifying → shipped` ship gate still refuses missing or `Divergent`
109
- evidence verdicts on its own. Commit with `canon gate promote`.
122
+ `--actor-id` defaults to `canon`; `--session-id` fills `actor.session_id`.
123
+ A Review carries no verdict; the `verifying → shipped` ship gate still
124
+ refuses missing or `Divergent` evidence verdicts on its own. It is
125
+ written directly to the ledger, with no staging step, and says so:
126
+ `canon review add: wrote <path> (new) — written directly; nothing to
127
+ promote`.
128
+
129
+ `--session-id` (on `evidence add`, `review add`, `finding add` and
130
+ `finding close`) takes one opaque token: non-empty, no surrounding
131
+ whitespace, no control characters. Anything else is a usage error
132
+ (exit 2) and nothing is written.
110
133
 
111
134
  ## The `require_review` policy (opt-in)
112
135
 
@@ -129,8 +152,11 @@ spec_coverage:
129
152
  scenarios from review.
130
153
  - **`distinct_actor`** — a review only counts when both its `reviewer`
131
154
  and its actor (`--actor-id`) differ from every actor on that
132
- scenario's evidence records. An agent cannot review its own attested
133
- work.
155
+ scenario's evidence records, and, when the review carries a session,
156
+ that session differs from every session on those evidence records. An
157
+ agent cannot review its own attested work, under another id or from
158
+ the same session. A record with no session never matches one, so
159
+ records written before sessions were recorded count by actor alone.
134
160
  - **`block_on_findings`** — fail on open blocker findings (below).
135
161
 
136
162
  A malformed `require_review` (an unknown key, an unknown status, `null`,
@@ -138,7 +164,8 @@ a non-mapping) poisons the whole `spec_coverage` section: `canon gate
138
164
  check` reports it invalid rather than treating it as off, the same as a
139
165
  malformed `require_cases`.
140
166
 
141
- Canon's own `.canon/policy.yaml` does not enable `require_review`.
167
+ Canon's own `.canon/policy.yaml` enables `require_review: {}`, and so does
168
+ the starter policy `canon init` writes.
142
169
 
143
170
  ## How it meets the gate
144
171
 
@@ -156,6 +183,13 @@ or, when the only reviews are self-reviews:
156
183
  unreviewed-promotion world.demo.01 — every review of this scenario is by an actor that also attested its evidence (`impl-agent`); spec_coverage.require_review.distinct_actor wants a reviewer other than the evidence actor
157
184
  ```
158
185
 
186
+ or, when another actor id reviewed from a session that attested the
187
+ evidence:
188
+
189
+ ```
190
+ unreviewed-promotion world.demo.01 — every review of this scenario is by an actor that also attested its evidence (`impl-agent`) or from a session that did (`sess-impl`); spec_coverage.require_review.distinct_actor wants a reviewer other than the evidence actor, in a session other than any evidence session
191
+ ```
192
+
159
193
  ### `open-blocker`
160
194
 
161
195
  With `block_on_findings`, a Finding whose latest version is severity
@@ -188,7 +222,7 @@ prints on stderr which review checks it ran or skipped:
188
222
 
189
223
  ```
190
224
  canon subject status: review guard for building → verifying (spec_coverage.require_review, scope: verifying, shipped)
191
- ran unreviewed-promotion — every owned scenario needs a review by an actor other than its evidence actor
225
+ ran unreviewed-promotion — every owned scenario needs a review by an actor, and from a session, other than its evidence's
192
226
  ran open-blocker — 1 adopted change(s) checked for open blocker findings
193
227
  ```
194
228
 
@@ -135,7 +135,8 @@ what brings a scenario into that scope. See `canon-gate`.
135
135
  requires that the Subject OWNS at least one scenario (one whose latest
136
136
  synced generation carries `@subject:<id>`), and that EVERY owned
137
137
  scenario carries a latest, non-`Divergent` verdict in the ledger (the
138
- same last-wins rule `canon gate check` uses). With
138
+ same last-wins rule `canon gate check` uses; a record keyed by both a
139
+ task and the scenario counts for the scenario). With
139
140
  `spec_coverage.require_cases` set, every feature surface the Subject
140
141
  owns must also carry a scenario of each required `@case:` (e.g. one
141
142
  `@case:failure`) — attested golden-path scenarios alone do not ship. A
@@ -152,7 +153,7 @@ prints on stderr which it ran or skipped:
152
153
 
153
154
  ```
154
155
  canon subject status: review guard for building → verifying (spec_coverage.require_review, scope: verifying, shipped)
155
- ran unreviewed-promotion — every owned scenario needs a review by an actor other than its evidence actor
156
+ ran unreviewed-promotion — every owned scenario needs a review by an actor, and from a session, other than its evidence's
156
157
  ran open-blocker — 1 adopted change(s) checked for open blocker findings
157
158
  ```
158
159
 
@@ -204,7 +205,143 @@ canon subject adopt subject-domain-loop-plan \
204
205
 
205
206
  `adopt` stamps the change's `subject_id` and adds it to the Subject's
206
207
  `change_ids`, so `canon query --kind change --change-id …` and `canon
207
- query --kind subject` agree on the link from both ends.
208
+ query --kind subject` agree on the link from both ends. It writes only
209
+ the side that lacks the link, so rerunning it on a linked pair writes
210
+ nothing and exits `0`.
211
+
212
+ The two records cannot be written atomically, so the subject is written
213
+ first: its `change_ids` is what the gate reads a subject's adopted
214
+ changes from (`open-blocker`, the status guard), so a half-done adoption
215
+ still has that change's open blockers counted. If the change write then
216
+ fails, `adopt` exits `2`, names the subject that now lists the change,
217
+ and prints `canon subject adopt <change_id> --subject <id>` to complete
218
+ it.
219
+
220
+ ### Starting a new change: `canon change new`
221
+
222
+ For a change that does not exist yet, one command scaffolds it and
223
+ records it adopted:
224
+
225
+ ```bash
226
+ canon change new add-login --subject auth --title "Add login"
227
+ ```
228
+
229
+ - **Writes** `openspec/changes/<slug>/proposal.md` (the title is its
230
+ `## Why`, which imports as the change summary) and `tasks.md` (no rows
231
+ yet, so the change imports as `proposed`), under the repo's first
232
+ `openspec` plans source. `canon init` configures that source
233
+ (`{dialect: openspec, root: .}`), so `canon.yaml` needs no edit.
234
+ - **Reads** the files with the same `openspec` adapter `canon ingest
235
+ plans` uses, then **records** the change already adopted, through the
236
+ same write as `canon subject adopt`.
237
+ - **What a failure leaves behind.** The files are staged under `.canon/`
238
+ and the change dir is moved into place only once they parse. A failure
239
+ before any record is written (staging, publishing the dir, or the
240
+ first record write) removes the change dir, the staged files and any
241
+ directory created for them: nothing is left behind. The two records
242
+ cannot be written atomically. If the change record fails after the
243
+ subject record was written, the command exits `2`, keeps the change
244
+ dir and that one subject record, and prints the repair, `canon ingest
245
+ plans && canon subject adopt <slug> --subject <id>`, which completes
246
+ the link and is safe to rerun.
247
+ - **Refuses** with exit `2`, writing nothing, when the subject does not
248
+ exist, when the slug already has a change dir (active or archived) or a
249
+ `change` record, or when `canon.yaml` has no `openspec` plans source.
250
+
251
+ Add task rows to `tasks.md` as `- [ ] <n> <title>`, rerun `canon ingest
252
+ plans`, and flip each with `canon gate task <slug>#<n>` once evidence
253
+ exists.
254
+
255
+ ## Where are we: `canon status`
256
+
257
+ ```bash
258
+ canon status # subjects by status, their gaps, next commands
259
+ canon status --json # the same report, stable shape
260
+ canon status --repo ../other
261
+ ```
262
+
263
+ A read: it always exits 0 and writes nothing (no ledger record, no
264
+ `.canon/audit` line). Every count comes from the joins the gate itself
265
+ uses, so status and the gate cannot disagree about a gap:
266
+
267
+ - **Header** — the canon version, and the policy in force:
268
+ `spec_coverage` (with `scope`/`exclude_lanes`), `require_cases` and
269
+ `require_review`. When `.canon/policy.yaml` is absent, unreadable, has
270
+ no `spec_coverage` section, or has an invalid one, a `warning:` line
271
+ says the gate requires no evidence, failure cases or review (or refuses
272
+ until it is fixed).
273
+ - **Subjects grouped by status** (lifecycle order). Per subject:
274
+ scenarios it owns through `@subject:` (after `exclude_lanes`, which
275
+ are counted separately), how many carry a ledger verdict (the
276
+ `verifying → shipped` gate's reading; divergent ones are counted too),
277
+ how many carry a review that satisfies `require_review` (any review
278
+ when it is off; with `distinct_actor`, a self-review does not count),
279
+ open blocker findings on its adopted changes, and each feature surface
280
+ missing a `require_cases` case. `(N due)` marks reviews the next
281
+ transition will require.
282
+ - **Unowned** — scenarios with no `@subject:` tag.
283
+ - **`next:`** — at most five commands, each with a one-line reason; the
284
+ rest are counted. Rules, in order: no `canon.yaml` → `canon init`; an
285
+ unreadable one → `canon init --check-config`; a scenario tagged with a
286
+ subject that has no record → `canon subject new <that id>`; no subject
287
+ at all → `canon subject new`; subjects but no scenario records →
288
+ `canon inventory sync`. Then per subject, verifying first, then
289
+ building, specced, proposed, shipped: no scenario → `canon scenario new
290
+ … --subject <id>`; a missing case → `canon scenario new <surface>.<next
291
+ nn> … --case <case>`; (building and later) an unevidenced or divergent
292
+ scenario → `canon evidence add --scenario-id …`; an open blocker →
293
+ `canon finding close …`; a due, unreviewed, evidenced scenario →
294
+ `canon review add …` from another session; none of these →
295
+ `canon subject status <id> <next status>`. Last, unowned scenarios →
296
+ tag them and `canon inventory sync`. With more than one `specs.roots[]`
297
+ entry, every suggested `canon scenario new` carries `--project`: the
298
+ owning root for a missing case, `<root-id>` (the reason lists the
299
+ roots) for a subject that owns no scenario yet.
300
+
301
+ Status fails closed rather than suggest progress the gate would refuse:
302
+
303
+ - When `scenario`, `subject`, `review` or `finding` routes away from the
304
+ `local` rung (the only rung status and the gate read), its records
305
+ would read as empty. The only `next:` step is
306
+ `canon init --check-config`, with the reason naming the
307
+ `routing.<kind>: local` setting to restore.
308
+ - When `spec_coverage` is present but unusable, `canon gate check`
309
+ refuses. The first `next:` step is `canon gate check`, with the reason
310
+ naming the section to fix, and no subject is suggested to move to its
311
+ next status until it parses.
312
+
313
+ `--json` prints one object:
314
+
315
+ ```json
316
+ {
317
+ "statusVersion": 1,
318
+ "canonVersion": "0.14.0",
319
+ "policy": { "present": true, "spec_coverage": { "require_evidence": true, "scope": [], "exclude_lanes": [], "require_cases": ["failure"], "require_review": { "scope": ["verifying", "shipped"], "distinct_actor": true, "block_on_findings": true } } },
320
+ "warnings": [],
321
+ "subjects": [
322
+ { "id": "cart", "title": "Cart", "status": "building", "scenarios": 2, "excluded": 0, "evidenced": 2, "divergent": 0,
323
+ "reviewed": 1, "reviewDue": true, "openBlockers": 1, "missingCases": [] }
324
+ ],
325
+ "unowned": ["cart.promo.01"],
326
+ "next": [
327
+ { "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" },
328
+ { "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; …" },
329
+ { "command": "canon inventory sync", "why": "1 scenario(s) carry no @subject tag (first: cart.promo.01); …" }
330
+ ],
331
+ "nextOmitted": 0
332
+ }
333
+ ```
334
+
335
+ Each `missingCases` entry is `{ "projectId", "surface", "case" }`: the
336
+ spec root, the `<area>.<surface>`, and the required case it lacks.
337
+
338
+ `policy.spec_coverage` has the shape `canon context --json` prints
339
+ (`null` when absent, `{"invalid": …}` when unusable). `statusVersion`
340
+ changes only when a field changes meaning or is removed.
341
+
342
+ `subject new`, `adopt` and `status` write their records directly; the
343
+ human-readable success line ends `— written directly; nothing to
344
+ promote` (`--json` prints the record and nothing else).
208
345
 
209
346
  ## Reading the per-domain management view
210
347
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@journeykit/canon",
3
- "version": "0.13.0",
3
+ "version": "0.14.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.13.0",
34
- "@journeykit/canon-core-linux-x64": "0.13.0",
35
- "@journeykit/canon-core-win32-x64": "0.13.0"
33
+ "@journeykit/canon-core-darwin-arm64": "0.14.0",
34
+ "@journeykit/canon-core-linux-x64": "0.14.0",
35
+ "@journeykit/canon-core-win32-x64": "0.14.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^20.0.0",