@journeykit/canon 0.7.0 → 0.8.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/index.js CHANGED
@@ -155,5 +155,10 @@ if (!binary) {
155
155
  console.error(" build from source: cargo build --release -p canon-cli");
156
156
  process.exit(1);
157
157
  }
158
- var result = spawnSync(binary, process.argv.slice(2), { stdio: "inherit" });
159
- process.exit(result.status ?? 1);
158
+ var bundledSkills = join(cliDir, "dist", "skills");
159
+ var checkoutSkills = join(workspaceRoot, "canon", "skills");
160
+ var skillsSource = existsSync(join(bundledSkills, "SKILL.src.md")) ? bundledSkills : checkoutSkills;
161
+ var result = spawnSync(binary, process.argv.slice(2), {
162
+ stdio: "inherit",
163
+ env: { ...process.env, CANON_SKILLS_SOURCE: skillsSource }
164
+ });
@@ -0,0 +1,46 @@
1
+ ---
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.
4
+ ---
5
+
6
+ # Canon
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.
9
+
10
+ ## Setup and provider projection
11
+
12
+ ```bash
13
+ canon init
14
+ canon skills install # detects .claude/.codex/.omp/.pi; if none exists, installs Claude+Codex
15
+ canon skills install --providers=claude,codex,omp,pi
16
+ canon skills check # read-only drift check
17
+ canon skills doctor # diagnostics; never deletes user files
18
+ ```
19
+
20
+ 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
25
+ projections are project-local passive skill bundles: the
26
+ `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`.
30
+
31
+ ## Route by task
32
+
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.
35
+ - **Understand available fields/enums/policy bindings:** read `reference/canon-context.md` and `reference/canon-vocab.md`.
36
+ - **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
+ - **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
+ - **Ingest artifacts or sessions:** read `reference/canon-artifact-ingest.md`, `reference/canon-session-ingest.md`, or `reference/canon-plan-import.md`.
40
+ - **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`.
41
+ - **Retrieve dispatch guidance:** read `reference/canon-retrieve.md` and load `scripts/canon-retrieve-pre-dispatch.sh` when wiring hooks.
42
+ - **Read generated reports and dashboards:** read `reference/canon-report-dashboard.md`.
43
+
44
+ ## Safety contract
45
+
46
+ `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.
@@ -0,0 +1,72 @@
1
+ # canon-artifact-ingest
2
+
3
+ `canon ingest artifacts` turns a repo's canon-native ARTIFACTS — the
4
+ outputs of review/divergence/task/handoff work — into the regime-keyed
5
+ verdict trajectories that strategy memory and the reward gate consume
6
+ (see `canon-learn`). One pass runs the four adapters, normalizes, derives
7
+ verdicts, and writes idempotently.
8
+
9
+ ## `canon ingest artifacts [--repo <dir>] [--watch]`
10
+
11
+ ```bash
12
+ canon ingest artifacts # one pass over the repo's artifacts
13
+ canon ingest artifacts --repo ../svc # a specific repo
14
+ canon ingest artifacts --watch # poll loop
15
+ ```
16
+
17
+ Source roots resolve from `canon.yaml`'s `artifacts.sources` config
18
+ (`openspec_root`, ledger/divergence/handoff roots) — generic, never a
19
+ hardcoded path; ordinarily the consumer repo's own roots. Unconfigured =
20
+ no scan for that family.
21
+
22
+ ## The four adapters
23
+
24
+ Each reads one artifact family and emits a normalized event keyed by that
25
+ family's join key:
26
+
27
+ - **`divergence`** — parity/divergence reports → verdict keyed by the
28
+ lane derived from the report's on-disk path.
29
+ - **`ledger`** — review ledger records → verdict; the record's
30
+ trust-level tag (`@reviewed`/`@ratified`) rides through as a
31
+ passthrough `trust_level` field, not collapsed into one "success"
32
+ bucket. (The other three adapters carry no trust-level concept.)
33
+ - **`openspec_task`** — `tasks.md` checkbox rows: a `- [ ]` → `- [x] … —
34
+ ✅ <evidence>` flip, or a `**DEFERRED**`/`**DROPPED**` rewrite, keyed by
35
+ `task_id` (`<change_id>#<n>`). The evidence string is parsed for a
36
+ mergeable PR / CI reference where present (CI-first).
37
+ - **`handoff`** — handoff transitions, keyed by `handoff_id`.
38
+
39
+ ## Verdict derivation
40
+
41
+ Verdict derivation is a pure function from a normalized event to an
42
+ optional verdict `{role, polarity, becomes}`:
43
+
44
+ - A matched mapping row → a verdict for that role.
45
+ - No mapped row → NO verdict. A prose-only task flip, a deferred/dropped
46
+ task, or a handoff transition alone produces none — deliberate, not a
47
+ gap.
48
+
49
+ ## Idempotence — a re-ingest is a no-op
50
+
51
+ Write-identity is content-digest based: the digest derives both the
52
+ `regime_key` `<hash>` segment and the trajectory write-identity. Before
53
+ writing, the driver does a regime-key existence check — a second ingest
54
+ over an unchanged corpus re-derives the identical id and persists zero
55
+ new trajectories (reported as `trajectories_skipped_duplicate`).
56
+
57
+ ## Reading a role's own verdict stream
58
+
59
+ Verdicts persist regime-keyed (`<role>/<repo>/<area>/<hash>`), so a
60
+ role-scoped agent reads only its own lane — the same regime namespacing
61
+ `canon retrieve --role <r> --regime <k>` and `canon learn` query. This
62
+ verdict stream is the evidence the reward gate scores when deciding
63
+ promotion/demotion.
64
+
65
+ ## What this skill does NOT cover
66
+
67
+ - Session/cost ingestion (`canon ingest sessions`) — a different
68
+ pipeline; see the `canon-session-ingest` skill.
69
+ - Strategy promotion/demotion mechanics — see the `canon-learn` skill;
70
+ this skill only produces the verdict evidence promotion reads.
71
+ - The `regime_key` grammar itself — see `canon-retrieve`, which derives
72
+ the same key via the `canon regime-key` serializer.
@@ -0,0 +1,155 @@
1
+ # canon-authoring
2
+
3
+ A `.feature` corpus is the join spine everything else in canon hangs
4
+ off: `canon inventory sync` indexes it, `canon review`/`canon evidence`
5
+ attest against it, `canon gate check` measures coverage over it. A
6
+ corpus that is organized badly does not fail `canon format` — it
7
+ produces a green gate that measures the wrong thing. This skill is the
8
+ organizing rule.
9
+
10
+ ## Vocabulary
11
+
12
+ | Term | Is | Is NOT |
13
+ |---|---|---|
14
+ | **area** | One bounded context of the product, a kebab noun (`checkout`, `world-map`, `dm`). The directory `area=<area>/`. | A sprint, a team, a theme ("experience", "quality"). |
15
+ | **surface** | One screen, flow, or API a user or caller actually meets (`cart`, `place-lock`, `session-create`). Exactly ONE `.feature` file per surface. | A grab-bag ("misc", "experience", "review") — see anti-patterns. |
16
+ | **scenario** | One observable behavior of that surface: a refusal, a transition, a boundary, a derived value, an idempotence property. Readable by someone who has never seen the code. | A function name, an implementation step, a rule about a document or a report. |
17
+ | **`<area>.<surface>.<nn>`** | The scenario's durable id. Assigned once, never renumbered; gaps are fine. | A sort key you tidy up. |
18
+
19
+ A `Feature:` title names the surface. If you cannot name the surface,
20
+ you do not have one yet.
21
+
22
+ ## Lanes: what kind of requirement is this?
23
+
24
+ Every scenario is one of a small set of lanes, tagged `@lane:<value>`:
25
+
26
+ | Lane | The scenario is about | Attested by |
27
+ |---|---|---|
28
+ | `behavior` | What the product does — state, input, data, navigation, errors. The default. | A test or an observed run. |
29
+ | `design` | How it looks and moves — tokens, spacing, motion, easing. | A design review. |
30
+ | `architecture` | How the code is shaped — layering, ownership, error surfacing. | An architecture review. |
31
+ | `process` | How the TEAM works — what a review report may claim, how acceptance is run, what a build must check. | Nothing in the product. |
32
+
33
+ `process` rules are real, but they have no code surface and can never
34
+ be `faithful` from a test. Keep them out of the coverage number:
35
+
36
+ ```yaml
37
+ # .canon/policy.yaml
38
+ spec_coverage:
39
+ require_evidence: true
40
+ scope: [building, verifying]
41
+ exclude_lanes: [process]
42
+ ```
43
+
44
+ The base set comes from `canon.core`; a repo declares its own `lane`
45
+ enum in `.canon/vocab/<id>/enums.yaml` to replace it (see
46
+ `canon-vocab`). `canon context` shows the set in force. A value outside
47
+ a declared set is refused at the scaffold and dropped, with a counted
48
+ diagnostic, at `canon inventory sync`.
49
+
50
+ **The test for a `behavior` scenario:** name the test or run that would
51
+ attest it. If you can't, it is a `process` rule, or it is a gap — record
52
+ the gap, do not write the scenario.
53
+
54
+ ## Subject pinning
55
+
56
+ Every scenario belongs to a Subject (the durable product unit, see
57
+ `canon-subject`). Write the Subject first, then pin with `--subject`;
58
+ `canon inventory sync` carries the tag onto the index record and
59
+ `spec_coverage.scope` filters on it. An unpinned scenario is invisible to
60
+ a scoped gate.
61
+
62
+ ## The commands — and the one thing you never type
63
+
64
+ ```bash
65
+ canon feature new checkout.cart --title "Cart" # once per surface
66
+ canon scenario new checkout.cart.01 --title "Adding an item raises the count" \
67
+ --subject checkout-core --lane behavior # once per behavior
68
+ canon format specs # clean?
69
+ canon inventory sync # index it
70
+ ```
71
+
72
+ - `--actor <id>` (or `CANON_ACTOR` in the environment) puts YOUR id on the
73
+ provenance line. Default is `canon-scaffold`.
74
+ - **Never hand-type, move, or edit a `# canon:` line.** The scaffold
75
+ writes it directly above the scenario's tags:
76
+
77
+ ```gherkin
78
+ # canon: {"schema":1,"at":"2026-09-16T10:02:11Z","actor":{"agent_id":"Main"}}
79
+ @subject:checkout-core
80
+ @lane:behavior
81
+ @checkout.cart.01
82
+ Scenario: Adding an item raises the count
83
+ Given an empty cart
84
+ When one item is added
85
+ Then the count reads 1
86
+ ```
87
+
88
+ A line directly UNDER the header (older corpora) is also accepted.
89
+ Anywhere else — separated by a blank line, above `Feature:` with
90
+ scenario tags in between — is `missing-provenance`, and the whole
91
+ root's sync aborts.
92
+ - Steps are yours to write; the scaffold's `Given a step` is a
93
+ placeholder, not a spec.
94
+
95
+ ## Anti-patterns, by name
96
+
97
+ - **The grab-bag surface.** One `experience.feature` holding pointer
98
+ capture, i18n fallback, a compiler tag rule, and "the review report
99
+ must not claim video quality". Four surfaces and a process rule. Split
100
+ by what the user meets; move the rule to `lane:process`.
101
+ - **A Then about a document.** "Then the report distinguishes X from Y."
102
+ The product has no report. `lane:process`, or `policy.yaml`.
103
+ - **A Feature title that is a theme.** "Fidelity of the original
104
+ experience" names no surface. Name the screen.
105
+ - **Hand-stamped provenance.** Eighty lines with one timestamp and the
106
+ same `agent_id`. Use `canon scenario new --actor`.
107
+ - **Coverage by splitting.** One behavior as two scenarios so two rows go
108
+ green. One behavior, one id.
109
+ - **Runner tags as classification.** `@design`, `@p2`, `@wip` are
110
+ invisible to canon — they index nothing. Only `@lane:` and `@subject:`
111
+ are read; any other `@name:value` namespace is counted as a diagnostic
112
+ at sync so you can see it was dropped.
113
+
114
+ ## Worked example
115
+
116
+ ```
117
+ specs/features/kind=feature/area=checkout/
118
+ cart.feature # surface: the cart screen
119
+ payment.feature # surface: the payment step
120
+ ```
121
+
122
+ ```gherkin
123
+ Feature: Cart
124
+ # canon: {"schema":1,"at":"2026-09-16T10:02:11Z","actor":{"agent_id":"Main"}}
125
+
126
+ # canon: {"schema":1,"at":"2026-09-16T10:02:11Z","actor":{"agent_id":"Main"}}
127
+ @subject:checkout-core
128
+ @lane:behavior
129
+ @checkout.cart.01
130
+ Scenario: Adding an item raises the count
131
+ Given an empty cart
132
+ When one item is added
133
+ Then the count reads 1
134
+
135
+ # canon: {"schema":1,"at":"2026-09-16T10:02:40Z","actor":{"agent_id":"Main"}}
136
+ @subject:checkout-core
137
+ @lane:design
138
+ @checkout.cart.02
139
+ Scenario: The count badge uses the accent token and a 120ms ease-out
140
+ Given a cart with one item
141
+ When a second item is added
142
+ Then the badge animates over 120ms with ease-out in the accent color
143
+
144
+ # canon: {"schema":1,"at":"2026-09-16T10:03:02Z","actor":{"agent_id":"Main"}}
145
+ @subject:checkout-core
146
+ @lane:process
147
+ @checkout.cart.03
148
+ Scenario: A cart review names the device it was run on
149
+ Given a cart design review
150
+ When the report is written
151
+ Then it names the device and OS the review ran on
152
+ ```
153
+
154
+ `.03` is excluded from coverage by `exclude_lanes: [process]`; `.01` is
155
+ attested by a test, `.02` by a design review.
@@ -0,0 +1,137 @@
1
+ # canon-context
2
+
3
+ `canon context` is a capability QUERY over the same schema/policy/
4
+ vocabulary registry `canon format`/`canon gate` validate against — never
5
+ validation itself. It answers "what CAN I author in this repo, and how?":
6
+ record kinds + their envelope fields, enum domains, join-key grammars,
7
+ partition layout, policy-derived evidence requirements, the typed
8
+ authoring vocabulary, and the CEL binding surface — folded into one
9
+ deterministic authoring surface.
10
+
11
+ ## `canon context [--repo <dir>] [--json]`
12
+
13
+ ```bash
14
+ canon context # compact human outline (prompt-injectable)
15
+ canon context --json # the full machine-readable surface
16
+ canon context --repo ../other # resolve a specific repo's surface
17
+ ```
18
+
19
+ ## Reproducible context packs and prompt bundles
20
+
21
+ Use an explicit, repository-relative JSON manifest when a run needs a
22
+ replayable snapshot. The manifest selects files; Canon copies their bytes into
23
+ an immutable content-addressed registry and records only metadata and digests
24
+ in the pack manifest.
25
+
26
+ ```bash
27
+ canon context-pack create --manifest .canon/context-manifest.json
28
+ canon context-pack create --manifest .canon/context-manifest.json --repo ../my-repo --json
29
+ canon context-pack show sha256:<pack-digest> --repo . --json
30
+ canon context-pack verify sha256:<pack-digest>
31
+ ```
32
+
33
+ `create` prints only the pack id by default, so captured content is never
34
+ written to concise terminal output. `--json` prints the pack manifest,
35
+ including selected-path metadata and content digests. `show` verifies the
36
+ manifest and immutable content objects before returning it; `verify` exits
37
+ nonzero when the pack is missing, stale, or tampered. Manifest paths must be
38
+ relative to the resolved repo, remain inside it, and contain no symlink;
39
+ missing or unsafe paths are rejected by the context-pack module.
40
+
41
+ ## Provider-neutral adapter responses
42
+
43
+ An execution provider may return a versioned response envelope for Canon to
44
+ validate. Validation is read-only: `canon` never starts a provider, interprets
45
+ provider-specific extension payloads, or enforces declared capabilities.
46
+
47
+ ```bash
48
+ canon adapter validate --response response.json
49
+ canon adapter validate --response response.json --repo . --json
50
+ ```
51
+
52
+ The envelope has `protocol_version: 1`, required `run_id`, `provider`,
53
+ `model`, and `context_pack_id`, a closed status vocabulary
54
+ (`succeeded`/`failed`/`aborted`), and strict filesystem/network/secrets
55
+ capability vocabularies. Evidence references are safe, relative references
56
+ with `sha256:<64-hex>` digests. Telemetry timestamps must be RFC3339 with
57
+ `ended_at >= started_at`, and counts/cost are nonnegative.
58
+
59
+ Core fields are closed and unknown fields are rejected. Provider-specific JSON
60
+ belongs only under opaque `extensions`; successful JSON output contains the
61
+ normalized core summary and extension key names, never extension payloads.
62
+ `--repo` can verify the referenced context pack; file-only validation reports
63
+ `context_join_verified: null`. A valid declaration is not a sandbox:
64
+ providers MUST enforce filesystem, network, and secret capabilities before
65
+ execution.
66
+
67
+ Version prompt inputs separately when several runs should share the same
68
+ prompt files:
69
+
70
+ ```bash
71
+ canon prompt register --name reviewer --version v1 \
72
+ --manifest .canon/reviewer-prompt.json
73
+ canon prompt show --name reviewer --version v1 --json
74
+ ```
75
+
76
+ Prompt registration uses the same immutable content registry. `prompt show`
77
+ verifies the selected bundle and returns its metadata/digests; neither concise
78
+ output form includes captured prompt bytes.
79
+
80
+ - **A capability query, never validation:** `canon context` ALWAYS exits
81
+ `0` with the full surface — even when `canon format --check` or
82
+ `canon gate` would report diagnostics against the same repo. It reads
83
+ only the schema/policy/vocabulary registry, never the corpus or the
84
+ evidence ledger.
85
+ - `--repo` resolves through the same nearest-`canon.yaml`-ancestor walk
86
+ `canon format`/`canon gate`/`canon retrieve` use: omitted or `.` walks up
87
+ from cwd to the project root; any other explicit dir is used as-is.
88
+ - `--json` renders the full surface; the default renders a compact per-
89
+ section outline. Both project from the identical resolution (byte-stable
90
+ across runs).
91
+
92
+ ## The surface sections
93
+
94
+ - **`kinds`** — each record kind's `{schema_version, envelope_fields,
95
+ partition}`.
96
+ - **`enums`** — each enum domain's members (verdicts, statuses, lanes,
97
+ roles, polarity, …).
98
+ - **`joinKeys`** — the join-spine grammar string per key (`regime_key`,
99
+ `session_id`, `task_id`, …).
100
+ - **`policy`** — the resolved `policy.yaml`-derived evidence/trust
101
+ requirements per kind.
102
+ - **`vocab`** — the typed authoring vocabulary (directive/enum/evidence-
103
+ kind index) + its content-hash version.
104
+ - **`cel`** — the per-kind CEL binding surface: the bindable
105
+ `record.<field>` names + types and the callable function allowlist a
106
+ `policy.yaml` `applies_when:`/predicate may reference. The agent-facing
107
+ answer to "what can a CEL predicate for this kind reference?"
108
+
109
+ ## Single source of truth
110
+
111
+ `canon context` never re-derives a schema/policy/enum list of its own — it
112
+ resolves through the same shared schema, policy, and vocabulary loaders
113
+ the validators use. A schema edit propagates to `canon context`'s output
114
+ AND `canon format`'s diagnostics from one edit site, never two.
115
+
116
+ ## Reading an "expected one of: …" validator error
117
+
118
+ When `canon format --check` rejects an out-of-domain enum value it emits:
119
+
120
+ ```
121
+ `not-a-real-kind` is not a valid value for `kind` of `run` (expected one of: run, review, divergence, …)
122
+ ```
123
+
124
+ The `expected one of:` member list is the SAME enum domain `canon context
125
+ --json`'s `enums.<field>` reports for that kind — so to fix the artifact,
126
+ pick a member `canon context` lists. Both read the member list off the
127
+ same schema, no hand-maintained second copy.
128
+
129
+ ## What this skill does NOT cover
130
+
131
+ - `canon format --check`'s full violation taxonomy — see `canon-fmt`;
132
+ this skill only covers reading its enum diagnostic against the surface.
133
+ - Authoring a typed task atom / handoff body — see
134
+ `canon-vocab`; `canon context`'s `vocab`/`cel` sections
135
+ tell you what's declarable, not how to declare it.
136
+ - `policy.yaml` CEL authoring semantics — see `canon-policy`; this skill
137
+ only surfaces the bindable identifier/function set per kind.
@@ -0,0 +1,43 @@
1
+ # canon-fmt
2
+
3
+ `canon format --check` is canon's read-only format validator over a
4
+ consumer-repo artifact corpus: ledger records (run/drill/review/clear/
5
+ code-review/design-review), divergence events (manifest/review/
6
+ remediation), `features/`, `inventory/`, and `policy.yaml`. It never
7
+ rewrites or mutates the corpus — it only validates and reports.
8
+
9
+ ## Running
10
+
11
+ ```bash
12
+ canon format --check <corpus-root> # e.g. a consumer repo's spec/
13
+ ```
14
+
15
+ Read-only, always. Exits nonzero if any violation is found, and prints
16
+ a report grouped by failure class in this fixed order:
17
+
18
+ | Failure class | Meaning | Fix |
19
+ |---|---|---|
20
+ | `layout-grammar` | Wrong Hive path shape — a `features/`/`inventory/` file not under a `kind=<kind>/area=<area>/` prefix, a wrong leaf filename, or `assets.lock`'s ad-hoc format. | Move the file under the correct `kind=…/area=…/` prefix with the expected leaf name. |
21
+ | `missing-envelope` | A YAML file (`inventory/`, `policy.yaml`) has no top-level `schema`/`kind`/`at`/`actor` keys. | Add the four envelope keys at the top level. |
22
+ | `missing-provenance` | A `.feature` file's `Feature:`/`Scenario:` header has no `# canon: {...}` comment either directly above it (with its tags, no blank line) or directly below it. | Use `canon scenario new` / `canon feature new`, which stamp it; never hand-type the line. See `canon-authoring`. |
23
+ | `missing-actor` | No structured `actor` object — a bare `by` string, or absent. | Replace `by: <name>` with a structured `actor` object. |
24
+ | `unspecified-evidence` | A ledger `run`/`drill` record's `evidence` field is absent or an empty, untyped array. | Add a typed `evidence` array. |
25
+ | `free-text-ref` | `upstream_ref`/`port_ref` has a segment not matching `<file>#<symbol>[:<a>-<b>]`. | Rewrite the ref into the `<file>#<symbol>[:<a>-<b>]` grammar. |
26
+ | `joined-ref` | `upstream_ref`/`port_ref` is `;`- or `,`-joined but not split into a structured `refs` array. | Split into a `refs:` array, one entry per ref. |
27
+ | `abbreviated-sha` | `app_sha`/`harness_sha` is present but shorter than the full 40-hex form. | Use the full 40-character SHA. |
28
+ | `one-way-backref` | A divergence review event's `ledger_ref` has no reciprocal `divergence_refs` entry on the ledger record it names. | Add the matching `divergence_refs` entry to that ledger record. |
29
+ | `missing-join-identity` | Corpus-wide, one line: no ledger record carries `change_id`/`task_id`. | Standing note only — new records carry these going forward; historical backfill is out of scope. |
30
+
31
+ Every class is a stable string, safe to grep/diff across runs.
32
+
33
+ ## What it does NOT do
34
+
35
+ - **Rewrite anything.** It only validates and reports; a one-shot
36
+ migration of an existing corpus is a separate, per-repo concern.
37
+ - **Backfill `change_id`/`task_id` on historical records.** Both stay
38
+ absent on records that predate the schema; `missing-join-identity` is
39
+ a standing corpus-level note, not a per-record defect it fixes.
40
+ - **Guess a `{file, symbol}` from free prose.** A non-conforming ref is
41
+ reported as `free-text-ref` exactly as found, never fabricated.
42
+ - **Touch a consumer repo's CI gates.** Conforming a corpus and
43
+ reconciling its own tooling is that repo's own operational step.
@@ -0,0 +1,199 @@
1
+ # canon-gate
2
+
3
+ `canon gate` is canon's trust spine: a two-layer evidence gate over a
4
+ repo's artifact corpus. It answers two DIFFERENT questions per artifact —
5
+ "does required evidence exist" (coverage) and "did the evidence pass, by
6
+ whom, how stale" (verdict-ledger) — never collapsing them into one
7
+ "done" boolean.
8
+
9
+ ## The trust ladder
10
+
11
+ Each artifact climbs a trust ladder as evidence accrues: an unreviewed
12
+ record is weaker than a reviewed one, which is weaker than a ratified
13
+ one. `policy.yaml` sets a `trust_required` level per artifact class; the
14
+ release-scoped check (below) enforces it. A human-only `flagged` overlay
15
+ overrides the ladder — a flagged artifact is never green regardless of
16
+ evidence. (Author `policy.yaml`'s trust/staleness fields via `canon-policy`.)
17
+
18
+ ## The eight failure classes
19
+
20
+ Every violation carries one of these stable, grep-able strings:
21
+
22
+ | Class | Meaning |
23
+ |---|---|
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. The detail string distinguishes them. Coverage means "a test exists", not "a test passed": even a `Divergent` verdict satisfies the role-cell form. |
25
+ | `unreviewed-promotion` | An artifact tagged `reviewed` has no matching ledger review record. |
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. |
28
+ | `malformed-evidence` | A candidate record doesn't parse, is misfiled, or carries an unparseable interim tag. Malformed evidence is no evidence. |
29
+ | `flagged` | The human-only `flagged` overlay is set — never green regardless of passing evidence. |
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. |
32
+
33
+ ## `canon gate check [--repo <dir>] [--release]`
34
+
35
+ Assembles coverage + ledger + staleness + the always-on trust-ladder
36
+ check, plus the opt-in spec-coverage check, over the resolved repo's
37
+ corpus and runs them, printing every violation grouped by failure class.
38
+ `--release` additionally engages the release-scoped
39
+ `trust-below-required` check; the trust-ladder check is never dropped
40
+ when `--release` is passed.
41
+
42
+ ```bash
43
+ canon gate check --repo . # ordinary evaluation
44
+ canon gate check --repo . --release # + trust-below-required
45
+ ```
46
+
47
+ Exit `0` clean, `1` gate-red (any violation), `2` usage/load failure
48
+ (unreadable ledger, corrupt `canon.yaml`). `--repo` (or its omission)
49
+ resolves through the nearest-ancestor `canon.yaml` walk, so it reads the
50
+ repo ROOT's `.canon/policy.yaml` and `.canon/ledger` from any subdirectory.
51
+
52
+ ### The spec-derived worklist (`spec_coverage`, opt-in)
53
+
54
+ Coverage groups the EVIDENCE corpus, so it can only ever enumerate
55
+ artifacts that already have evidence. A `.feature` scenario nobody has
56
+ attested to appears in no group and is invisible to it. `spec_coverage`
57
+ adds the other direction: it starts from the Scenario corpus
58
+ `canon inventory sync` materializes and left-joins evidence onto it, so
59
+ an unimplemented or mismatched spec becomes a reported violation.
60
+
61
+ ```yaml
62
+ # .canon/policy.yaml — ABSENT by default; absent means zero violations
63
+ spec_coverage:
64
+ require_evidence: true
65
+ scope: [building, verifying] # optional; omit for the whole corpus
66
+ ```
67
+
68
+ - **Unimplemented** — no `EvidenceRecord` carries the scenario's
69
+ `(project_id, scenario_id)`. Author one with
70
+ `canon evidence add --scenario-id <id> --project-id <root-id>`; both
71
+ flags are required together, because two spec roots may carry the same
72
+ scenario id.
73
+ - **Mismatched** — the folded divergence state is `open`,
74
+ `still-divergent`, or `resolved-invalid` (a resolution whose app sha
75
+ has moved), or the latest ledger verdict is `divergent`.
76
+ - **`scope`** narrows blocking to scenarios whose `@subject:<id>` tag
77
+ links them to a Subject in one of the named statuses. With a `scope`
78
+ set, an untagged scenario is out of scope; with `scope` omitted, every
79
+ scenario is in scope. A tag naming no Subject record is reported as a
80
+ dangling link, never silently skipped.
81
+
82
+ Two refusals rather than a silent pass, both surfacing as
83
+ `uncovered-cell` on the subject `spec_coverage`:
84
+
85
+ - A **malformed section** (unknown key, unknown `scope` status) refuses
86
+ at check time. It is never treated as absent — a typo must not be
87
+ silently equivalent to not opting in.
88
+ - A **corpus kind routed off the gate's rung** (`scenario`,
89
+ `divergence`, or `subject` sent anywhere but `local`) refuses, because
90
+ the gate reads one tier and would otherwise pass by seeing nothing.
91
+
92
+ Malformed rows in those three kinds do NOT surface as
93
+ `malformed-evidence`; they are kept off the evidence violation set on
94
+ purpose, so enabling nothing changes no existing verdict.
95
+
96
+ ## Effect-aware risk approvals (`risk_tiers`)
97
+
98
+ `risk_tiers` is absent by default and remains a no-op when absent. When it
99
+ is present, `canon gate check` selects the highest-ranked configured tier
100
+ whose current evidence binding matches a path or exact `effect:<kebab-slug>`.
101
+ The current generation is selected deterministically; approvals from an
102
+ older `evidence_sha`, a non-`faithful` verdict, or an unverified attestation
103
+ never count. A required tier also fails closed when the current record has
104
+ no usable `surface_ref`/effect binding, so a caller cannot suppress a
105
+ configured rule by declaring a safe path.
106
+
107
+ `canon evidence add --surface-ref src/auth/login.rs` (repeatable) or
108
+ `--surface-ref effect:secret-access` persists an explicitly validated
109
+ binding. This is still attestation input, not proof of the changed paths;
110
+ run/diff metadata is authoritative when available, and an unavailable
111
+ binding is a gate violation rather than a clean result.
112
+
113
+ Approval identity is separate from attestation text. `--approval-by` is
114
+ accepted as a verified approval only when it equals a non-empty
115
+ `CANON_ACTOR` environment identity and `--approval-role human` is supplied.
116
+ The record persists that verification bit for replay; arbitrary caller
117
+ strings, empty identities, and `agent` roles never satisfy a risk tier.
118
+ Malformed `risk_tiers` sections produce a stable `uncovered-cell` policy
119
+ violation (`risk_tiers policy-invalid: ...`) instead of degrading to an
120
+ empty clean map.
121
+
122
+ ## `canon gate task <task_id> [--repo <dir>]`
123
+
124
+ The evidence-gated task checkbox flip. Resolves `<task_id>`
125
+ (`<change_id>#<n>`) through `canon.yaml`'s `plans:` sources — the first
126
+ configured dialect whose `PlanWriteBack` locates the task's document
127
+ wins, so the flip is plan-dialect agnostic (`openspec` change dirs,
128
+ `superpowers` plan docs). A repo with no `plans:` section falls back to
129
+ the compat default `[{ dialect: openspec, root: <repo> }]`; no source
130
+ locating the task at all is a loud failure naming every source consulted.
131
+ It requires
132
+ a matching non-`Divergent` evidence record, and flips `- [ ]` → `- [x]`
133
+ with an appended evidence note ONLY on a clean check. Every other path —
134
+ missing evidence, a `Divergent` verdict, a fabricated note — leaves the
135
+ row byte-unchanged and exits `1` with the blocking violation on stderr.
136
+ An already-`[x]` row is an idempotent no-op (exit `0`). An unknown
137
+ `task_id` is reported (exit `1`).
138
+
139
+ ```bash
140
+ canon gate task my-change#5.2 --repo .
141
+ ```
142
+
143
+ This is canon's own authority for the checkbox grammar — always flip
144
+ through `canon gate task`, never by hand.
145
+
146
+ ## `canon gate promote [--repo <dir>] [--dry-run]`
147
+
148
+ Staging → committed: every well-formed record under
149
+ `.canon/ledger/_staging/` is re-validated with the SAME checks the gate
150
+ applies, assigned a monotonic per-(role, surface) `run_seq` (gap-free
151
+ within one invocation, continuing from the committed max), and moved into
152
+ the committed ledger. A malformed or unpartitionable candidate is refused
153
+ — exit `1`, no `run_seq` consumed, the file left in place. `--dry-run`
154
+ prints the plan (target path + assigned `run_seq` per candidate) without
155
+ writing or deleting.
156
+
157
+ ```bash
158
+ canon gate promote --repo . # land every staged candidate
159
+ canon gate promote --repo . --dry-run # preview only
160
+ ```
161
+
162
+ ## `canon gate install-hooks [--repo] [--event] [--matcher] [--command] [--timeout]`
163
+
164
+ Idempotent, diff-only hook-seam installation: merges one
165
+ `{matcher?, hooks: [{type: "command", command, timeout}]}` entry into
166
+ BOTH `<repo>/.claude/settings.json` and `<repo>/.codex/hooks.json` —
167
+ additive only, never touching an existing entry in the same matcher
168
+ group. Running it twice with no manual edits between reports "no diff"
169
+ and writes nothing. When neither file already carries a `canon
170
+ gate`-invoking command, it ALSO emits
171
+ `<repo>/.canon/scripts/canon-gate-pre-commit.sh` (advisory by default — set
172
+ `CANON_GATE_ADVISORY=0` to make it block the commit on a failing gate).
173
+
174
+ ```bash
175
+ canon gate install-hooks --repo .
176
+ # non-default wiring:
177
+ canon gate install-hooks --repo . --event PreToolUse --matcher Edit --command "canon gate check" --timeout 30
178
+ ```
179
+
180
+ Prefer this over hand-editing `settings.json`/`hooks.json`.
181
+
182
+ ## `canon gate selftest`
183
+
184
+ Runs the shipped fixture corpus — one fixture per failure class, each a
185
+ deliberately broken corpus proving that class fires and ONLY that class
186
+ (both under-detection and over-triggering fail the run). Takes no
187
+ `--repo`; self-contained. Run it before trusting any other `canon gate
188
+ check` run's green.
189
+
190
+ ```bash
191
+ canon gate selftest
192
+ ```
193
+
194
+ ## Reading a violation
195
+
196
+ Every printed line is `<failure-class> <subject> — <detail>`. `<subject>`
197
+ is the artifact's own join identity (`task_id` preferred, then
198
+ `scenario_id`, then `run_id`) — grep the ledger or `tasks.md` for it
199
+ directly, never guess.