@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.
- package/dist/skills/SKILL.src.md +105 -150
- package/dist/skills/reference/brief-align.md +42 -0
- package/dist/skills/reference/brief-clarify.md +42 -0
- package/dist/skills/reference/brief-harden.md +41 -0
- package/dist/skills/reference/brief-polish.md +42 -0
- package/dist/skills/reference/brief-shape.md +46 -0
- package/dist/skills/reference/canon-authoring.md +1 -1
- package/dist/skills/reference/canon-context.md +3 -3
- package/dist/skills/reference/canon-gate.md +133 -44
- package/dist/skills/reference/canon-inventory.md +2 -2
- package/dist/skills/reference/canon-learn.md +4 -6
- package/dist/skills/reference/canon-plan-import.md +10 -7
- package/dist/skills/reference/canon-policy.md +5 -3
- package/dist/skills/reference/canon-review.md +31 -12
- package/dist/skills/reference/canon-storage.md +22 -7
- package/dist/skills/reference/canon-subject.md +199 -62
- package/dist/skills/reference/role-implementer.md +80 -0
- package/dist/skills/reference/role-orchestrator.md +76 -0
- package/dist/skills/reference/role-planner.md +84 -0
- package/dist/skills/reference/role-reviewer.md +70 -0
- package/package.json +4 -4
|
@@ -28,7 +28,7 @@ Every violation carries one of these stable, grep-able strings:
|
|
|
28
28
|
| `malformed-evidence` | A candidate record doesn't parse, is misfiled, or carries an unparseable interim tag. Malformed evidence is no evidence. |
|
|
29
29
|
| `flagged` | The human-only `flagged` overlay is set — never green regardless of passing evidence. |
|
|
30
30
|
| `unevidenced-flip` | `canon gate task <task_id>` was asked to flip a checkbox with no matching, non-`Divergent` evidence record. |
|
|
31
|
-
| `fabricated-evidence` | An evidence note contains a blocklisted marker (`"would pass"`, `"TBD"`, `"n/a"`) or a bare `verified` claim with no attached command result. |
|
|
31
|
+
| `fabricated-evidence` | An evidence note contains a blocklisted marker (`"would pass"`, `"TBD"`, `"n/a"`) standing as a whole word — never inside a path, a file name, a URL or a longer word, so `pack.json/atlas` is not `n/a` — or a bare `verified` claim with no attached command result. `canon evidence add` refuses such a note before writing (`canon evidence add: refused — fabricated-evidence …`, exit 1). |
|
|
32
32
|
| `open-blocker` | With `spec_coverage.require_review` set (and `block_on_findings`, the default), a Finding whose latest version is severity `blocker`, disposition `open`, on a change an in-scope subject adopted. Subject string `<change_id>#<round>.<seq>`. See `canon-review`. |
|
|
33
33
|
| `false-success` | The latest audit record (`canon evidence add --kind audit`) of a scenario says `refuted`: an external evaluator found a counterexample to the claimed success. Always on, regardless of policy or sampling, and takes precedence over any declared faithful verdict; only a newer audit (`confirmed` or `indeterminate`) clears it. The detail names the evaluator, its version, its sha256 and the counterexample paths. |
|
|
34
34
|
|
|
@@ -142,7 +142,7 @@ A present section, even `require_evidence: false` or a malformed one
|
|
|
142
142
|
case). This is the gap evidence presence cannot see: every scenario
|
|
143
143
|
attested, none of them specifying a refusal. Untagged scenarios satisfy
|
|
144
144
|
no case, so an unclassified corpus is reported, not passed. Works with
|
|
145
|
-
`require_evidence: false` too. `canon subject status <id> shipped`
|
|
145
|
+
`require_evidence: false` too. `canon subject status <id> --to shipped`
|
|
146
146
|
applies the same rule to the scenarios the subject owns.
|
|
147
147
|
- **Independent review** (`require_review`) — scenarios whose subject is
|
|
148
148
|
in `require_review.scope` (default `[verifying, shipped]`; `[]` = every
|
|
@@ -228,25 +228,38 @@ canon evidence add --kind audit --project-id root --scenario-id cart.add.01 \
|
|
|
228
228
|
- Audits fold apart: per `(project_id, scenario_id)` there is a latest
|
|
229
229
|
declared record and a latest audit. An audit never becomes the
|
|
230
230
|
scenario's latest declared verdict.
|
|
231
|
-
- `canon subject status <id> shipped` refuses `false-success`, and an
|
|
231
|
+
- `canon subject status <id> --to shipped` refuses `false-success`, and an
|
|
232
232
|
owed sampled audit when `require_audit.scope` covers `shipped`.
|
|
233
233
|
- `canon status` shows an `audits` section (sampled / confirmed / owed /
|
|
234
234
|
refuted) and suggests `canon evidence add --kind audit …` in `next:`.
|
|
235
235
|
|
|
236
|
-
###
|
|
237
|
-
|
|
238
|
-
`canon evidence add` for a scenario
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
236
|
+
### Carrying and dropping bindings (`--drop-attachment`)
|
|
237
|
+
|
|
238
|
+
`canon evidence add` for a scenario carries forward what the scenario's
|
|
239
|
+
current latest record of the same fold (declared records, or audit
|
|
240
|
+
records for an audit; committed ledger plus `_staging`) bound: every
|
|
241
|
+
file the new write does not name again is bound into the new record
|
|
242
|
+
unchanged (same path, digest and stored blob), and `--json` lists it as
|
|
243
|
+
`carried: [paths]`. A file named again (`--artifact`/`--report`, new
|
|
244
|
+
bytes at the same path included) replaces its earlier binding. A suite
|
|
245
|
+
batch (`--all-scenarios`) therefore never costs a scenario its
|
|
246
|
+
screenshots. A carried file whose blob the artifact store does not
|
|
247
|
+
hold intact is refused (exit `2`, `EVIDENCE_CARRY_BLOB_MISSING`, nothing
|
|
248
|
+
written): re-bind it, run `canon evidence vault`, or drop it. The audit
|
|
249
|
+
fold (`--kind audit`) does not carry: an audit that leaves out a file
|
|
250
|
+
the latest audit bound is refused (exit `2`) unless it names the file
|
|
251
|
+
again or drops it with `--drop-attachment`, so a counterexample never
|
|
252
|
+
rides along into a confirming audit.
|
|
253
|
+
|
|
254
|
+
The only way to lose a binding is `--drop-attachment <path>`
|
|
255
|
+
(repeatable) with one single-line `--drop-reason <text>`, recorded as
|
|
256
|
+
`attachment_drop: {paths, reason}`. With `--all-scenarios` the drop
|
|
257
|
+
applies to each scenario that binds the path. Refused (`2`): a drop
|
|
258
|
+
without a reason, a reason without a drop, a drop path that no
|
|
259
|
+
scenario of the write leaves out, a multi-line reason, or a drop
|
|
260
|
+
without `--scenario-id`/`--all-scenarios` (task-only evidence has no
|
|
261
|
+
carry or drop). `canon status` lists recorded drops (`dropped bindings
|
|
262
|
+
(N):`; `drops[]` in `--json`).
|
|
250
263
|
|
|
251
264
|
### Nothing is attested before the brief is approved (`--override-reason`)
|
|
252
265
|
|
|
@@ -256,7 +269,8 @@ reports it, and builds nothing for that scope until the human answers.
|
|
|
256
269
|
refuses (exit `1`, nothing staged or stored) while a subject the record
|
|
257
270
|
attests for has an enforced, unapproved brief: the newest adopted
|
|
258
271
|
change's canon `.canon/changes/<change>/brief.md` has no `Approved:`
|
|
259
|
-
line,
|
|
272
|
+
line, was edited after its approval (`BRIEF_CHANGED_SINCE_APPROVAL`), or
|
|
273
|
+
there is no canon `brief.md` yet but the change comes from an
|
|
260
274
|
external openspec plan with a `proposal.md` under the plan sources (the
|
|
261
275
|
check `canon status` and the ship guard use; see
|
|
262
276
|
`canon-subject`). The subjects are the scenario's `@subject` owner for
|
|
@@ -264,8 +278,8 @@ check `canon status` and the ship guard use; see
|
|
|
264
278
|
`--task`:
|
|
265
279
|
|
|
266
280
|
```
|
|
267
|
-
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
|
|
268
|
-
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
|
|
281
|
+
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 --change c-demo --by <the human> --note "<their words>"`
|
|
282
|
+
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 --by <the human> --note "<their words>"` only from that answer; to attest anyway, pass --override-reason <text>, which is recorded on the evidence record
|
|
269
283
|
```
|
|
270
284
|
|
|
271
285
|
For a change from an external plan with no canon brief yet, the first
|
|
@@ -274,8 +288,8 @@ change `c-ext` comes from the external plan
|
|
|
274
288
|
openspec/changes/c-ext/proposal.md, which canon reads and never edits, so
|
|
275
289
|
its approval lives in that canon file, so the human has not answered it;
|
|
276
290
|
once they do, create .canon/changes/c-ext/brief.md holding a `Source:
|
|
277
|
-
openspec/changes/c-ext/proposal.md` line
|
|
278
|
-
`
|
|
291
|
+
openspec/changes/c-ext/proposal.md` line, then record their words with
|
|
292
|
+
`canon brief approve --change c-ext --by <the human> --note "<their words>"` ``. An `Approved:` line in the
|
|
279
293
|
external `proposal.md` itself is never read.
|
|
280
294
|
|
|
281
295
|
A subject with no brief to answer (no adopted change, or a newest change
|
|
@@ -305,7 +319,7 @@ brief overrides: 1 evidence record(s) attested before their brief was approved
|
|
|
305
319
|
|
|
306
320
|
### Review-waiver advisories
|
|
307
321
|
|
|
308
|
-
A subject moved into a review-scoped status with `canon subject status
|
|
322
|
+
A subject moved into a review-scoped status with `canon subject status <id> --to <state>
|
|
309
323
|
--override-reason` (see `canon-subject`) keeps the `unreviewed-promotion`
|
|
310
324
|
and `open-blocker` gaps its waiver recorded visible without failing the
|
|
311
325
|
gate. Only those exact `(class, subject)` pairs are waived; a gap that
|
|
@@ -319,15 +333,18 @@ review waivers: 1 advisory(ies) — not failing the gate:
|
|
|
319
333
|
|
|
320
334
|
The waiver lasts only until the subject's next status write.
|
|
321
335
|
|
|
322
|
-
`canon subject status <id> shipped` also refuses where a brief exists and
|
|
323
|
-
|
|
324
|
-
`brief.md
|
|
336
|
+
`canon subject status <id> --to shipped` also refuses where a brief exists and
|
|
337
|
+
is not approved (the subject's newest adopted change has a canon
|
|
338
|
+
`brief.md` with no `Approved:` line or one the brief's text no longer
|
|
339
|
+
matches, or an external `proposal.md` and no canon `brief.md` yet;
|
|
325
340
|
see `canon-subject`), as an `unreviewed-promotion` naming
|
|
326
|
-
the subject
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
not
|
|
341
|
+
the subject, and while `canon brief check` has structure blockers or a
|
|
342
|
+
flow step has no scenario or no failure scenario (the coverage rules gate
|
|
343
|
+
shipping, never approval). With no brief to answer it prints an advisory
|
|
344
|
+
and does not refuse. Like the evidence refusal above, it is a backstop:
|
|
345
|
+
the stop is at the brief. `canon gate check` does not grade briefs, so a
|
|
346
|
+
waived ship brief prints no advisory here; `canon status` keeps showing
|
|
347
|
+
the brief as not approved.
|
|
331
348
|
|
|
332
349
|
### Reviews that predate pinning (checked against their pin)
|
|
333
350
|
|
|
@@ -391,6 +408,8 @@ canon evidence add --scenario-id cart.add.04 --project-id root \
|
|
|
391
408
|
# a JUnit XML or Cucumber JSON report: also records the matched case + outcome
|
|
392
409
|
canon evidence add ... --report junit:target/nextest/default/junit.xml
|
|
393
410
|
canon evidence add ... --report cucumber:reports/cucumber.json
|
|
411
|
+
# a Swift Testing event stream (swift test --experimental-event-stream-output <path>)
|
|
412
|
+
canon evidence add ... --report swift-testing:.build/events.jsonl
|
|
394
413
|
```
|
|
395
414
|
|
|
396
415
|
A case is bound when the scenario id is its OWN: the first
|
|
@@ -408,6 +427,12 @@ verdict when ANY bound case failed or was skipped refuses (exit 1). Files
|
|
|
408
427
|
must be inside the repository, are recorded by relative path, and are
|
|
409
428
|
stored (see Bound evidence stays provable).
|
|
410
429
|
|
|
430
|
+
A Swift Testing case is named by its display name (`@Test("cart.add.04
|
|
431
|
+
refuses an out-of-stock item")`), else its function name
|
|
432
|
+
(`cart_add_04_refuses()`). A test with a failing issue failed, a skipped
|
|
433
|
+
test was skipped, a test that started and ended without one passed, and
|
|
434
|
+
a test that started and never ended failed.
|
|
435
|
+
|
|
411
436
|
Strength, weakest first: `attested` (no attachment) < `artifact` (any
|
|
412
437
|
bound file) < `report` (a parsed report whose case passed). The policy
|
|
413
438
|
decides whether the gate cares:
|
|
@@ -431,6 +456,56 @@ violation in every mode. Canon still cannot know whether the bound test
|
|
|
431
456
|
actually exercises the scenario — binding narrows the trust gap, it does
|
|
432
457
|
not close it.
|
|
433
458
|
|
|
459
|
+
### One report, many scenarios (0.18)
|
|
460
|
+
|
|
461
|
+
One test run proves many scenarios. Bind it once:
|
|
462
|
+
|
|
463
|
+
```bash
|
|
464
|
+
canon evidence add --project-id root --all-scenarios \
|
|
465
|
+
--report junit:target/nextest/default/junit.xml \
|
|
466
|
+
--kind test-run --ref "cargo nextest run" --role implementer
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
Each indexed scenario of `--project-id` that owns a case (the rule above)
|
|
470
|
+
gets its own record, with only its own cases. Its verdict comes from
|
|
471
|
+
them: `faithful` when every case passed, `divergent` when one failed,
|
|
472
|
+
`not-applicable` when one was skipped and none failed. `--verdict <v>`
|
|
473
|
+
claims the same verdict for every record instead. A case whose id is not
|
|
474
|
+
indexed is named and skipped (`skipped <id> — not an indexed scenario`);
|
|
475
|
+
a report no indexed scenario owns is refused (exit 2). The records share
|
|
476
|
+
the stored report and every `--artifact`, and the output lists each
|
|
477
|
+
scenario with its verdict on its own line.
|
|
478
|
+
|
|
479
|
+
It is all or nothing: a claimed `faithful` verdict over a failed case,
|
|
480
|
+
a fabrication marker in `--summary`, an unapproved brief for any
|
|
481
|
+
scenario's subject, or a carried attachment whose blob is missing from the
|
|
482
|
+
store refuses the whole batch, and nothing is written. Each scenario's
|
|
483
|
+
earlier attachments are carried into its new record (see above), and
|
|
484
|
+
`--drop-attachment` applies per scenario. `--scenario-id`, `--task`,
|
|
485
|
+
`--report-case`, `--kind audit` and the approval flags name one subject
|
|
486
|
+
and are refused with `--all-scenarios`.
|
|
487
|
+
|
|
488
|
+
A task's evidence with `--report` and no `--scenario-id` binds EVERY case
|
|
489
|
+
in the report, so a `faithful` verdict needs all of them to pass.
|
|
490
|
+
|
|
491
|
+
### A write that makes a review stale says so (0.18)
|
|
492
|
+
|
|
493
|
+
When new evidence (one record or a batch) supersedes the evidence a
|
|
494
|
+
current pinned review saw, `canon evidence add` prints one line per
|
|
495
|
+
review it makes stale, with the command that re-pins it:
|
|
496
|
+
|
|
497
|
+
```
|
|
498
|
+
canon evidence add: review by `reviewer-2` of cart.add.01 is now stale (it pinned the evidence this record supersedes); re-pin it: canon review add --project-id root --scenario-id cart.add.01 --reviewer reviewer-2 --actor-id reviewer-2 --role reviewer --pin <HEAD sha> --original-spec-ref specs/kind=feature/area=cart/add.feature
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
The decision is the gate's own (the same stale-review rule `canon gate
|
|
502
|
+
check` reports). The command repeats the review's reviewer, actor, role,
|
|
503
|
+
session, provenance ref, runtime labels and artifacts, pinned at `HEAD` (`<sha>`
|
|
504
|
+
when the repository has no commit). A record that is only staged says
|
|
505
|
+
the review "goes stale once this evidence is promoted": promote first,
|
|
506
|
+
then re-pin. The reviewer re-reviews before re-pinning; several
|
|
507
|
+
scenarios re-pin in one `canon review add` (see `canon-review`).
|
|
508
|
+
|
|
434
509
|
## Bound evidence stays provable (the artifact store)
|
|
435
510
|
|
|
436
511
|
`canon evidence add --artifact/--report` copies each bound file into
|
|
@@ -468,20 +543,29 @@ have its bindings checked. A superseded record is not checked —
|
|
|
468
543
|
re-attesting is the fix.
|
|
469
544
|
|
|
470
545
|
Files bound to a review (`canon review add --artifact`, see
|
|
471
|
-
`canon-review`) are checked the same
|
|
472
|
-
latest per `(project_id, scenario_id, reviewer)
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
546
|
+
`canon-review`) are checked by the same rule for every CURRENT in-effect
|
|
547
|
+
review: the latest per `(project_id, scenario_id, reviewer)`, less any
|
|
548
|
+
that went stale against its scenario (its spec or evidence pin moved, or
|
|
549
|
+
a review from before pinning is stale at its commit). A stale review is
|
|
550
|
+
superseded — the gate reads the newer review, or reports the missing
|
|
551
|
+
one as `unreviewed-promotion` — so its files are not re-checked, as a
|
|
552
|
+
superseded evidence record's are not. A missing blob whose working-tree
|
|
553
|
+
file still matches is an advisory, listed with the reviewer:
|
|
554
|
+
|
|
555
|
+
```
|
|
556
|
+
unstored cart.add.01 reports/cart-badge.png (sha256 9848…, review by `reviewer-2`) is verified against the working tree only
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
Anything else is `stale-evidence` on the review's scenario, and the
|
|
560
|
+
detail names the review: ``review by `<reviewer>` (actor `<id>`,
|
|
561
|
+
<time>)``.
|
|
478
562
|
|
|
479
563
|
`canon evidence vault [--max-artifact-mib <N>]` stores the bytes for
|
|
480
|
-
existing records: every attachment whose
|
|
481
|
-
its digest is copied in, and everything
|
|
482
|
-
with both digests. It exits `0` whenever it
|
|
483
|
-
an unstored file matters), `2` when the
|
|
484
|
-
fails.
|
|
564
|
+
existing evidence records and current reviews (a superseded review's files are not vaulted): every attachment whose
|
|
565
|
+
working-tree file still matches its digest is copied in, and everything
|
|
566
|
+
else is listed as not stored, with both digests. It exits `0` whenever it
|
|
567
|
+
ran (the gate decides whether an unstored file matters), `2` when the
|
|
568
|
+
ledger cannot be read or a write fails.
|
|
485
569
|
|
|
486
570
|
## Effect-aware risk approvals (`risk_tiers`)
|
|
487
571
|
|
|
@@ -514,6 +598,11 @@ empty clean map.
|
|
|
514
598
|
|
|
515
599
|
## `canon gate task <task_id> [--repo <dir>]`
|
|
516
600
|
|
|
601
|
+
For imported external plans, and for a `tasks.md` that already exists in
|
|
602
|
+
a canon change. `canon change new` writes no `tasks.md` (0.19), and
|
|
603
|
+
nothing gates on one: a canon-owned change is covered by its flow steps'
|
|
604
|
+
scenarios (`canon status`).
|
|
605
|
+
|
|
517
606
|
The evidence-gated task checkbox flip. Resolves `<task_id>`
|
|
518
607
|
(`<change_id>#<n>`) through `.canon/changes/` (canon's own changes, read
|
|
519
608
|
first whenever the directory exists, with no `plans:` entry) and then
|
|
@@ -595,7 +684,7 @@ promote`, so no separate
|
|
|
595
684
|
staged. A record another run staged is not touched: only `gate promote`
|
|
596
685
|
commits everything staged. It stays safe to run anyway: with nothing
|
|
597
686
|
staged it prints
|
|
598
|
-
`nothing staged` and exits `0`. `divergence
|
|
687
|
+
`nothing staged` and exits `0`. `divergence add` stages for `canon
|
|
599
688
|
divergence promote` and names it, under the same `write:` rule. Every
|
|
600
689
|
other write — `review add`, `divergence
|
|
601
690
|
resolve|defer`, `subject new|adopt|status`, `change new`, `scenario new`, `feature new`,
|
|
@@ -72,7 +72,7 @@ scenario index # refreshed by every reader; `canon inventory sy
|
|
|
72
72
|
│
|
|
73
73
|
▼
|
|
74
74
|
canon review add # attests a review — see canon-inventory downstream / canon-gate
|
|
75
|
-
canon divergence {
|
|
75
|
+
canon divergence {add,promote,resolve,defer,status} # tracks/resolves a divergence
|
|
76
76
|
canon gate check [--release] # trust-ladder / staleness / coverage — see canon-gate
|
|
77
77
|
│
|
|
78
78
|
▼
|
|
@@ -158,7 +158,7 @@ specs:
|
|
|
158
158
|
|
|
159
159
|
- **Review** — `canon review add` attests a verdict against a synced
|
|
160
160
|
scenario. (See `canon-inventory` downstream commands and each command's `--help`.)
|
|
161
|
-
- **Divergence** — `canon divergence {
|
|
161
|
+
- **Divergence** — `canon divergence {add,promote,resolve,defer,status}`
|
|
162
162
|
tracks/resolves divergences against synced scenarios.
|
|
163
163
|
- **Gate** — `canon gate check` enforces trust-ladder / staleness /
|
|
164
164
|
coverage over the corpus (see `canon-gate`).
|
|
@@ -46,15 +46,13 @@ The production activation path is a signed, paired-evaluation workflow:
|
|
|
46
46
|
```bash
|
|
47
47
|
canon learn approve <strategy_id> --evaluation evaluation.json \
|
|
48
48
|
--principal alice --repo .
|
|
49
|
-
# `request` is an exact alias:
|
|
50
|
-
canon learn request <strategy_id> --evaluation evaluation.json \
|
|
51
|
-
--principal alice --repo .
|
|
52
49
|
```
|
|
53
50
|
|
|
54
|
-
|
|
51
|
+
With `--json` the payload is `.data` of the `{ "data", "meta" }` envelope, and
|
|
52
|
+
`meta.next` names the human signing step. The `data` contains the evaluation digest, RFC3339 `approved_at`,
|
|
55
53
|
subject `strategy:<strategy_id>`, namespace
|
|
56
|
-
`canon-learning-approval-v1`, and `payload_hex`. `approve
|
|
57
|
-
|
|
54
|
+
`canon-learning-approval-v1`, and `payload_hex`. `approve` never
|
|
55
|
+
reads a private key and never sets `verified`.
|
|
58
56
|
6. Decode `payload_hex` to the exact canonical JSON bytes. The signed object
|
|
59
57
|
is:
|
|
60
58
|
|
|
@@ -10,8 +10,9 @@ one, and neither can `canon gate`.
|
|
|
10
10
|
## 1. Canon's own changes and external `plans:` sources
|
|
11
11
|
|
|
12
12
|
`.canon/changes/` is canon's native plan source (dialect id `canon`):
|
|
13
|
-
one `<slug>/` directory per change holding `brief.md` and `tasks.md
|
|
14
|
-
|
|
13
|
+
one `<slug>/` directory per change holding `brief.md` (and a `tasks.md`
|
|
14
|
+
only when someone adds one: an optional work note), written by
|
|
15
|
+
`canon change new` (see `canon-subject`). Every plan reader —
|
|
15
16
|
`canon ingest plans`, `canon gate task`, `canon evidence add --task`,
|
|
16
17
|
`canon dispatch --task`, `canon status` and the brief check — reads it
|
|
17
18
|
first whenever the directory exists, with no `plans:` entry. Being first,
|
|
@@ -21,7 +22,8 @@ Its records carry the actor `canon-plan-import-canon`; an external
|
|
|
21
22
|
openspec change's carry `canon-plan-import-openspec`. A `brief.md` with a
|
|
22
23
|
`Source: <path>` line is not a change of its own: it is the canon brief
|
|
23
24
|
of a change imported from an external plan (it points at the external
|
|
24
|
-
document and holds the human's `Approved:` line
|
|
25
|
+
document and holds the human's `Approved:` line, written by
|
|
26
|
+
`canon brief approve`), so the native reader
|
|
25
27
|
skips that directory and the external source keeps the change id.
|
|
26
28
|
|
|
27
29
|
`plans.sources` lists only EXTERNAL plans — documents you prepare outside
|
|
@@ -89,12 +91,13 @@ No dialect/plugin filter exists or is needed — an imported `change`/
|
|
|
89
91
|
## The `canon` dialect (`.canon/changes/`)
|
|
90
92
|
|
|
91
93
|
Uses the `openspec` change-dir grammar below, with three differences:
|
|
92
|
-
the brief is `brief.md` (
|
|
94
|
+
the brief is `brief.md` (the first paragraph of its intent section, or of
|
|
95
|
+
`## Why` in a pre-0.19 brief, is the summary; a
|
|
93
96
|
missing one is a `missing-brief-md` malformed dir), the source root is
|
|
94
97
|
always `.canon/changes/` itself (archived changes under
|
|
95
98
|
`.canon/changes/archive/`), and a `brief.md` carrying a `Source:` line is
|
|
96
|
-
skipped (a pointer brief, above). `tasks.md` uses the same
|
|
97
|
-
grammar.
|
|
99
|
+
skipped (a pointer brief, above). An existing `tasks.md` uses the same
|
|
100
|
+
checkbox row grammar; a change with none imports with zero tasks.
|
|
98
101
|
|
|
99
102
|
## The `openspec` dialect
|
|
100
103
|
|
|
@@ -175,7 +178,7 @@ downstream reader's job.
|
|
|
175
178
|
## What this skill does NOT cover
|
|
176
179
|
|
|
177
180
|
- **Authoring a change** — `canon change new` writes
|
|
178
|
-
`.canon/changes/<slug>/brief.md`
|
|
181
|
+
`.canon/changes/<slug>/brief.md` (no `tasks.md`) and adopts the change
|
|
179
182
|
into a subject (see `canon-subject`); it never scaffolds an openspec
|
|
180
183
|
change dir. This skill covers IMPORTING plans.
|
|
181
184
|
- **`canon gate check`/`canon gate task`** (the hand-authored one-task-at-
|
|
@@ -74,8 +74,10 @@ canon policy approve --by <who> [--note <text>] [--repo <dir>]
|
|
|
74
74
|
|
|
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
|
-
loop).
|
|
78
|
-
|
|
77
|
+
loop). `canon brief approve` records both in one call, so the agent does
|
|
78
|
+
not run this command next to it. Run `canon policy approve` alone for a
|
|
79
|
+
policy-only change, as the human, or as the agent right after that answer
|
|
80
|
+
(`canon policy approve --by <the human> --note "<their words>"`). It
|
|
79
81
|
writes `.canon/policy.approval.json` directly (nothing to promote):
|
|
80
82
|
|
|
81
83
|
```json
|
|
@@ -227,7 +229,7 @@ read as a big-endian u64, are less than `rate · 2^64` — computed exactly,
|
|
|
227
229
|
as a comparison against `ceil(rate · 2^64)`, so `rate: 0` samples nothing
|
|
228
230
|
and `rate: 1` samples everything. A sampled in-scope scenario whose
|
|
229
231
|
latest audit is missing or `indeterminate` is `uncovered-cell`; with
|
|
230
|
-
`shipped` in `scope`, `canon subject status <id> shipped` also refuses
|
|
232
|
+
`shipped` in `scope`, `canon subject status <id> --to shipped` also refuses
|
|
231
233
|
it. `canon context` and the `canon status` policy line show
|
|
232
234
|
`require_audit=(rate=… seed=`…` scope=…)`.
|
|
233
235
|
|
|
@@ -55,7 +55,7 @@ The order an agent follows to claim a subject done:
|
|
|
55
55
|
the screenshots or recordings it looked at with `--artifact <path>`
|
|
56
56
|
(below).
|
|
57
57
|
|
|
58
|
-
4. **Move the subject**: `canon subject status <id> verifying`, later
|
|
58
|
+
4. **Move the subject**: `canon subject status <id> --to verifying`, later
|
|
59
59
|
`shipped`. See `canon-subject`.
|
|
60
60
|
|
|
61
61
|
## `canon finding add` / `canon finding close`
|
|
@@ -135,7 +135,8 @@ fixed finding reopened with `--disposition open` counts as open again.
|
|
|
135
135
|
## `canon review add`
|
|
136
136
|
|
|
137
137
|
```bash
|
|
138
|
-
canon review add --project-id <root-id> --scenario-id <id> \
|
|
138
|
+
canon review add --project-id <root-id> --scenario-id <id> [--scenario-id <id>]... \
|
|
139
|
+
[--scenario-ids-from <file|->] \
|
|
139
140
|
--reviewer <who> --pin <sha-or-ref> --role <role> \
|
|
140
141
|
(--upstream-ref <ref> | --original-spec-ref <ref>) [--actor-id <id>] [--session-id <id>] \
|
|
141
142
|
[--outcome signed|indeterminate|unavailable] \
|
|
@@ -143,7 +144,7 @@ canon review add --project-id <root-id> --scenario-id <id> \
|
|
|
143
144
|
[--artifact <path>]... [--max-artifact-mib <MiB>]
|
|
144
145
|
```
|
|
145
146
|
|
|
146
|
-
One attributed Review record
|
|
147
|
+
One attributed Review record per scenario, joined on
|
|
147
148
|
`(project_id, scenario_id)` — both are required because two spec roots
|
|
148
149
|
may carry the same scenario id. Exactly one provenance ref is required.
|
|
149
150
|
`--actor-id` defaults to `canon`; `--session-id` fills `actor.session_id`.
|
|
@@ -153,6 +154,18 @@ own. It is written directly to the ledger, with no staging step, and says
|
|
|
153
154
|
so: `canon review add: wrote <path> (new) — written directly; nothing to
|
|
154
155
|
promote`.
|
|
155
156
|
|
|
157
|
+
### Several scenarios in one call (0.18)
|
|
158
|
+
|
|
159
|
+
`--scenario-id` is repeatable, and `--scenario-ids-from <file>` (`-` reads
|
|
160
|
+
stdin) adds one id per line; `#` starts a comment and blank lines are
|
|
161
|
+
skipped. One reviewer, pin, provenance ref and set of artifacts cover
|
|
162
|
+
every review; each review pins its own scenario and evidence, and each
|
|
163
|
+
gets its own `pinned` and `wrote` line. The artifacts are stored once.
|
|
164
|
+
It is all or nothing: a scenario with no record, or a line in the file
|
|
165
|
+
that is not a scenario id, refuses the call (exit 2) and writes no review.
|
|
166
|
+
This is also the batch re-pin after `canon evidence add` reports reviews
|
|
167
|
+
it made stale (see `canon-gate`).
|
|
168
|
+
|
|
156
169
|
### What the reviewer looked at (artifacts)
|
|
157
170
|
|
|
158
171
|
`--artifact <path>` (repeatable) binds a file the reviewer looked at — a
|
|
@@ -173,19 +186,25 @@ The field is absent when no `--artifact` is given. `review add` prints
|
|
|
173
186
|
.canon/artifacts/sha256/ (<k> newly stored) — commit them with the
|
|
174
187
|
ledger`.
|
|
175
188
|
|
|
176
|
-
`canon gate check` re-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
`canon
|
|
189
|
+
`canon gate check` re-checks every file bound to a current in-effect
|
|
190
|
+
review (the latest per `(project_id, scenario_id, reviewer)`, unless it
|
|
191
|
+
went stale against its scenario) by the evidence rule (see `canon-gate`,
|
|
192
|
+
"Bound evidence stays provable"). A missing blob whose working-tree file
|
|
193
|
+
still matches is an advisory naming `canon evidence vault`, which stores
|
|
194
|
+
it back. A missing blob with a changed or missing working-tree file, or
|
|
195
|
+
an altered blob, is `stale-evidence` on the review's scenario, naming
|
|
196
|
+
the review:
|
|
181
197
|
|
|
182
198
|
```
|
|
183
|
-
stale-evidence world.demo.01 — review by `reviewer-2` (actor `reviewer-2`, 2026-10-11T09:00:00Z) bound file `reports/title.png` recorded sha256 <hex>; stored blob .canon/artifacts/sha256/<hex>: missing; working tree: <hex> — the bound bytes are gone, look again and re-record the review with `canon review add --artifact <path>`
|
|
199
|
+
stale-evidence world.demo.01 — review by `reviewer-2` (actor `reviewer-2`, 2026-10-11T09:00:00Z) bound file `reports/title.png` recorded sha256 <hex>; stored blob .canon/artifacts/sha256/<hex>: missing; working tree: <other hex> — the bound bytes are gone, look again and re-record the review with `canon review add --artifact <path>`
|
|
184
200
|
```
|
|
185
201
|
|
|
186
202
|
The reviewer's later review replaces the earlier one, so its files are
|
|
187
|
-
the ones checked.
|
|
188
|
-
|
|
203
|
+
the ones checked. A review that went stale (its scenario's spec or
|
|
204
|
+
evidence moved since) is not checked either: it no longer counts, and the
|
|
205
|
+
scenario's newer review is the one whose files are checked. Canon binds
|
|
206
|
+
and re-checks bytes only; it never compares images or judges what a
|
|
207
|
+
screenshot shows.
|
|
189
208
|
|
|
190
209
|
### What the review saw (pins)
|
|
191
210
|
|
|
@@ -394,7 +413,7 @@ review waivers: 1 advisory(ies) — not failing the gate:
|
|
|
394
413
|
|
|
395
414
|
## The status guard
|
|
396
415
|
|
|
397
|
-
With `require_review` present, `canon subject status <id> <state>`
|
|
416
|
+
With `require_review` present, `canon subject status <id> --to <state>`
|
|
398
417
|
prints on stderr which review checks it ran or skipped:
|
|
399
418
|
|
|
400
419
|
```
|
|
@@ -98,17 +98,23 @@ canon query --kind handoff --since 2026-06-01T00:00:00Z
|
|
|
98
98
|
|
|
99
99
|
# Machine-readable output instead of the human table.
|
|
100
100
|
canon query --kind trajectory --json
|
|
101
|
+
|
|
102
|
+
# Page a long result and keep only the fields you need; `meta` carries
|
|
103
|
+
# returned, total, hasMore and nextCursor (`--all` returns everything).
|
|
104
|
+
canon query --kind trajectory --json --limit 50 --fields at,kind
|
|
105
|
+
canon query --kind trajectory --json --limit 50 --cursor <nextCursor>
|
|
101
106
|
```
|
|
102
107
|
|
|
103
108
|
## What `canon report` does and doesn't read
|
|
104
109
|
|
|
105
110
|
canon exposes layered `stg_`/`int_`/`mart_` query views; `canon report`
|
|
106
111
|
builds its numbers from the `mart_` views. Those read ONLY local roots
|
|
107
|
-
— the git ledger
|
|
112
|
+
— the git ledger, the sqlite `hot` db file (local, no credential; read
|
|
113
|
+
since 0.18) and a LOCAL `.canon/r2` parquet mirror. `canon report`
|
|
108
114
|
never opens a live DB connection or the live bucket, so:
|
|
109
115
|
|
|
110
|
-
- Kinds routed to `hot`
|
|
111
|
-
report
|
|
116
|
+
- Kinds routed to a postgres `hot` rung do NOT appear in `canon
|
|
117
|
+
report`; kinds routed to `canon init`'s default sqlite `hot` rung do.
|
|
112
118
|
- Kinds routed to `cold` (s3) appear only if a local `.canon/r2` mirror
|
|
113
119
|
has been separately materialized — there is no automatic sync.
|
|
114
120
|
|
|
@@ -119,8 +125,11 @@ section listing them and emits a matching stderr `WARN`.
|
|
|
119
125
|
|
|
120
126
|
## Retention, export, and access audit
|
|
121
127
|
|
|
122
|
-
`canon purge --kind <kind> --before <RFC3339> [--repo <dir>] [--
|
|
123
|
-
is the canonical data-lifecycle boundary
|
|
128
|
+
`canon purge --kind <kind> --before <RFC3339> (--plan | --confirm <planDigest>) [--repo <dir>] [--json]`
|
|
129
|
+
is the canonical data-lifecycle boundary: `--plan` prints the removal set and
|
|
130
|
+
its `planDigest` and deletes nothing; `--confirm <planDigest>` deletes only if
|
|
131
|
+
the plan recomputed now has the same digest (`PLAN_CHANGED`, exit 2, when the
|
|
132
|
+
data moved), and a bare `purge` is refused (`PURGE_PLAN_REQUIRED`). The only purgeable kinds are
|
|
124
133
|
`session`, `run`, `event`, and `trajectory`; the cutoff applies to every
|
|
125
134
|
routed rung and every configured aging destination, including retained
|
|
126
135
|
historical versions. For `trajectory`, the command also purges the
|
|
@@ -133,8 +142,14 @@ an explicit `partial` result with matched/deleted counts and a nonzero
|
|
|
133
142
|
status. Purge never rewrites Git history or deletes source transcript
|
|
134
143
|
files.
|
|
135
144
|
|
|
145
|
+
With `--json`, `purge` and `export` answer `{ "data", "meta" }` like every other
|
|
146
|
+
command: `data` is the plan or manifest, `meta.next` the step after (a plan's is the
|
|
147
|
+
human's review and `--confirm`), and an export's page state (`total`, `returned`,
|
|
148
|
+
`nextCursor`) sits in `meta`. `export --json --out <file>` writes the manifest to the
|
|
149
|
+
file and answers with `data: { out, kind, recordCount }`.
|
|
150
|
+
|
|
136
151
|
`canon export --kind <kind> [--before <RFC3339>] [--after <RFC3339>]
|
|
137
|
-
[--out <file>] [--json]` emits a versioned manifest containing source scope,
|
|
152
|
+
[--out <file>] [--json] [--limit <n> | --all] [--cursor <c>]` emits a versioned manifest containing source scope,
|
|
138
153
|
counts, digests, and redacted metadata. It does not authorize backend access;
|
|
139
154
|
credentials remain an operator prerequisite. New output files are mode
|
|
140
155
|
`0600`. Raw event detail text is excluded unless `--include-sensitive` is
|
|
@@ -160,7 +175,7 @@ record bodies.
|
|
|
160
175
|
Four writes stage their record instead of committing it: `canon evidence
|
|
161
176
|
add`, `canon finding add` and `canon finding close` (under
|
|
162
177
|
`.canon/ledger/_staging/`, committed by `canon gate promote`) and `canon
|
|
163
|
-
divergence
|
|
178
|
+
divergence add` (committed by `canon divergence promote`). `canon.yaml`'s
|
|
164
179
|
`write:` section decides whether they promote in the same run:
|
|
165
180
|
|
|
166
181
|
```yaml
|