@journeykit/canon 0.15.0 → 0.17.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.
@@ -20,11 +20,13 @@ authored in one place:
20
20
  - **Changes**: `change_ids` on the Subject, appended by `canon subject
21
21
  adopt`.
22
22
  - **Scenarios**: the `@subject:<id>` tag on each Gherkin scenario,
23
- indexed onto `Scenario.subject_id` by `canon inventory sync`. The
24
- Subject record carries no scenario list — the spec corpus is the only
25
- source, and the Subjects report panel, `spec_coverage.scope`, and the
26
- shipped gate all read it from there. To link a scenario, tag it and
27
- re-sync; there is no subject-side command for it.
23
+ indexed onto `Scenario.subject_id` by the scenario index (rebuilt by
24
+ `canon inventory sync`, and refreshed by every command that reads it).
25
+ The Subject record carries no scenario list — the spec corpus is the
26
+ only source, and the Subjects report panel, `spec_coverage.scope`, and
27
+ the shipped gate all read it from there. To link a scenario, tag it;
28
+ the next `canon status` or `canon gate check` indexes the tag. There is
29
+ no subject-side command for it.
28
30
 
29
31
  ## The 4-step loop
30
32
 
@@ -81,9 +83,10 @@ canon retrieve --role dev --domain dev --subject subject-domain-loop --k 5
81
83
  ### 3. Work → pin every artifact to `subject_id`
82
84
 
83
85
  - **Scenarios**: `canon scenario new <tag> --subject <subject-id>` emits
84
- the `@subject:<subject-id>` tag; `canon inventory sync` maps the tag
85
- onto the scenario's `subject_id`. A malformed/absent tag leaves it
86
- unset (fail-soft), never an error. See `canon-authoring`.
86
+ the `@subject:<subject-id>` tag; the scenario index maps the tag onto
87
+ the scenario's `subject_id` the next time a command reads it (no sync
88
+ step; see `canon-inventory`). A malformed/absent tag leaves it unset
89
+ (fail-soft), never an error. See `canon-authoring`.
87
90
  - **Changes**: adopt imported plan changes under the Subject (see
88
91
  [Adopt flow](#adopt-flow)).
89
92
  - **Reviews / evidence / divergences**: authored as usual (`canon review
@@ -145,6 +148,48 @@ empty set; so is one whose `scenario` records route away from the
145
148
  gate's rung. Each refusal prints by failure class (`uncovered-cell`),
146
149
  exits 1, and the status stays `verifying`. `retired` is not gated.
147
150
 
151
+ **The brief (0.16, 0.17):** `verifying → shipped` also refuses where a brief
152
+ exists and is not approved, read exactly as `canon status` reads it (one
153
+ shared check, so status says `ship` only when this passes): the subject's
154
+ newest adopted change has a canon `.canon/changes/<change>/brief.md`
155
+ with no `Approved:` line outside HTML comments and code fences, or has no
156
+ canon `brief.md` yet but comes from an external openspec plan with a
157
+ `proposal.md` under the plan sources. It runs with or without a policy.
158
+ The refusal is an `unreviewed-promotion` naming the subject, and says
159
+ what to do:
160
+
161
+ ```
162
+ canon subject status: unreviewed-promotion demo-subject — verifying → shipped: brief not approved — .canon/changes/c-demo/brief.md has no `Approved:` line; a human must answer the brief, and only from that answer the agent will record their words as `Approved: <who> <when> — "<their words>"` in .canon/changes/c-demo/brief.md
163
+ canon subject status: refused — get the human's answer to the brief and record it as its `Approved:` line, or pass --override-reason <text> to move anyway; the waiver is recorded on the subject
164
+ ```
165
+
166
+ For an external change with no canon brief yet, the reason reads
167
+ ``.canon/changes/<c>/brief.md does not exist yet: change `<c>` comes from
168
+ the external plan <proposal>, which canon reads and never edits, so its
169
+ approval lives in that canon file`` and the agent will ``create
170
+ .canon/changes/<c>/brief.md holding a `Source: <proposal>` line and
171
+ record their words there as `Approved: <who> <when> — "<their words>"` ``.
172
+
173
+ Never write the line without the human's answer. When there is no brief to
174
+ answer (no adopted change, or a newest change with neither a canon
175
+ `brief.md` nor an external `proposal.md`), the move is not refused: it
176
+ prints ``canon subject status: advisory: <id>'s brief is not approved —
177
+ its newest adopted change `<c>` has no brief (no
178
+ .canon/changes/<c>/brief.md, no openspec proposal.md), so there is no
179
+ brief to answer and shipping is not refused`` (or `— <id> adopts no
180
+ change, so …`), and status shows the brief as not approved, as an
181
+ advisory. `subjects[].brief.enforced` in `canon status --json` says
182
+ which case applies. A brief that says the human ships changes who runs
183
+ the transition, not the check.
184
+
185
+ This refusal is a backstop, and so is the evidence one: shipping is not
186
+ the first thing an unapproved brief stops. The agent writes the brief,
187
+ then stops and reports it, and nothing is built or attested for the
188
+ subject until the human answers. `canon evidence add` refuses (exit 1,
189
+ nothing staged) a scenario whose `@subject` owner, or a task whose change
190
+ an adopting subject, has an enforced, unapproved brief, through the same
191
+ check; see `canon-gate`.
192
+
148
193
  ### The review guard (`spec_coverage.require_review`, opt-in)
149
194
 
150
195
  When `.canon/policy.yaml` sets `spec_coverage.require_review` (see
@@ -172,9 +217,11 @@ canon subject status demo-subject verifying \
172
217
  --override-reason "reviewer out until Monday" --actor-id lead
173
218
  ```
174
219
 
175
- `--override-reason "<one line>"` waives ONLY `unreviewed-promotion` and
176
- `open-blocker` — never a ship-gate verdict or case gap, never unreadable
177
- routing. It prints the `waived ...` lines and `override recorded by
220
+ `--override-reason "<one line>"` waives ONLY `unreviewed-promotion` (the
221
+ review gaps and the brief check) and `open-blocker` — never a ship-gate
222
+ verdict or case gap, never unreadable routing. A waived brief is recorded
223
+ as `{ class: unreviewed-promotion, subject: <subject id> }`. It prints
224
+ the `waived ...` lines and `override recorded by
178
225
  `lead` for the N violation(s) above: <reason>`, and records on the new
179
226
  Subject record exactly the violations it let through:
180
227
 
@@ -192,13 +239,14 @@ subject's next status write, so it never outlives the status it was
192
239
  granted for. While it stands, `canon gate check` lists the recorded gaps
193
240
  as advisories instead of failing; a gap it did not record (a blocker
194
241
  raised later, a scenario tagged later) still fails the gate. Without
195
- `require_review`, nothing is printed and nothing changes.
242
+ `require_review`, nothing is printed and nothing changes, except that
243
+ `shipped` still runs the brief check above.
196
244
 
197
245
  ## Adopt flow
198
246
 
199
- Planning docs become change/task records via `canon ingest plans` (see
200
- `canon-plan-import`), but that stops at import. `canon subject adopt`
201
- lifts an imported change into a managed Subject:
247
+ Planning docs written outside canon become change/task records via
248
+ `canon ingest plans` (see `canon-plan-import`), but that stops at import.
249
+ `canon subject adopt` lifts an imported change into a managed Subject:
202
250
 
203
251
  ```bash
204
252
  canon ingest plans --repo . # import → change/task rows
@@ -206,6 +254,13 @@ canon subject adopt subject-domain-loop-plan \
206
254
  --subject subject-domain-loop # link change → subject
207
255
  ```
208
256
 
257
+ A change made with `canon change new` (below) is recorded and adopted
258
+ already, and editing its `tasks.md` needs no import either:
259
+ `canon evidence add --task` and `canon gate task` read the file as it is,
260
+ and a flip re-imports that change itself. Run `canon ingest plans` for a
261
+ plan written outside canon, or when `canon query --kind task` must list
262
+ rows nobody has flipped yet.
263
+
209
264
  `adopt` stamps the change's `subject_id` and adds it to the Subject's
210
265
  `change_ids`, so `canon query --kind change --change-id …` and `canon
211
266
  query --kind subject` agree on the link from both ends. It writes only
@@ -222,21 +277,33 @@ it.
222
277
 
223
278
  ### Starting a new change: `canon change new`
224
279
 
225
- For a change that does not exist yet, one command scaffolds it and
226
- records it adopted:
280
+ For a change that does not exist yet, one command writes it and records
281
+ it adopted:
227
282
 
228
283
  ```bash
229
284
  canon change new add-login --subject auth --title "Add login"
230
285
  ```
231
286
 
232
- - **Writes** `openspec/changes/<slug>/proposal.md` (the title is its
233
- `## Why`, which imports as the change summary) and `tasks.md` (no rows
234
- yet, so the change imports as `proposed`), under the repo's first
235
- `openspec` plans source. `canon init` configures that source
236
- (`{dialect: openspec, root: .}`), so `canon.yaml` needs no edit.
237
- - **Reads** the files with the same `openspec` adapter `canon ingest
287
+ ```
288
+ canon change new: wrote .canon/changes/add-login/brief.md
289
+ canon change new: wrote .canon/changes/add-login/tasks.md
290
+ canon change new: recorded change `add-login` adopted into subject `auth` — written directly; nothing to promote
291
+ ```
292
+
293
+ - **Writes** canon's own change dir, `.canon/changes/<slug>/`:
294
+ `brief.md` (`# <title>`, `## Why` holding the title, which imports as
295
+ the change summary, `## What Changes` with a comment saying what goes
296
+ there, and a comment naming where the human's `Approved:` line goes,
297
+ so the file starts unapproved) and `tasks.md` (no rows yet, so the
298
+ change imports as `proposed`; the openspec checkbox row grammar).
299
+ `.canon/changes/` is canon's native plan source: every plan reader
300
+ reads it with no `plans:` entry, so `canon.yaml` needs no edit and no
301
+ external plans source is needed (see `canon-plan-import`).
302
+ - **Reads** the files with the native `canon` plan adapter `canon ingest
238
303
  plans` uses, then **records** the change already adopted, through the
239
- same write as `canon subject adopt`.
304
+ same write as `canon subject adopt`. Its records carry the actor
305
+ `canon-plan-import-canon` (canon-written); an external openspec
306
+ change's carry `canon-plan-import-openspec`.
240
307
  - **What a failure leaves behind.** The files are staged under `.canon/`
241
308
  and the change dir is moved into place only once they parse. A failure
242
309
  before any record is written (staging, publishing the dir, or the
@@ -248,12 +315,22 @@ canon change new add-login --subject auth --title "Add login"
248
315
  plans && canon subject adopt <slug> --subject <id>`, which completes
249
316
  the link and is safe to rerun.
250
317
  - **Refuses** with exit `2`, writing nothing, when the subject does not
251
- exist, when the slug already has a change dir (active or archived) or a
252
- `change` record, or when `canon.yaml` has no `openspec` plans source.
253
-
254
- Add task rows to `tasks.md` as `- [ ] <n> <title>`, rerun `canon ingest
255
- plans`, and flip each with `canon gate task <slug>#<n>` once evidence
256
- exists.
318
+ exist, or when the slug already has a `.canon/changes/<slug>` dir
319
+ (active or under `.canon/changes/archive/`) or a `change` record.
320
+
321
+ Add task rows to `tasks.md` as `- [ ] <n> <title>`, and flip each with
322
+ `canon gate task <slug>#<n>` once evidence exists. `canon evidence add
323
+ --task` and the flip read `tasks.md` as it is, so a new row needs no
324
+ `canon ingest plans` run.
325
+
326
+ A change from an external plan (an openspec change listed under
327
+ `plans.sources`, imported by `canon ingest plans` and adopted with
328
+ `canon subject adopt`) gets its brief as a canon file too:
329
+ `.canon/changes/<change>/brief.md` holding a `Source: <path>` line that
330
+ points at the external document and, once the human answers, the
331
+ `Approved:` line. Canon never edits the external document. Such a
332
+ pointer `brief.md` is not a change of its own: the native reader skips
333
+ it, so the external source keeps the change, its summary and its tasks.
257
334
 
258
335
  ## Where are we: `canon status`
259
336
 
@@ -286,7 +363,36 @@ uses, so status and the gate cannot disagree about a gap:
286
363
  stale or whose latest reviews are all `indeterminate`/`unavailable`
287
364
  (see `canon-review`), and the `canon review add` step names both. With
288
365
  `require_review` on, a `warning:` line counts the in-effect reviews that
289
- predate review pinning (0.15).
366
+ predate review pinning (0.15), checked against the commit each pinned:
367
+ current, stale (named) and undecidable.
368
+ - **Brief** (0.16, 0.17) — for each `building`, `verifying` or `shipped`
369
+ subject, and each `proposed` subject whose newest adopted change has a
370
+ brief (a canon `brief.md`, or an external `proposal.md`), a `brief:`
371
+ line under it says whether the brief is approved:
372
+ `brief: approved in <brief> — <the recorded words>`, or
373
+ `brief: not approved (…)`. Approval is an `Approved:` line in
374
+ `.canon/changes/<change>/brief.md` for the subject's newest adopted
375
+ change (the last one adopted; a live dir before an archived one), and
376
+ only there: a line that starts with `Approved:` (indent allowed), says
377
+ something after it, and sits outside HTML comments (`<!-- … -->`,
378
+ multi-line included) and fenced code blocks (```` ``` ```` or `~~~`),
379
+ written by the agent once the human answers, as
380
+ `Approved: <who> <when> — "<the human's words>"`. Fixes and follow-ups
381
+ live as tasks in the approved change, so a newly adopted change is a
382
+ new scope: an older change's approval does not cover it. A change from
383
+ an external plan has its brief in the same canon file, with a
384
+ `Source: <path>` line pointing at the external document; an `Approved:`
385
+ line inside an external `proposal.md` is not read. Before that canon
386
+ file exists, an external openspec `proposal.md` under the plan sources
387
+ (the first source holding the change; with no `plans:` section, the
388
+ `openspec` source rooted at the repo that `canon gate task` also falls
389
+ back to) still makes the brief one to answer, and the line reads
390
+ ``brief: not approved (.canon/changes/<c>/brief.md does not exist yet:
391
+ change `<c>` comes from the external plan <proposal>, which canon reads
392
+ and never edits, so its approval lives in that canon file)``. With a
393
+ canon brief and no line it reads
394
+ ``brief: not approved (<brief> has no `Approved:` line)``.
395
+ There is no record kind for it; the file is the record.
290
396
  - **Unowned** — scenarios with no `@subject:` tag.
291
397
  - **Audits** — how many scenarios are sampled for
292
398
  `spec_coverage.require_audit`, confirmed, owed (no audit or
@@ -296,23 +402,62 @@ uses, so status and the gate cannot disagree about a gap:
296
402
  one).
297
403
  - **Dropped bindings** — `dropped bindings (N):` lists every recorded
298
404
  `--drop-attachment` (scenario, paths, reason); `drops[]` in `--json`.
299
- - **`next:`** — at most five commands, each with a one-line reason; the
405
+ - **Brief overrides** — a `warning:` line per evidence record attested
406
+ with `canon evidence add --override-reason` while its subject's brief
407
+ was not approved: what it attests, who, when, the subject and the
408
+ reason (see `canon-gate`).
409
+ - **`next:`** — at most five steps, each with a one-line reason; the
300
410
  rest are counted. Rules, in order: no `canon.yaml` → `canon init`; an
301
411
  unreadable one → `canon init --check-config`; a scenario tagged with a
302
412
  subject that has no record → `canon subject new <that id>`; no subject
303
- at all → `canon subject new`; subjects but no scenario records →
304
- `canon inventory sync`. Then per subject, verifying first, then
305
- building, specced, proposed, shipped: no scenario → `canon scenario new
306
- … --subject <id>`; a missing case → `canon scenario new <surface>.<next
307
- nn> … --case <case>`; (building and later) an unevidenced or divergent
308
- scenario → `canon evidence add --scenario-id …`; an open blocker →
309
- `canon finding close …`; a due, unreviewed, evidenced scenario →
310
- `canon review add …` from another session; none of these →
311
- `canon subject status <id> <next status>`. Last, unowned scenarios →
312
- tag them and `canon inventory sync`. With more than one `specs.roots[]`
313
- entry, every suggested `canon scenario new` carries `--project`: the
314
- owning root for a missing case, `<root-id>` (the reason lists the
315
- roots) for a subject that owns no scenario yet.
413
+ at all → `canon subject new`; subjects and `.feature` scenarios but no
414
+ scenario records → `canon inventory sync` (status refreshes the index
415
+ itself first, so this means the refresh refused on a format violation
416
+ or a duplicate id, which the sync names). Then per subject, verifying
417
+ first, then building, specced, proposed, shipped: an unapproved brief
418
+ (building and later, and proposed once its change has a brief)
419
+ → the human step below, which is then the subject's
420
+ only step; no scenario → `canon scenario new … --subject <id>` (and, when no
421
+ `.feature` file holds a scenario yet, `canon feature new` first, unless
422
+ every subject waits on its brief); a missing case → `canon scenario new
423
+ <surface>.<next nn> … --case <case>`; (building and later) an
424
+ unevidenced or divergent scenario → `canon evidence add --scenario-id
425
+ …` (its reason adds "then `canon gate promote`" only under
426
+ `write.promote: manual`); an open blocker → `canon finding close …`; a
427
+ due, unreviewed, evidenced scenario → `canon review add …` from another
428
+ session; none of these → `canon subject status <id> <next status>`.
429
+ Last, unowned scenarios → `canon status`, after tagging them in their
430
+ `.feature` files (status re-indexes the edit; no sync step). With more
431
+ than one `specs.roots[]` entry, every suggested `canon scenario new`
432
+ carries `--project`: the owning root for a missing case, `<root-id>`
433
+ (the reason lists the roots) for a subject that owns no scenario yet.
434
+ - **The one human step.** Every step is the agent's to run except one:
435
+ a subject whose brief is enforced (its newest change's canon
436
+ `brief.md` has no `Approved:` line, or it has no canon `brief.md` yet
437
+ and comes from an external openspec `proposal.md`) gets
438
+ `human: stop and report the brief in <brief>; build and attest nothing for <id> until the human answers (one answer approves brief and policy)`,
439
+ where `<brief>` is the canon `brief.md`, or the external `proposal.md`
440
+ when no canon brief exists yet. Its reason reads ``<id>: <why
441
+ unanswered> (an older change's approval does not cover a new scope),
442
+ and the approval is never assumed: `canon evidence add` refuses <id>'s
443
+ scenarios and tasks until it is recorded; once the human answers,
444
+ <record> and run `canon policy approve --by <the human> --note "<their
445
+ words>"` ``, where `<record>` is ``record their words as `Approved: <who>
446
+ <when> — "<their words>"` in <brief>``, or, for an external change,
447
+ ``create <brief> holding a `Source: <proposal>` line and record their
448
+ words there as `Approved: <who> <when> — "<their words>"` ``.
449
+ It is the subject's only step, whatever its status: nothing suggests
450
+ `canon feature new`, `canon scenario new`, `canon evidence add`, a
451
+ review or a move-on for it before the human answers, and
452
+ `canon subject status <id> shipped` refuses on the same check. The
453
+ approval is never assumed. A subject with no brief to answer gets no human step;
454
+ its `brief:` line says
455
+ `not approved (no adopted change; advisory, nothing to approve yet)` or
456
+ ``not approved (change `<id>` has no brief; advisory, nothing to
457
+ approve yet)`` and nothing is withheld (a `proposed` subject with no
458
+ brief, and a `specced` subject, show no `brief:` line at all). Once nothing blocks, a
459
+ ship-ready verifying subject gets the agent's own
460
+ `canon subject status <id> shipped`; status never asks a human to ship.
316
461
 
317
462
  Status fails closed rather than suggest progress the gate would refuse:
318
463
 
@@ -330,19 +475,24 @@ Status fails closed rather than suggest progress the gate would refuse:
330
475
 
331
476
  ```json
332
477
  {
333
- "statusVersion": 1,
334
- "canonVersion": "0.14.0",
478
+ "statusVersion": 3,
479
+ "canonVersion": "0.17.0",
335
480
  "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 } } },
336
481
  "warnings": [],
337
482
  "subjects": [
338
483
  { "id": "cart", "title": "Cart", "status": "building", "scenarios": 2, "excluded": 0, "evidenced": 2, "divergent": 0,
339
- "reviewed": 1, "staleReviews": 0, "unsignedReviews": 0, "reviewDue": true, "openBlockers": 1, "missingCases": [] }
484
+ "reviewed": 1, "staleReviews": 0, "unsignedReviews": 0, "reviewDue": true, "openBlockers": 1, "missingCases": [],
485
+ "brief": { "approved": true, "enforced": false, "change": "c-cart", "brief": ".canon/changes/c-cart/brief.md", "briefExists": true, "approval": "Journey 2026-10-11 — \"approved\"" } },
486
+ { "id": "promo", "title": "Promo", "status": "verifying", "scenarios": 1, "excluded": 0, "evidenced": 1, "divergent": 0,
487
+ "reviewed": 1, "staleReviews": 0, "unsignedReviews": 0, "reviewDue": true, "openBlockers": 0, "missingCases": [],
488
+ "brief": { "approved": false, "enforced": true, "change": "c-promo", "brief": ".canon/changes/c-promo/brief.md", "briefExists": false, "source": "openspec/changes/c-promo/proposal.md" } }
340
489
  ],
341
490
  "unowned": ["cart.promo.01"],
342
491
  "next": [
343
- { "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" },
344
- { "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; …" },
345
- { "command": "canon inventory sync", "why": "1 scenario(s) carry no @subject tag (first: cart.promo.01); …" }
492
+ { "command": "stop and report the brief in openspec/changes/c-promo/proposal.md; build and attest nothing for promo until the human answers (one answer approves brief and policy)", "why": "promo: .canon/changes/c-promo/brief.md does not exist yet: change `c-promo` comes from the external plan openspec/changes/c-promo/proposal.md, which canon reads and never edits, so its approval lives in that canon file (an older change's approval does not cover a new scope), and the approval is never assumed: … once the human answers, create .canon/changes/c-promo/brief.md holding a `Source: openspec/changes/c-promo/proposal.md` line and record their words there as `Approved: <who> <when> — \"<their words>\"` and run `canon policy approve --by <the human> --note \"<their words>\"`", "actor": "human" },
493
+ { "command": "canon finding close --change-id c-cart --round 1 --seq 1 --disposition fixed --resolution-sha <sha> --actor-id <unit> --session-id <session>", "why": "cart: 1 open blocker finding(s) (first: …); fix it, commit, then close it", "actor": "agent" },
494
+ { "command": "canon review add --project-id root --scenario-id cart.add.02 --reviewer <reviewer> --actor-id <reviewer> --session-id <review-session> --role reviewer --pin <sha> --original-spec-ref \"<feature file>\"", "why": "cart: 1 of 2 scenario(s) lack a qualifying review; …", "actor": "agent" },
495
+ { "command": "canon status", "why": "1 scenario(s) carry no @subject tag (first: cart.promo.01); add `@subject:<id>` above each in its .feature file, then check again: …", "actor": "agent" }
346
496
  ],
347
497
  "nextOmitted": 0
348
498
  }
@@ -351,9 +501,30 @@ Status fails closed rather than suggest progress the gate would refuse:
351
501
  Each `missingCases` entry is `{ "projectId", "surface", "case" }`: the
352
502
  spec root, the `<area>.<surface>`, and the required case it lacks.
353
503
 
504
+ `brief` is present for `building`, `verifying` and `shipped` subjects,
505
+ and for a `proposed` subject whose newest adopted change has a brief (a
506
+ canon `brief.md` or an external `proposal.md`). `change` is the newest
507
+ adopted change (omitted when the subject adopts none); `brief` its canon
508
+ `.canon/changes/<change>/brief.md`, the one file the approval is read
509
+ from (omitted when the change has no brief to answer); `briefExists`
510
+ (always present) whether that file exists (`false` for an external
511
+ change whose canon brief is still to be created); `source` the external
512
+ plan document the change comes from (the canon brief's `Source:` line,
513
+ or, before that file exists, the external `proposal.md`; omitted for a
514
+ canon-written change); `approval` the text after `Approved:`; and
515
+ `enforced` whether the missing approval blocks (a brief exists to
516
+ answer). The 0.16 `proposal` field is gone.
517
+
518
+ Each `next` entry's `actor` is `agent` (its `command` is a canon command
519
+ line) or `human` (its `command` says what to get from the human).
520
+
354
521
  `policy.spec_coverage` has the shape `canon context --json` prints
355
522
  (`null` when absent, `{"invalid": …}` when unusable). `statusVersion`
356
- changes only when a field changes meaning or is removed.
523
+ changes only when a field changes meaning or is removed. It became `2`
524
+ in 0.16, when `next[].command` stopped always being a command line
525
+ (`actor` tells which), and `brief` was added alongside. It is `3` since
526
+ 0.17, when `brief.proposal` was removed: the canon `brief.md` is
527
+ `brief.brief`, with `briefExists` and `source`.
357
528
 
358
529
  `subject new`, `adopt` and `status` write their records directly; the
359
530
  human-readable success line ends `— written directly; nothing to
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@journeykit/canon",
3
- "version": "0.15.0",
3
+ "version": "0.17.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.15.0",
34
- "@journeykit/canon-core-linux-x64": "0.15.0",
35
- "@journeykit/canon-core-win32-x64": "0.15.0"
33
+ "@journeykit/canon-core-darwin-arm64": "0.17.0",
34
+ "@journeykit/canon-core-linux-x64": "0.17.0",
35
+ "@journeykit/canon-core-win32-x64": "0.17.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^20.0.0",