wowbagger 0.1.0-alpha.1 → 0.1.0-alpha.11

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.
Files changed (77) hide show
  1. package/CHANGELOG.md +1116 -0
  2. package/LICENSE +21 -201
  3. package/README.md +1032 -190
  4. package/adapters/claude-code/wowbagger-adapter.json +4 -4
  5. package/adapters/codex/wowbagger-adapter.json +1 -1
  6. package/adapters/opencode/wowbagger-adapter.json +1 -1
  7. package/assets/wowbagger-v1-more-whimsical.jpg +0 -0
  8. package/assets/wowbagger-v2-less-whimsical.jpg +0 -0
  9. package/assets/wowbagger-v3-herding-agent-cats.jpg +0 -0
  10. package/assets/wowbagger-v4-robot-agent-herding.jpg +0 -0
  11. package/assets/wowbagger-v5-typing-cats-circuit-staff.jpg +0 -0
  12. package/docs/adapter-contract.md +1572 -0
  13. package/docs/host-contract.md +287 -0
  14. package/docs/mutation-contract.md +2949 -0
  15. package/docs/work-claim-contract.md +1179 -0
  16. package/package.json +29 -3
  17. package/schemas/bare-ready-result.json +27 -0
  18. package/schemas/bare-validation-result.json +23 -0
  19. package/schemas/common.json +470 -0
  20. package/schemas/core-capabilities-response.json +346 -0
  21. package/schemas/core-envelope.json +141 -0
  22. package/schemas/core-inspect-response.json +37 -0
  23. package/schemas/core-inspect-workbench-response.json +340 -0
  24. package/schemas/core-list-error-response.json +242 -0
  25. package/schemas/core-list-query.json +98 -0
  26. package/schemas/core-list-response.json +140 -0
  27. package/schemas/core-report-response.json +379 -0
  28. package/schemas/core-transition-error-response.json +852 -0
  29. package/schemas/core-transition-request.json +47 -0
  30. package/schemas/core-transition-response.json +49 -0
  31. package/schemas/index.json +108 -0
  32. package/schemas/ledger-mutation-refusal.json +288 -0
  33. package/schemas/report-config-v1.json +140 -0
  34. package/schemas/report-config-v2.json +242 -0
  35. package/scripts/migrate-schema-2.js +7 -0
  36. package/skills/wowbagger/SKILL.md +617 -23
  37. package/src/adapter/core-probe.js +98 -10
  38. package/src/adapter/entrypoint-main.js +97 -17
  39. package/src/adapter/invoke.js +43 -24
  40. package/src/adapter/process-outcome.js +306 -25
  41. package/src/claim-capabilities.js +10 -3
  42. package/src/claim-coordinator.js +161 -10
  43. package/src/claim-journal.js +153 -11
  44. package/src/claim-prospective.js +124 -0
  45. package/src/claim-publication.js +793 -151
  46. package/src/claim-request.js +20 -0
  47. package/src/claim-store.js +40 -9
  48. package/src/claim-sync.js +66 -0
  49. package/src/cli.js +993 -39
  50. package/src/extension-provision.js +33 -0
  51. package/src/extensions.js +141 -0
  52. package/src/git-autocommit.js +1067 -0
  53. package/src/git-reconciliation.js +211 -15
  54. package/src/instrumentation.js +75 -0
  55. package/src/launch.js +52 -0
  56. package/src/ledger.js +163 -2
  57. package/src/lifecycle.js +153 -0
  58. package/src/limits.js +50 -0
  59. package/src/list.js +365 -0
  60. package/src/mutation.js +815 -192
  61. package/src/projection.js +39 -0
  62. package/src/ready.js +64 -38
  63. package/src/report-attention.js +102 -0
  64. package/src/report-evidence.js +303 -0
  65. package/src/report-graph.js +465 -0
  66. package/src/report-html.js +485 -0
  67. package/src/report-markdown.js +171 -0
  68. package/src/report-sequencing.js +285 -0
  69. package/src/report-svg.js +342 -0
  70. package/src/report-view.js +189 -0
  71. package/src/report.js +566 -0
  72. package/src/schema-migration.js +28 -4
  73. package/src/validate.js +19 -6
  74. package/src/workbench.js +117 -0
  75. package/vendor/3d-force-graph/3d-force-graph.min.js +5 -0
  76. package/vendor/3d-force-graph/LICENSE +21 -0
  77. package/vendor/3d-force-graph/VERSIONS.json +24 -0
package/CHANGELOG.md CHANGED
@@ -7,6 +7,1122 @@ consolidation. The first tagged release inherits this file.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 0.1.0-alpha.11 - 2026-08-27
11
+
12
+ ### Fixed
13
+
14
+ - **Uncommitted sibling revisions keep target-scoped reconciliation safe.**
15
+ Previously authorized predecessor bytes now identify an in-protocol sibling
16
+ window without turning genuine hand edits or same-branch regressions into
17
+ nonblocking findings.
18
+ Restoring earlier authorized bytes in the same working tree remains
19
+ `unauthorized-revision` when the current branch owns the expected revision.
20
+ Detached `HEAD` uses its reachable history for the same current-owner guard,
21
+ so it cannot turn that regression into advisory sibling synchronization.
22
+ When Git proves that a sibling ref owns the expected revision, the finding
23
+ retains that `owner_ref` and `owner_commit` instead of reporting the owner as
24
+ unavailable.
25
+ - **Journal-owning auto-commit rebuilds its derived reconciliation log.** Claim
26
+ decisions may dirty that tracked projection; patch, transition,
27
+ parent-migrate, snooze, and publish-claimed now validate and commit the rebuilt
28
+ log while every foreign dirty ledger path and create still refuse.
29
+ - **Claim-verification failures preserve their cause.** Preflight and
30
+ post-commit errors carry the underlying claim verification code and reason;
31
+ claim-store lock contention is retryable while persistent reconciliation is
32
+ not.
33
+ `mutation-finalize` recovery now carries the identical diagnostics without
34
+ inventing a `findings` member.
35
+ - **Auto-commit uses target scope before and after committing.** Unrelated
36
+ synchronization findings no longer turn a successfully committed mutation
37
+ into a reported post-commit failure; target-blocking findings remain fatal and
38
+ visible through claim-verify.
39
+
40
+ ### Changed
41
+
42
+ - **Parent migration and snooze now have complete contract guidance.** The
43
+ contract and installed skill document their requests, CAS and date rules,
44
+ response domains, auto-commit behavior, and legacy journal fence-family
45
+ semantics. Parent-migrate help no longer invents a live-item restriction.
46
+ - **Parent and snooze fields are documented as dedicated mutations, not
47
+ create-once values.** Existing items can be repointed to or from an epic with
48
+ `parent-migrate`, and `snooze` can set or clear `snoozed_until`; `kind` and
49
+ `provenance` remain genuinely create-once.
50
+ - **Release numbering preserves the unpublished alpha.10 cut.** Alpha.10 was
51
+ cut and tagged but never published to npm. This release advances to alpha.11
52
+ instead of moving that tag, so tag identity remains immutable and the
53
+ registry history honestly skips alpha.10.
54
+
55
+ ### Known limitations
56
+
57
+ - **Parent-migrate and snooze lock owners lose diagnostic detail.** Their real
58
+ lock still refuses every concurrent mutation, but the refusal currently
59
+ reports `owner: null` with `owner_diagnostic: "invalid-shape"`. Item #174
60
+ tracks restoring those owner details; mutual exclusion is unaffected.
61
+ - **Ownership classification still needs one consolidated topology audit.**
62
+ Items #173, #176, and #177 close every case identified so far. Item #178
63
+ remains open to audit the topology as one matrix because each point fix
64
+ revealed an adjacent gap.
65
+
66
+ ## 0.1.0-alpha.10 - 2026-08-26
67
+
68
+ ### Fixed
69
+
70
+ - **Cross-worktree reconciliation is target-scoped.** An unrelated item
71
+ mutation no longer refuses because another worktree has a committed revision.
72
+ When synchronization is required, the finding names the owning reference.
73
+ - **Fresh clones recover committed claim history.** Reconciliation hydrates an
74
+ empty local claim journal from the committed reconciliation log, while
75
+ lock-free claim reads project that history without writing. Invalid or
76
+ over-capacity committed sequences fail closed.
77
+ - **Auto-commit recovery handles refusal and no-op paths.** Refused preflight
78
+ checks no longer rewrite the tracked reconciliation log. The first
79
+ post-provision create works, byte-identical mutations commit only their log,
80
+ and `mutation-finalize` accepts the corresponding log-only recovery after
81
+ `HEAD` advances.
82
+ - **Parent migration and snooze expose their guarded write contracts.**
83
+ Both commands support auto-commit, report their own response domains, return
84
+ a single unchanged invalid-request envelope, and report stale parent
85
+ witnesses as conflicts.
86
+ - **Create reserves the `extensions` request container.** Item data must name
87
+ extension members directly so patch and extension declarations can address
88
+ them.
89
+ - **Auto-commit preflight reports retryability explicitly.** Only a held
90
+ auto-commit mutex is retryable.
91
+ - **CLI and release metadata stay discoverable and complete.** Help describes
92
+ claim verification and list requests, and the release-site manifest covers
93
+ historical version records.
94
+
95
+ ## 0.1.0-alpha.9 - 2026-08-23
96
+
97
+ ### Added
98
+
99
+ - **A public Wowbagger brand asset.** The repository now carries an optimized
100
+ 1024px image of Bowerick Wowbagger directing robotic agent cats at consoles
101
+ with a circuit-lit shepherd's staff. The asset is included in the npm
102
+ package for README and GitHub presentation.
103
+
104
+ ### Changed
105
+
106
+ - **Public product prose now describes the current engine.** README, npm
107
+ metadata, Claude plugin metadata, marketplace metadata, and the installed
108
+ skill now explain report sequencing dashboards, named views, facets, graph
109
+ filtering, guarded CAS mutations, claims, fencing, reconciliation, and the
110
+ separation between core and host responsibilities.
111
+ - **Agent onboarding is explicit.** The README and skill include a concise
112
+ agent TL;DR, exact core setup checks, the separate plugin/skills installer
113
+ routes, and the response-loss rule.
114
+
115
+ ## 0.1.0-alpha.8 - 2026-08-23
116
+
117
+ ### Added
118
+
119
+ - **A published launch seam for a host that cannot run a shell.** The package
120
+ now declares `exports`, and its main entry `wowbagger` exposes
121
+ `CORE_SCRIPT_PATH`, `MINIMUM_NODE_MAJOR`, and `resolveCoreLaunch(argv)`, which
122
+ returns the exact process tuple a direct-core host needs: an absolute Node
123
+ executable, an argument array whose first element is the absolute
124
+ `bin/wowbagger.js`, and `shell: false`. A host that resolves its own runtime
125
+ gets `resolveCoreLaunch(argv, { nodeExecutable })`, and a relative or bare
126
+ executable name is refused rather than left for PATH to answer. The script
127
+ path is also resolvable on its own as `wowbagger/wowbagger.js` for a host that
128
+ wants the path without importing anything. Deep imports that already worked —
129
+ `wowbagger/src/limits.js` and every other published path — keep resolving
130
+ through an explicit `"./*"` subpath. No command accepts, refuses, emits, or
131
+ writes anything different.
132
+
133
+ - **The machine contract ships as JSON Schema, not only as prose and fixtures.**
134
+ Seventeen JSON Schema 2020-12 documents under `schemas/` are published with
135
+ the package and resolvable as `wowbagger/schemas/<file>.json`, with
136
+ `schemas/index.json` naming each one's response domain and that domain's
137
+ version. They cover the core envelope and its four exact root shapes, the
138
+ capabilities envelope with every advertised limit as a constant, the two bare
139
+ results, the list query and its page and refusals, the default and workbench
140
+ inspect projections, the transition request, success, and every documented
141
+ refusal with its mutation state, the `ledger-mutation` fence refusals, report
142
+ configuration versions 1 and 2, and the report responses including a named
143
+ view. Every schema fixes its root members exactly and pins the version of its
144
+ own domain, so a core envelope refuses a namespaced refusal, a version 4
145
+ envelope, or an extra root member, and a version 1 report configuration
146
+ refuses a version 2 one. Exactness reaches the members too: an item core must
147
+ carry both relation lists, because the core view always emits them, and a
148
+ refusal raised before a commit is established pins its state to `unchanged`
149
+ rather than admitting the indeterminate state that only an interrupted write
150
+ can report. The validator is a test-only dependency; the runtime still has
151
+ exactly one dependency.
152
+
153
+ ### Documentation
154
+
155
+ - **`docs/host-contract.md` publishes the direct-core host boundary.** One
156
+ document now states what a UI plugin or other non-agent consumer needs and
157
+ what it must supply itself: the package resolution seam and the four-part
158
+ launch — Node.js 20 or later, an absolute Node executable, the absolute
159
+ `wowbagger.js`, an argument array, and `shell: false`; bounded stdin or a
160
+ host-created request file, never shell source and never inline unbounded argv
161
+ JSON; captured stdout and stderr; the owning-host path rule for a worktree, a
162
+ plain folder, direct SSH, and WSL, with no cross-runtime path guessing;
163
+ namespace-first response dispatch and the exit table; every advertised limit
164
+ with its exact value; the once-only dispatch sequence for a lost response; and
165
+ the seventeen packaged JSON Schemas by domain. It states that a missing
166
+ executable is a host-level result rather than malformed Wowbagger JSON, that
167
+ the host owns executable discovery, working directory, timeout, cancellation,
168
+ process-tree containment, stream caps, and routing, and that Wowbagger will
169
+ not add automatic transitions, mirrored ledger state, operation identity,
170
+ remote routing, or a daemon. The full `inspect` read is documented as
171
+ deliberately unbounded, with the reason. The README, the installed skill, and
172
+ `SPEC.md` section 10 point at it, and it ships in the npm package.
173
+ - **The public version and lifecycle vocabulary agrees with the runtime.**
174
+ `SPEC.md` section 10 said core mutation contracts 1 and 2 were defined and
175
+ that the runtime emitted version 2; it now says 1 through 5 and version 5. The
176
+ mutation contract's status line said versions 1, 2, and 3 with the runtime on
177
+ 3; it now says 1 through 5 and version 5. `deferred` has been a real status
178
+ since it shipped, but `SPEC.md` omitted it from the status field, the
179
+ lifecycle table, the transition table, the terminal-date invariants, the
180
+ decision-action list, and the terminal-decision table, and the mutation
181
+ contract omitted the `deferred` date from the lossless core view; all seven
182
+ now name it, along with the `resolve`, `defer`, and `undefer` decision actions
183
+ the validator has always accepted. No behaviour changed: these were prose
184
+ omissions, and the tests that guard them read the vocabularies from
185
+ `src/lifecycle.js` and `src/validate.js` rather than retyping them.
186
+ - **Response loss is a named contract instead of folklore.** The mutation
187
+ contract, the adapter contract, the README, and the installed skill now carry
188
+ the same sequence for a mutation whose response never arrived: dispatch once,
189
+ never replay, invalidate the inspected revision, reconnect, then re-read the
190
+ ledger. The mutation contract's new section 10 table separates the outcomes a
191
+ caller may act on — committed success, proven non-write, committed recovery,
192
+ unknown publication — from the two that establish nothing, a signalled or
193
+ timed-out transport and a missing envelope, and states that a later item state
194
+ never proves that the lost dispatch caused it. Adapter contract section 6.2
195
+ documents the `mutation_outcome: "unknown"` envelope and its per-command
196
+ `recovery` object exactly as the adapter emits them, and states that exit 4
197
+ `revision-conflict` is a proven non-write that is never relabelled response
198
+ loss. There is no operation ID, durable outcome store, or replay endpoint;
199
+ adding correlation requires a new contract decision. No command accepts,
200
+ refuses, emits, or writes anything different. Two conformance vectors now pin
201
+ the core's own exit-6 `write-outcome-unknown` and `post-commit-recovery-required`
202
+ envelopes at the adapter's process-outcome seam, taking the adapter vector set
203
+ to 212 assertions.
204
+ - **The exit tables state where `report` actually lands.** Both contracts filed
205
+ exit 1 as a bare-result-only condition and exit 3 as every invalid ledger,
206
+ while `report` answers an invalid ledger, an unreadable input, and a failed
207
+ publication at exit 1. The mutation contract now carries an exit 1 row naming
208
+ those codes, lists `report-config-invalid` and `report-view-not-found` in its
209
+ exit 2 row, and scopes exit 3 to every command except `report`; the host
210
+ contract's exit 1 and exit 3 conditions say the same. Two guards assert the
211
+ rows against the codes the runtime emits.
212
+
213
+ ### Changed
214
+
215
+ - **The report filters by facet groups instead of one value at a time.** The
216
+ drill-down's single-value mapped-field selects and its All/Ready/Blocked/
217
+ Ineligible buttons are gone. In their place is one group of checkbox chips per
218
+ dimension of the open set — Readiness, Status, Kind, and every configured
219
+ mapped field, so a mapped `class: bug` is a chip rather than a value hidden in
220
+ a dropdown. Values inside a group are alternatives, groups narrow each other,
221
+ and the search box is one more condition; every chip states the count it would
222
+ leave, measured against the search and the other groups but never against its
223
+ own, so two selections in one group cannot make their siblings read zero. A
224
+ visible result count, per-chip selected state, and `Clear filters` are new, and
225
+ opening a Work next or Attention row still clears whatever detached its card,
226
+ facets included.
227
+ - **The ledger graph filters by lifecycle status.** One chip group above the
228
+ stage carries the statuses the ledger holds, all selected, with `Select all`
229
+ and `Clear`. Deselecting a status drops its nodes, every link incident to one,
230
+ and their labels, reheats the layout in place, and takes a hidden node off the
231
+ hover card; the roster and node count follow the same selection, and an empty
232
+ selection draws an empty graph that says so. The legend, the WebGL-less
233
+ roster, camera interaction, and the no-fetch contract are unchanged, and
234
+ nothing here reads or writes the ledger.
235
+
236
+ ### Fixed
237
+
238
+ - **A report failure before publication says so, and never arrives causeless.**
239
+ An output path the filesystem cannot resolve — a `--out` under a regular
240
+ file, an unreadable directory on the way to it — was a raw runtime error
241
+ escaping into the command's catch-all, answered as `report-write-failed` with
242
+ empty `details` even though nothing had been rendered or replaced. It is now
243
+ `report-read-failed` with `details.operation` naming which resolution failed,
244
+ `details.path` naming the path the caller configured or passed, and
245
+ `details.cause` naming the filesystem's own code. An error no report path
246
+ throws on purpose still answers `report-write-failed`, because nothing
247
+ reached the output path, but it now carries `{operation, cause}` instead of
248
+ `{}`. Every `details.cause` is a bounded token — an error code, or the
249
+ error's kind when the runtime gave no code — so a publication failure no
250
+ longer republishes a runtime message, and the paths, credentials, and
251
+ run-specific values a message carries stay out of the envelope. Atomic
252
+ publication is unchanged: a report already at the selected path survives
253
+ every one of these refusals.
254
+ - **An empty named view stops blaming the reader.** A view whose criteria
255
+ matched nothing rendered the drill-down's `No items match these filters.` and
256
+ the graph's `No status is selected`, sending a reader to controls that could
257
+ not bring an item back. A named artifact with no items now states
258
+ `No ledger item matches this view's criteria.` and, in the graph, that it has
259
+ nothing to draw — visibly, without waiting for scripting. A report that holds
260
+ items keeps the filter and status copy, which is the honest answer when the
261
+ reader is the one who narrowed it, and the base report's bytes are unchanged.
262
+ - **`core-report-response.json` named a member the report never emitted.** The
263
+ published `ledger-invalid` refusal required `details.validation_errors`,
264
+ which is the mutation commands' member name; `report` has always emitted
265
+ `details.errors`. A consumer validating a real refusal against the shipped
266
+ schema failed on the runtime's own bytes. The schema now states `errors`, and
267
+ a live invalid-ledger report run is validated against it.
268
+
269
+ ## 0.1.0-alpha.7 - 2026-08-18
270
+
271
+ ### Changed
272
+
273
+ - **The item source is bounded at every candidate door, and the core contract
274
+ moves to 4.** The published version 3 core accepted a 50-MiB `create` with
275
+ exit `0` and state `committed` in 0.70 s: `create`, `transition`, and `patch`
276
+ had no bound
277
+ anywhere, and `publish-claimed` alone bounded candidates but reported an
278
+ oversized, perfectly canonical candidate as `The candidate source is not
279
+ canonical base64.` One shared `MAX_ITEM_SOURCE_BYTES` of 8,388,608 now bounds
280
+ the complete serialized successor at all four doors, and every one of them
281
+ answers the same named refusal: `item-source-too-large`, exit 2, state
282
+ `unchanged`, details exactly `{id, size_bytes, limit_bytes}` in the core
283
+ domain and `{item_id, size_bytes, limit_bytes}` in the ledger-publication
284
+ domain. The measurement is serialized UTF-8 bytes, so frontmatter, decisions,
285
+ extensions, and body all draw on the same budget; `transition` is bounded
286
+ because its decision block can push a legal stored item past it. Core
287
+ capabilities advertises the value at `result.limits.max_item_source_bytes`.
288
+ This narrows accepted input against a published version, so the core contract
289
+ moves to 4 and version 3 consumers fail closed at negotiation. A ledger
290
+ committed before the bound does not brick: an oversized item still validates,
291
+ still inspects, and a patch that shrinks it under the bound is accepted.
292
+
293
+ - **The work-claim API moves to 2.** The oversized-candidate response replaces
294
+ the error `publish-claimed` version 1 pinned for that input, so
295
+ `result.operations.work_claim.api_version` is now `2` and version 1 consumers
296
+ fail closed. Malformed base64 keeps its version 1 `invalid-request`: without
297
+ canonical base64 there is no item source to measure. The base64-character
298
+ precheck is gone — the 11,534,336-byte serialized-request bound already caps
299
+ what a candidate can decode to, and the precheck was the remaining path that
300
+ answered a genuine size refusal with a false base64 message. That transport
301
+ bound is unchanged and still measures a different object.
302
+ - **A claimed publication no longer takes one cooperative lock per ledger
303
+ item.** `publish-claimed` computed its lock closure from every item in the
304
+ loaded ledger, so publishing one item on a 1,500-item ledger created, wrote,
305
+ fsynced, and unlinked 1,503 lock files. It runs from journal replay through
306
+ its terminal record inside the namespace write lock, and every other
307
+ cooperative writer of a provisioned ledger enters that same lock before it
308
+ writes, so the per-item closure excluded nobody the namespace lock did not
309
+ already exclude. Publication now takes no per-item locks. Newly instrumented
310
+ phase counters measured the cost this removes: on 1,500 items the lock phase
311
+ was 11.2 s of the 12.6 s the publication took, against 0.2 s for reading Git
312
+ HEAD. Everything else is unchanged and proved byte-for-byte identical against
313
+ the previous implementation across all six publication outcome classes —
314
+ success, fence refusal, revision conflict, validation refusal, idempotent
315
+ replay, and indeterminate publication — in envelope, claim journal, and item
316
+ bytes. Every cooperative writer of one ledger must be upgraded together: a
317
+ writer that honors only per-ID locks can race one that honors only the
318
+ namespace lock.
319
+ - **The README installs with `@next` and says why.** The registry mandates a
320
+ `latest` dist-tag, so `latest` mirrors `next`; `@next` stays the documented
321
+ spelling and the explicit prerelease consent.
322
+ - **Cuts happen on the release branch, not in a session worktree.** Merge
323
+ session work first, then cut; the cut command refuses to run anywhere but the
324
+ branch tip. The previous two-phase topology is why the last two release tags
325
+ name merge commits rather than their cut commits.
326
+ `docs/adapter-release-path.md` records the ritual.
327
+
328
+ ### Fixed
329
+
330
+ - **A live publication is no longer reported as a broken lock.** Publication
331
+ lock files recorded `"operation": "publish-claimed"`, but the lock reader
332
+ accepts only `create`, `transition`, and `patch`, so a concurrent writer that
333
+ hit a live publication lock classified it `invalid-shape` — a diagnostic that
334
+ says the lock is corrupt. Publication writes no lock files at all now, so
335
+ there is nothing to misclassify.
336
+
337
+ - **A committed `patch` is forwarded instead of reported as an unknown
338
+ outcome.** The shipped adapter engine still named the pre-widening patchable
339
+ pair `number` and `priority`, so every patch a consumer actually sends — a
340
+ body rewrite, a title correction, a relation-list replacement, a declared
341
+ extension member — read as a non-canonical request, failed result
342
+ correlation, and came back `mutation-outcome-unknown` with recovery guidance
343
+ about a write that had provably committed. `number` is the immutable item
344
+ identity and was never patchable at all. The patchable field set is now what
345
+ mutation contract section 9 names — `title`, `priority`, `depends_on`,
346
+ `related`, `body`, `body_append`, and `extensions` — with the two body write
347
+ modes mutually exclusive and the extensions container judged only on its own
348
+ shape, and correlation follows each member to the surface it is observable
349
+ on. The independent reference model already had all of this, so the drift was
350
+ one-sided and no differential test could see it; the new end-to-end
351
+ core-outcome vectors found it on their first run.
352
+
353
+ - **`publish-claimed` now reconciles the journal unconditionally, like every
354
+ other mutating command.** The work-claim contract has always said an
355
+ uncommitted prior mutation refuses the next `create`, `transition`, `patch`,
356
+ **or `publish-claimed`**. The code only reconciled when it happened to
357
+ observe an unresolved `publish-intent`, and an uncommitted legacy mutation
358
+ leaves none behind. A fixture pinned the gap: with a legacy create and
359
+ transition sitting uncommitted, `claim-verify` returned exit 6 and a legacy
360
+ `create` refused with `publication-reconciliation-required`, while
361
+ `publish-claimed` on a claimed item published straight over the unreconciled
362
+ ledger. It now reconciles before the fence decision on every publication and
363
+ refuses with exit 6 `claim-store-unavailable`,
364
+ `details.reason: "publication-reconciliation-required"`, and
365
+ `details.findings` — the same envelope the legacy fence emits, so one
366
+ `claim-verify` clears every blocked path. **A publication behind an
367
+ unresolvable prior intent now returns that refusal instead of exit 6
368
+ `publication-outcome-unknown`.** The old code named the refused publication's
369
+ own outcome uncertain when it had not run at all; `state: "unchanged"` is the
370
+ honest answer, and the blocking finding still travels in `details.findings`.
371
+ The cost is honest about which read is new. Reconciliation adds no
372
+ complete-ledger read: it produces the snapshot the candidate validation and
373
+ the mutation engine's pre-lock read already share, so a claimed publication
374
+ still reads the working-tree ledger exactly twice. It does add the Git `HEAD`
375
+ read — `rev-parse`, one `ls-tree`, and a batched `cat-file` over every item
376
+ blob at `HEAD` — to every publication that previously had no unresolved
377
+ intent. That is the same read `create`, `transition`, `patch`, and every
378
+ claim lifecycle command already perform, so `publish-claimed` now pays the
379
+ toll its peers pay rather than a new one. Each publication persists one clock
380
+ floor, as it did before; a publication behind a pending intent, which used to
381
+ persist two, now persists one as well.
382
+
383
+ - **A shipped adapter no longer advertises trusted approval it cannot
384
+ exercise.** The three shipped entrypoints declared
385
+ `trusted_approval: {"supported": true, "sources": ["consumer"]}` while
386
+ `runAdapterEntrypoint` passed no approval, clock, nonce store, or core
387
+ executable identity to the invoke engine, so `create`, `transition`, and
388
+ `patch` through every shipped adapter refused `consumer-approval-required`
389
+ and could not succeed on any input. The declaration now reflects the runtime
390
+ of the invocation, the way `optional_features.claims` already reflects the
391
+ core probe: a bare entrypoint run declares no trusted approval and refuses a
392
+ mutation `capability-unavailable` with `missing: ["trusted-approval"]`, the
393
+ refusal section 5.1 already required for an absent declaration, and a host
394
+ that wires an approval source declares it truthfully. The two refusals stay
395
+ distinguishable, because they are different facts — no approval source at all
396
+ versus a source that produced no approval for this invocation — and conformance
397
+ case `07-mutation-approval` now pins both against the runtimes that produce
398
+ them, adding one assertion to the conformance suite.
399
+ - **A committed `create` is forwarded instead of reported as an unknown
400
+ outcome.** Both adapter engines required `schema_version: 1` in a create
401
+ result and re-serialized the expected candidate with the schema 1 default, so
402
+ every create against a real ledger — an empty ledger is schema 2 — failed the
403
+ result correlation, was judged an invalid core envelope, and came back as
404
+ `mutation-outcome-unknown` with recovery guidance about a write that had
405
+ provably committed. The ledger's schema version and the number the core
406
+ assigns under its own lock are the only two members of the answer a caller
407
+ could not have known; both engines now read those from the result and
408
+ re-derive the whole candidate from the request bytes, leaving every other
409
+ member pinned by an exact byte comparison against the source the core
410
+ returned. The defect was unreachable while every shipped mutation refused
411
+ before launch, and surfaced the moment a host runtime carried a real approval
412
+ through to the core.
413
+ - Adapter contract section 5's core-request table and section 5.1's approval
414
+ rule named only `create` and `transition`. Both have accepted `patch` since
415
+ adapter contract version 2; the prose now says so.
416
+
417
+ ### Added
418
+
419
+ - **`inspect` answers a bounded per-item lifecycle affordance projection.**
420
+ `inspect --ledger <dir> (--id <id> | --number <n>) --workbench --as-of
421
+ YYYY-MM-DD --json` returns, from one complete validated ledger snapshot,
422
+ `result.workbench`: the projection version, the as-of date, the ledger
423
+ snapshot witness, an `observation` member, a bounded item summary, and one
424
+ `transition_options` entry for every lifecycle target the native edge table
425
+ allows out of that item's kind and status. Each option names its target
426
+ status, its generated decision action or `null`, whether a caller-supplied
427
+ summary and rationale are required, the minimum legal transition date
428
+ `max(created, updated)`, its observed enabled state, and the observed
429
+ precondition issues and multi-item blockers in the exact `transition`
430
+ vocabulary. A workbench can now show a person which transitions an item can
431
+ take without duplicating lifecycle logic and without submitting a mutating
432
+ probe.
433
+
434
+ The read is an observation, not a lease, and says so in the response:
435
+ `observation.authority` is `observed-snapshot` and `observation.rechecked_by`
436
+ names what a later `transition` rechecks under lock — revision, lock, claim
437
+ fence, reconciliation, and candidate validation. It writes no item, lock,
438
+ claim journal, reconciliation log, or Git state, and it takes no lock.
439
+ `transition` and the projection share one lifecycle definition
440
+ (`src/lifecycle.js`), and a differential guard dispatches every projected
441
+ option and every unadvertised target through the real mutation, so an
442
+ advertised affordance cannot drift from what the mutation does.
443
+
444
+ Every variable-size field is bounded and says what it left out: the projected
445
+ title, the relation lists, each option's issues and blockers, and the related
446
+ IDs inside an issue. `--workbench` requires `--as-of`, an unpaired `--as-of`
447
+ is refused rather than ignored, and an `inspect` invocation without
448
+ `--workbench` is byte-identical to before. `capabilities` advertises
449
+ `operations.inspect.workbench` and the three exact bounds
450
+ `max_workbench_title_characters`, `max_workbench_collection_entries`, and
451
+ `max_workbench_response_bytes`; the core contract version stays 5, and the
452
+ projection is negotiated by its own `projection_version`. An invalid ledger is
453
+ `ledger-invalid` at exit 3 with no projection attached, and a projection that
454
+ would exceed its response bound is refused whole with
455
+ `workbench-response-too-large` at exit 2.
456
+
457
+ - **The conformance suite now measures real core outcomes end to end.** A new
458
+ equivalence case, `16-core-outcome-e2e`, carries nine hand-authored scenarios
459
+ that each run the direct real core in one isolated temporary workspace and,
460
+ separately, spawn the real shipped entrypoint over the bootstrap wire against
461
+ the real core in a second workspace materialized from the same before state:
462
+ `inspect` item-not-found, a committed `create`, a committed `transition`, a
463
+ committed `patch` by body replacement and by declared extension member, the
464
+ six-member date refusal, and all three `ledger-mutation` claim-fence refusal
465
+ classes. Success vectors match the exact core exit, stdout bytes, decoded
466
+ adapter streams with their digests and lengths, and the exact ledger
467
+ post-state; refusal vectors match the exact nonzero exit and unchanged ledger
468
+ bytes, and the fence refusals arrive as `ok: true` adapter transport results
469
+ rather than adapter errors. Before this case, no conformance assertion and no
470
+ bootstrap-wire test carried a mutation through a spawned entrypoint into a
471
+ launched core, which is how two real adapter defects shipped. The suite is
472
+ 210 assertions across 16 cases.
473
+
474
+ Determinism comes from fixed inputs and never from normalizing output: a
475
+ caller-supplied ULID, seeded revisions on isolated temporary ledgers, literal
476
+ dates, and — for the two real claim-fence read-backs — a fixed namespace, a
477
+ hand-authored claim journal, and a seeded future clock floor, so the emitted
478
+ `observed_at` is `max(physical_now, floor)` and therefore the floor. That
479
+ makes the refusal bytes fixed without mocking the core clock, and it expires:
480
+ both runners fail loudly and name `2031-01-15T12:01:00.000Z` once wall time
481
+ reaches it. Goldens are authored from the adapter contract, the work-claim
482
+ contract, and the normative `spec/fixtures/mutations/**` bytes; every byte
483
+ reused from those fixtures carries a `derived_from` pin, so drift on either
484
+ side stops the vector and asks for a reviewed golden change instead of
485
+ regenerating one. Only base64, SHA-256, and byte length are derived, and only
486
+ from an already hand-authored byte string.
487
+
488
+ The conformance host gains a granting approval mode, without which no
489
+ mutation can cross the spawned entrypoint at all. **Adapter contract section
490
+ 10 previously said no conformance fixture could manufacture authority; that
491
+ is no longer true, and the claim is withdrawn rather than quietly narrowed.**
492
+ What replaces it is narrower and checkable: the granting mode is reachable
493
+ only from a fixture's own runtime configuration, which no shipped adapter
494
+ package reads and no wire this contract defines carries; every granting
495
+ scenario runs against a throwaway temporary ledger; the approval is minted
496
+ from the binding the engine resolved and canonicalized by the independent
497
+ reference model. The evidence label is the production adapter engine under a
498
+ conformance host approval provider, not a live consumer approval mechanism.
499
+
500
+ - **A host process can wire consumer approval into a shipped adapter
501
+ entrypoint.** `runAdapterEntrypoint` now accepts an optional `hostRuntime`
502
+ carrying the approval source, the current time, the redeemed-nonce store, and
503
+ the core executable identity the host attests. It is a code-level parameter of
504
+ the embedding process and is deliberately absent from every wire: the
505
+ bootstrap request root schema is exact and has no approval member, so an
506
+ approval a model places on the request is an `invalid-invocation` that never
507
+ reaches the gate, exactly as adapter contract section 5.1 requires. The
508
+ approval may be a finished event or a resolver the adapter calls with the
509
+ exact binding it has just resolved — the argument vector, absolute workspace
510
+ paths, and instruction and handoff digests an approval covers do not exist
511
+ until the adapter has built them, so an interactive consumer prompt cannot
512
+ mint the approval any earlier. A resolver that fails produced no approval and
513
+ the mutation refuses; it never proceeds unapproved. The default is unchanged
514
+ and remains no approval. `test/adapter-host-approval-wire.test.js` carries the
515
+ first approved mutation in this repository to cross a spawned entrypoint into
516
+ a launched core and change a ledger, with its binding digest canonicalized by
517
+ the independent reference model rather than by the engine under test. No
518
+ version moved in any domain: `host.trusted_approval` has been optional since
519
+ version 1, the approval object and binding are untouched, and the mechanism is
520
+ invisible on every wire the contract defines. Adapter contract sections 3.2,
521
+ 3.3, 5.1, 10, and 12 state the seam, the honesty rule, the two-runtime
522
+ evidence, and the version argument.
523
+
524
+ - **The cut is one command, and version drift now fails the cut instead of
525
+ shipping.** `npm run release:cut -- <version> --date YYYY-MM-DD` runs on the
526
+ tip of the release branch, proves every version site is accounted for, plans
527
+ the new bytes in memory, runs the full release gate over them, and leaves one
528
+ `Cut <version>` commit and one annotated `v<version>` tag. It stops there:
529
+ push, `npm publish --tag next`, and the registry check stay separate named
530
+ steps, because no local command can undo any of them. Coverage is proved by
531
+ exact-set equality against a hand-maintained
532
+ `scripts/release-version-sites.json`, not by a global grep — the changelog and
533
+ the dated design records must keep naming old versions, so "grep finds
534
+ nothing" would be the wrong test. A release site added next month is
535
+ unmanifested and refuses the cut. `--dry-run` runs the same planner and the
536
+ same gate against a copy of HEAD and then proves the repository unchanged.
537
+ Reruns converge rather than repair: a cut tag at a clean HEAD reports
538
+ `already cut`, a complete cut commit without its tag resumes at tagging, and a
539
+ tag pointing elsewhere refuses.
540
+ - **The changelog can no longer lose its Unreleased section.** A cut opens a
541
+ fresh empty `## Unreleased` and files the released notes beneath it. The two
542
+ previous cuts renamed the heading instead, which left later changes landing
543
+ under an already published release.
544
+ - **The prerelease channel policy is stated and checkable.**
545
+ `npm run release:channels -- check|repair <version>` encodes it: `latest`
546
+ mirroring `next` at the published version and the first published alpha
547
+ deprecated. The first-choice policy — no `latest` at all, so a bare install
548
+ fails loudly — was refused by the registry itself: npm rejects deleting the
549
+ `latest` tag with E400 (verified live), so the current prerelease replaces
550
+ the dead first alpha as the forced default. `check` is read-only and is the
551
+ post-publish verification step; `repair` is idempotent and never unpublishes.
552
+
553
+ - **`--auto-commit` folds the commit-per-mutation ceremony into the mutation.**
554
+ The invariant is correct and the ceremony around it was the consumer's most
555
+ frequent daily cost: mutate, `git add`, `git commit`, `claim-verify`, repeat,
556
+ ten times for ten items. On a provisioned merge-coordinated ledger the new
557
+ opt-in bare flag on `create`, `transition`, `patch`, and `publish-claimed`
558
+ does that loop inside one invocation. It takes a per-working-tree mutex,
559
+ refuses any staged path anywhere and any dirty path under the ledger, checks
560
+ Git identity, runs an internal pre-mutation `claim-verify`, runs the mutation
561
+ unchanged, then commits **exactly** the changed item plus at most one
562
+ `.wowbagger/reconcile-<namespace>.md` under a fixed subject
563
+ (`wowbagger: transition item #7`; the canonical item ID for a schema-1 item
564
+ with no number). It verifies the resulting commit's parent, subject,
565
+ changed-path set, and every blob, then runs `claim-verify` again before it
566
+ answers. Success adds `git_commit`, `commit_paths`, and `claim_verified` to
567
+ `result`.
568
+
569
+ There is no configuration file setting, environment default, or repository
570
+ default, because a hidden default would make existing mutation automation
571
+ create Git commits unexpectedly. An invocation without the flag is
572
+ byte-identical to before, so the core contract stays 3 and the work-claim API
573
+ stays 1. The flag is direct-CLI only in this release; no adapter advertises or
574
+ constructs it.
575
+
576
+ What it will not do: commit anything on `state: "unchanged"` or
577
+ `state: "unknown"`, including the documented reconcile-log residue a refused
578
+ `publish-claimed` leaves behind; stage a path outside the ledger; broad-add,
579
+ amend, squash, reset, clean, stash, or unstage; pass `--no-verify` or disable
580
+ signing; fabricate an author; customize a commit message; or push, fetch,
581
+ pull, merge, or rebase. Hooks through `core.hooksPath`, `commit.gpgSign`, and
582
+ signing programs are honoured, and a hook that rewrites the subject or the
583
+ tree is reported rather than accepted.
584
+
585
+ - **An honest commit-failed contract, and one idempotent recovery verb.** A
586
+ post-publication Git failure that proves the commit is absent is exit 6
587
+ `git-commit-failed` with `state: "committed"` — the state still describes item
588
+ publication, not Git finalization — carrying the published revision, the exact
589
+ ledger-relative commit set with digests, `failure_stage`, `reason`, and a
590
+ bounded `recovery_token`. `create`, `transition`, and `patch` keep the core
591
+ domain; `publish-claimed` keeps `ledger-publication` and its top-level
592
+ `operation_id`. An **ambiguous** Git outcome is `git-commit-outcome-unknown`,
593
+ never `git-commit-failed`, and a commit that stands while reconciliation then
594
+ refuses is `post-commit-reconciliation-failed`. No failure envelope carries
595
+ hook output, signing output, absolute paths, or environment values.
596
+
597
+ New command: `wowbagger mutation-finalize --ledger <dir> --recovery-token
598
+ <token> --json`, answering in the work-claim domain because it changes Git
599
+ reconciliation state and no item byte. It re-derives every path from the
600
+ ledger and the provisioned namespace — the token is a witness, never authority
601
+ to select a path — re-checks the current bytes and the foreign-change rules,
602
+ creates the exact commit if it is absent, then runs `claim-verify`. When
603
+ `HEAD` already holds that exact commit it verifies and returns it without
604
+ creating a second one, so a lost response and a failed commit recover through
605
+ the same command, and repeating it is safe.
606
+
607
+ A failed attempt leaves its own commit set staged, because the design forbids
608
+ unstaging. Recovery tolerates exactly that residue and refuses anything else
609
+ staged; until it runs, the next `--auto-commit` invocation refuses on
610
+ `staged-paths-present`, which is the intended signal.
611
+
612
+ ## 0.1.0-alpha.6 - 2026-08-17
613
+
614
+ ### Added
615
+
616
+ - **`set.extensions` gives consumer-owned extension members a sanctioned patch
617
+ path.** Two field reports in two days: a consumer's own identifier field
618
+ rides a permitted extension member, and a wrong or missing one had no
619
+ ledger-side repair verb at all. On a provisioned ledger the hand-edit that
620
+ filled the gap is a stale write, so the protocol was forcing the edit it then
621
+ punished. `patch` now accepts an `extensions` container whose members name
622
+ extension members and whose values replace each one whole; `null` removes a
623
+ member. The fixed `set` allowlist is unchanged — `extensions` is one more
624
+ name on it, not an opening for arbitrary keys — so a top-level typo is still
625
+ an `unknown-member` refusal. Which members the container may name comes from
626
+ the committed `<ledger>/.wowbagger/extensions.json`, which declares a member
627
+ name and one value type each (`string`, `integer`, `boolean`, `string-list`).
628
+ **A ledger without that file has no patchable extension member at all**, and
629
+ the refusal names the missing declaration. Five new
630
+ `patch-precondition-failed` issue codes carry the refusals —
631
+ `extension-declaration-missing`, `extension-declaration-invalid`,
632
+ `extension-not-declared`, `extension-value-invalid`, `extension-anchored` —
633
+ in the existing four-member issue shape, with the member at fault named in
634
+ `field`. A member the item writes with a YAML anchor or alias is refused
635
+ rather than replaced, because replacing it would change every node bound to
636
+ the anchor; every member the request does not name keeps its exact
637
+ `extensionNodeIdentity` guarantee. The declaration authorizes a write and
638
+ never describes the ledger: `validate` does not read it, so an item whose
639
+ extension member disagrees with it stays valid and stays repairable. Nested
640
+ extension values still have no patch path and stay a reviewable hand-edit.
641
+ Core contract version stays 3 — the patch request schema widens and no
642
+ response envelope member is added, removed, or renamed — but version 3 is
643
+ published, so `contract_version` cannot answer whether a core carries this:
644
+ probe by sending an extension patch and reading the refusal, or pin the
645
+ distribution version. Documented in mutation contract section 9 and pinned by
646
+ `spec/fixtures/mutations/patch-extensions/`.
647
+
648
+ - **`claim-adopt` gives `unauthorized-revision` a non-destructive remedy.** A
649
+ consumer's staging checkout was blocked exit 6 on three items whose bodies
650
+ were hand-edited in a design session and merged. The refusal was correct, but
651
+ the only documented remedy — restore the authorized revision, then
652
+ `claim-verify` — discards reviewed, merged work. `claim-adopt` records that an
653
+ operator ruled the committed bytes legitimate and moves the coordinator's
654
+ authorized revision to them. It writes no item byte, so `updated` and the body
655
+ survive exactly. It is a standalone verb in the work-claim domain, a sibling
656
+ of `claim-verify`, and it is per item and per revision explicit: the request
657
+ names the item, the revision it believes is authorized, the revision being
658
+ adopted, and who is ruling. There is no adopt-all. It refuses
659
+ `adoption-witness-mismatch` on a stale witness (including a replay of a
660
+ successful adoption), `claim-held` while an unexpired claim holds the item,
661
+ `adoption-revision-uncommitted` unless the adopted revision is at Git `HEAD`
662
+ and in the caller's own working tree, and `adoption-ledger-invalid` when the
663
+ complete ledger would not validate. Success appends one `revision-adoption`
664
+ journal entry naming who, when, and both revisions, so the audit trail records
665
+ the ruling instead of losing it. Adoption is not a fence hole: the next
666
+ out-of-protocol edit is `unauthorized-revision` again, measured against the
667
+ adopted revision. Additive at contract version 1 — one new command, one new
668
+ journal entry type, three new error codes, no existing shape changed.
669
+ Documented in work-claim contract section 3.3 and pinned by
670
+ `spec/fixtures/work-claims/revision-adoption/`.
671
+
672
+ ### Changed
673
+
674
+ - **The report's epic-enablement factor now counts done or killed children
675
+ only.** It counted every child carrying a terminal date, which folded
676
+ archived and deferred children into the numerator: an epic with one done,
677
+ one archived, one deferred, and one backlog child reported enablement 0.75
678
+ while the mutation contract's terminal ratio for the same epic was 0.25. Two
679
+ numbers wore one name. A terminal date is not a terminal disposition —
680
+ archived restores and deferred undefers, both documented edges — so a parked
681
+ child is work postponed, not work retired, and counting it reported progress
682
+ that one transition takes back. The factor now reads the same done-or-killed
683
+ set as the contract and the epic complete rollup: one definition, three
684
+ surfaces. This is display-only and recomputed at render time; no ledger byte,
685
+ no wire shape, and no `ready` ordering changes. What does change is the
686
+ report: an epic with parked children reports a lower percentage, and its open
687
+ children rank lower on the epic-enablement step of `work next`.
688
+ - **Every `unauthorized-revision` remediation string now names both remedies.**
689
+ It was one sentence naming only the restore path, which reads as an
690
+ instruction to throw the edit away; the field report above did exactly that.
691
+ It is now two sentences, and each says what happens to the edit: `Restore the
692
+ authorized revision at <path>, then run claim-verify; that discards the edit.
693
+ Or adopt the committed revision of <path> with claim-adopt, then run
694
+ claim-verify; that keeps the edit.` The finding's `code`, `reason`,
695
+ `observed_surface`, `expected_path`, and revisions are unchanged; only the
696
+ human-readable `remediation` prose changed. `revision-regression` keeps its
697
+ restore-only string on purpose: it only fires while an active claim holds the
698
+ item, which is a state adoption refuses.
699
+ - **`patch` corrects an item title.** `set.title` takes a non-empty schema
700
+ string and replaces the title whole, under the same per-ID lock, exact-byte
701
+ compare-and-swap, candidate complete-ledger validation, and atomic
702
+ publication as every other patch; an item with an active claim is refused,
703
+ and `updated` moves to `request.date`. The scalar node is rewritten in place,
704
+ so the quoting style, the comments, the anchors, and every extension node
705
+ survive byte for byte. This closes a protocol contradiction reported twice
706
+ from the field: correcting a title used to require an out-of-protocol edit,
707
+ and on a provisioned ledger that edit is a stale write, so the next mutation
708
+ refused exit 6 `unauthorized-revision` and every later mutation stayed
709
+ blocked. `null` follows the frontmatter removal convention onto
710
+ `candidate-invalid`, because title is required; `""` is refused one step
711
+ earlier, at the request.
712
+ - **The mutation contract states the frontmatter ownership boundary.** Section
713
+ 9 gains a `Frontmatter ownership` table: one row per member, sorted into
714
+ core-owned (`schema_version`, `id`, `number`, `status`, `created`, `updated`,
715
+ the terminal dates, `decisions`), consumer-editable through `patch` (`title`,
716
+ `priority`, `depends_on`, `related`, `body`), and create-once (`kind`,
717
+ `provenance`, `parent`, `snoozed_until`). The boundary was previously
718
+ discoverable only by sending a patch and reading the refusal. A docs test
719
+ pins every row and the skill teaches the same three classes.
720
+
721
+ ### Decided
722
+
723
+ - **`kind` stays unpatchable, and the contract now says why.** A task-to-epic
724
+ flip changes which parent and children rules the item is validated under and
725
+ which lifecycle edges it may take. It needs its own verb with its own
726
+ preconditions, not a wider patch set.
727
+ - **Extension members stay out of `patch`, and the contract records the
728
+ reasons.** Two field reports asked for a sanctioned path for consumer-owned
729
+ identifier fields riding permitted extension members. The widening was
730
+ assessed against title's machinery and is not the same machinery: the
731
+ fail-closed `set` rule has no room for an arbitrary key, candidate validation
732
+ constrains no extension value, nested and anchored values do not survive a
733
+ whole-value replace the way a scalar does, and the oracle has no observable
734
+ surface to correlate an extension patch against. Section 9 names what a real
735
+ path would need — a `set.extensions` container, a declared per-ledger
736
+ extension schema, a stated rule for anchored and nested values, and an
737
+ oracle-visible surface — so the deferral is a design boundary rather than a
738
+ silence. Their status is stated in the ownership table either way.
739
+ - **`patch` gains `set.body_append`.** It takes a JSON string written after the
740
+ item's current body, under the same string rules `set.body` takes: the empty
741
+ string is valid, the bytes are the UTF-8 encoding of the string exactly, and
742
+ `null` is refused at `/set/body_append` because appending nothing is the empty
743
+ string. It is the same byte splice after the closing delimiter, so no
744
+ frontmatter byte moves, `updated` becomes request.date, and every existing
745
+ body byte survives — the request never names them. `body` and `body_append`
746
+ are mutually exclusive in one request: naming both is an `invalid-request`
747
+ issue at `/set`, exit 2, unchanged. This covers the annotation shape a mirror
748
+ consumer needs without making it carry a merge. The core contract stays
749
+ version 3 — it widens the patch request schema and moves no response envelope
750
+ member — but version 3 is already published without it, so a consumer
751
+ **cannot** probe for append support by reading `contract_version`. Send an
752
+ append and read the refusal instead: a core without it answers `unknown-member`
753
+ at `/set/body_append`, exit 2, unchanged.
754
+
755
+ ### Documentation
756
+
757
+ - **The allowed-edges table carries the defer and undefer edges.** `task` and
758
+ `epic` `backlog` to `deferred` and `deferred` to `backlog` have shipped in
759
+ `src/mutation.js` since deferral existed, both requiring a decision, and the
760
+ ownership table already documented `deferred` as a core-owned field that
761
+ `transition` writes on a defer. Section 8's edge table listed neither row, so
762
+ the one place a consumer looks up what it may drive under-reported the
763
+ lifecycle by two edges. Both rows are added with the evidence the code
764
+ generates — `set deferred; append defer decision` and `clear deferred; append
765
+ undefer decision` — and a docs guard pins the kind, the date, and the
766
+ decision on each. No emitted byte changes and the core contract stays version
767
+ 3: this documents shipped edges, it does not add them.
768
+
769
+ - **The epic derivation section cites one shared definition instead of a
770
+ divergence.** It recorded the report's epic-enablement factor as a different,
771
+ wider number than the terminal ratio. The report factor was narrowed to match
772
+ (see Changed above), so the paragraph is replaced: the contract, the epic
773
+ complete rollup, and the report all count done or killed direct children over
774
+ all direct children, and the section now says outright that a terminal date is
775
+ not the test. The docs guard is re-pointed at the new truth rather than
776
+ relaxed.
777
+
778
+ - **The contract states that `set.body` replaces and never merges.** A consumer
779
+ mirroring an external source regenerated an item body from its upstream card
780
+ and destroyed a ledger-only annotation; every check passed, because
781
+ `expected_revision` is a byte-level lost-update guard with no semantic safety.
782
+ Mutation contract section 9 and the skill's body bullet now say it plainly:
783
+ the replacement is total, and a mirroring consumer MUST read-modify-write from
784
+ the current item body and MUST never regenerate from the source alone. Docs
785
+ guards pin both sentences.
786
+
787
+ - **The contract documents the selector an `inspect` `item-not-found` refusal
788
+ echoes.** `inspect --number <n>` on a number no item carries emits
789
+ `details: {"number": <n>}`, and it has done so since `--number` shipped in
790
+ 0.1.0-alpha.5. Mutation contract section 5 claimed these details contain only
791
+ `id`, so the published prose and the published wire disagreed. Section 5 now
792
+ states the rule the runtime follows — the details carry exactly the selector
793
+ the request used, `id` for `--id` and `number` for `--number` — and
794
+ `spec/fixtures/mutations/inspect-number-not-found/` pins it. No emitted byte
795
+ changes and the core contract stays version 3: this documents a shipped
796
+ shape, it does not introduce one. The adapter surfaces still require id-only
797
+ details, deliberately: the adapter's `inspect` request accepts no `number`
798
+ member and always invokes `--id`, so it can never see the number variant.
799
+
800
+ ## 0.1.0-alpha.5 - 2026-08-16
801
+
802
+ ### Breaking
803
+
804
+ - **`number` is no longer caller-settable on schema version 2 ledgers.**
805
+ `create` refuses a request supplying `item.number` and assigns the next
806
+ number itself (`max + 1` under the number-index lock); `patch` refuses
807
+ `set.number` because the number is the immutable item identity. A consumer
808
+ mirroring a legacy backlog cannot carry its legacy numbers into wowbagger
809
+ handles — keep legacy identifiers in a permitted extension member or in the
810
+ item body instead. (Shipped as part of the number-as-identity work; this
811
+ notice was added after the 0.1.0-alpha.5 tarball was cut, so the packaged
812
+ changelog carries it only inside the contract version 3 delta note.)
813
+
814
+ ### Added
815
+
816
+ - **The report draws the whole ledger as a 3D dependency graph.** It sits below
817
+ the evidence layer, under the decision surface. Every item is a node labelled
818
+ `#N`, coloured by readiness or terminal status and sized by the same
819
+ transitive unblocking leverage the recommended order uses; `depends_on` edges
820
+ are straight and arrowed, `parent` edges are curved and unarrowed, and both
821
+ run from the prerequisite to the item it releases. Hovering or clicking a node
822
+ shows its number, title, status, age, leverage, and the same reasons line the
823
+ ranked list prints for it. The renderer is `3d-force-graph` 1.80.0 over
824
+ Three.js r183, vendored at `vendor/3d-force-graph/` with its upstream SHA-256
825
+ recorded beside it and pinned by a test, and inlined at generation time: the
826
+ report stays one self-contained file and fetches nothing at generation or view
827
+ time. It costs roughly 1.3 MB of report size. A browser without WebGL gets the
828
+ section's plain explanation and a per-node roster instead; no
829
+ decision-relevant content exists only in the 3D view.
830
+
831
+ ### Changed
832
+
833
+ - **An invalid ledger can now be diagnosed with the documented commands.** One
834
+ invalid item still refuses every read and every guarded mutation on that
835
+ ledger, but the refusals no longer hide what the operator has to read.
836
+ `inspect` keeps refusing exit 3 `ledger-invalid` — handing back a revision
837
+ from a ledger the core has not judged would read as a mutation precondition,
838
+ and there is no flag that skips validation — and its refusal now carries
839
+ `error.details.item`, the same lossless snapshot the success envelope
840
+ defines, for the item the request selected, whenever no validation error
841
+ names that item's path. A faulted item is withheld; `validate` already names
842
+ its repair. `claim-verify` now reports `result.ledger_validation`, carrying
843
+ `valid` and `errors` exactly as the bare `validate` result does, plus a
844
+ `remediation` when the ledger is invalid. Its claim answer is unchanged:
845
+ `findings`, `state`, and the exit status still describe claim state alone, so
846
+ a consistent journal over an invalid ledger is still exit 0 with
847
+ `findings: []` — it just no longer pretends that is a clear road. The report
848
+ costs no extra ledger read. Fixtures extend item #104's misplaced-item
849
+ scenario.
850
+ - **The adapter forwards `inspect` refusals instead of calling them protocol
851
+ errors.** Both the engine and the independent oracle demanded a canonical
852
+ mutation request before they would accept any error details, which no read
853
+ command has, so every `inspect` `item-not-found` and `ledger-invalid` refusal
854
+ was mapped to `core-protocol-error`. The precondition now applies only to
855
+ mutation commands. The same surfaces accept the `expected_path` and
856
+ `remediation` that item #104 added to a validation error, and the optional
857
+ `details.item` on an `inspect` `ledger-invalid` refusal — on `inspect` only;
858
+ a mutation refusal that carries one is still rejected.
859
+
860
+ - **The report's content security policy now also forbids `connect-src`.** The
861
+ report has never opened a connection; the policy now says so.
862
+
863
+ - **The `item-outside-layout` validation error now names the expected path and
864
+ the relocation that repairs it.** It keeps its stable code and its actual
865
+ `path`, and gains `expected_path` plus a `remediation`; its message names
866
+ both paths. A committed item outside the configured items directory refuses
867
+ every read and every guarded mutation on that ledger, including ones that
868
+ never touch the misplaced item, so the refusal has to say where the item
869
+ belongs. The claim fence is not involved: `claim-verify` reports no finding
870
+ on such a ledger, refuting an earlier consumer report that a
871
+ root-misplaced item makes the fence report `stale-write-detected` with
872
+ `actual_revision: null`. Fixtures pin both configuration orders — layout
873
+ bound first, and layout bound after the item was already committed at the
874
+ root.
875
+ - **The core contract version is now `3`.** Every core command envelope
876
+ (`capabilities`, `inspect`, `create`, `transition`, `patch`, `mint-id`,
877
+ `report`) carries `contract_version: 3`, the shipped adapters require core
878
+ contract version 3, and the installed skill's version check gates on 3. A
879
+ version 1 or version 2 consumer fails closed against this core, which is the
880
+ point of the bump. Version 3 is version 2 plus four deltas against published
881
+ `0.1.0-alpha.4`: the widened `date-before-created` / `date-before-updated`
882
+ issue shape carrying `item_created` and `item_updated` (the delta that forced
883
+ the bump — a version 2 consumer validating issue members exactly refuses the
884
+ six-member shape); the patch field set widening from `number`/`priority` to
885
+ `priority`/`depends_on`/`related`; number as the core-assigned immutable item
886
+ identity on schema version 2, with `create` refusing a supplied number and
887
+ `inspect` accepting `--number`; and `create` deriving its published path from
888
+ a committed `.wowbagger/layout.json`. The mutation contract's "Contract
889
+ versions" section carries the full enumeration. The legacy work-claim,
890
+ ledger-publication, and ledger-mutation envelopes and
891
+ `result.operations.work_claim.api_version` are separate version domains and
892
+ stay at 1; the adapter contract stays at 2.
893
+
894
+ ### Added
895
+
896
+ - The README and the installed skill warn that `git mv` refuses a freshly
897
+ created item, because `create` writes an untracked file and the `git add -A`
898
+ behind it in an unchecked batch commits the item at the ledger root instead.
899
+ Both state the safe sequence: plain `mv`, then `git add`, checking every exit
900
+ code before the commit. The warning sits in the `0.1.0-alpha.4` boundary text
901
+ that already tells consumers that core ignores the layout file.
902
+ - **`patch` can replace an item body.** `set.body` takes a JSON string that
903
+ replaces the whole body under `create`'s body rules, so a consumer whose
904
+ items mirror an external card updates them through the managed path instead
905
+ of hand-editing the Markdown. A body patch rewrites no frontmatter byte —
906
+ anchors, aliases, comments, quoting, styles, member order, and extension
907
+ members all survive, and only `updated` changes, as it does for every patch.
908
+ A body may be set in the same `set` as `priority`, `depends_on`, or
909
+ `related`, in one compare-and-swap write. `null` is refused at `/set/body`:
910
+ the body is a region of the file, so removing it means `""`, not null. A
911
+ claimed item, a stale revision, and a non-string body refuse as before. This
912
+ widens the patch request schema inside core contract version 3 and does not
913
+ move the version.
914
+ - One envelope rule now covers every `--json` response. The mutation contract
915
+ states the response domains (core, work-claim, ledger-publication,
916
+ ledger-mutation, and bare result), the dispatch steps a generic consumer
917
+ follows, which domain each command's success and each refusal class answers
918
+ in, and the exact root members of each shape. Both sanctioned exceptions are
919
+ stated with their reasons: `validate` and `ready` stay bare results because
920
+ scripts and fixtures depend on those bytes, and a claim-fenced refusal to
921
+ `create`, `transition`, or `patch` answers in the `ledger-mutation` domain
922
+ with `command: "<command>-v1"` and `contract_version: 1` because it is the
923
+ work-claim contract refusing, not the core contract. The work-claim contract
924
+ now names all three of its `namespace` values.
925
+ `spec/fixtures/envelope-domains/manifest.json` pins all 37 response classes,
926
+ and `test/envelope-dispatch.test.js` walks every one of them through the
927
+ documented dispatch rule and rejects drift in either direction. No emitted
928
+ byte changed and no contract version moved.
929
+ - `report` now renders a sequencing dashboard instead of a state snapshot. The
930
+ HTML opens with **Work next**, the ready set in a recommended order with the
931
+ factors that placed each entry printed beside it; then **Attention**, naming
932
+ blockers by number, the oldest open work with its age, and started work past
933
+ this ledger's own 85th-percentile cycle time; then an evidence layer with
934
+ aging buckets, weekly arrivals against completions, accept-to-complete cycle
935
+ time, and a Monte Carlo forecast as 50 and 85 percent bands. State counts,
936
+ item cards, and swarm batches remain, below that decision surface. Relations
937
+ and readiness reasons inside the drill-down now name items by number.
938
+ Ordering is a report-layer derivation, recomputed from ledger bytes at render
939
+ time and never persisted: `ready --json`, its four-step order, and the
940
+ mutation contract are unchanged. The report file stays self-contained with no
941
+ external runtime dependency.
942
+ - Report configuration accepts two more `fields` mappings, `class` and `due`.
943
+ `class` is a class of service from `expedite | fixed-date | standard |
944
+ intangible`; `expedite` lifts an item above every other ready item, an absent
945
+ value means `standard`, and an unrecognised value is ranked as standard and
946
+ reported in the report rather than dropped. `due` is an ISO calendar date
947
+ ordered by proximity. Both ride the existing extension-member channel, so no
948
+ core field carries them.
949
+ - The commit-per-mutation invariant is documented. On a provisioned ledger,
950
+ every mutation must be committed to Git before the next mutating command,
951
+ and `claim-verify` is the reconciliation procedure for the exit 6
952
+ `publication-reconciliation-required` refusal. The mutation contract, the
953
+ work-claim contract, the README, and the installed skill's claimed and
954
+ unclaimed loops all state the rule and the loop it implies. The mutation
955
+ contract also records why validating against working-tree bytes was
956
+ rejected.
957
+ - `claim capabilities --ledger <dir> --json` now advertises
958
+ `result.backend.write_serialization`. A provisioned Git-journal backend
959
+ reports `scope: "all-worktrees-of-one-repository"` and
960
+ `blocks_until: "peer-commit-visible-in-this-checkout"`; an unprovisioned
961
+ backend reports `scope: "none"`. This makes the serialization the shared
962
+ Git-common-directory journal already performed discoverable instead of
963
+ implied. The core `capabilities` envelope is unchanged; this change is not
964
+ one of the version 3 deltas.
965
+
966
+ - A `date-before-created` or `date-before-updated` issue now carries
967
+ `item_created` and `item_updated` after `related_ids` — the target item's own
968
+ dates at refusal time, both dates on both codes, on `transition` and `patch`
969
+ alike. One refusal now states the whole acceptable date window, so correcting
970
+ the request no longer costs an `inspect` round-trip. No other issue code
971
+ changes shape; a consumer that validates issue members exactly must accept
972
+ six members for these two codes. This widening is the reason the core
973
+ contract version moves to 3 (see Changed, above).
974
+ The mutation contract and the installed skill now also state that `create`
975
+ derives `created` from the ULID timestamp, which is UTC, with the
976
+ across-midnight example that produces this refusal.
977
+
978
+ ### Documentation
979
+
980
+ - **`create` stays journal-silent, and the work-claim contract now says why.**
981
+ Section 3.1 already stated that `create` records no claim-journal entry and
982
+ therefore never blocks a sibling worktree. It now records the decision to
983
+ keep that asymmetry and the three reasons behind it: create's publication is
984
+ already atomic, no-clobber, and byte-verified; journaling create would
985
+ serialize every worktree on the highest-volume mutation; and the remaining
986
+ exposure window closes at the item's first `transition` or `patch`. The
987
+ window is stated honestly — until that first journal-visible mutation an
988
+ out-of-protocol overwrite of a created item is not detected, and a commit
989
+ alone does not close the window, because reconciliation compares only the
990
+ revisions the journal expects. `test/create-journal-asymmetry.test.js` pins
991
+ both halves end to end. No behaviour changed.
992
+
993
+ ### Fixed
994
+
995
+ - A claim-fence refusal no longer reaches the agent as
996
+ `mutation-outcome-unknown`. Both adapter engines classified every response
997
+ with the core envelope validator, so a fenced refusal — `namespace:
998
+ "ledger-mutation"`, `command: "<command>-v1"`, `contract_version: 1`, `state:
999
+ "unchanged"` — failed core validation and became "the mutation may have been
1000
+ applied; inspect current state before retrying", on every fenced refusal on a
1001
+ provisioned ledger, about a write that provably never ran. The adapter now
1002
+ dispatches on the response domain first, exactly as the mutation contract's
1003
+ section 2 rule requires, and validates a fenced refusal on the work-claim
1004
+ contract's terms: `claimed-item-write-refused` on `create`,
1005
+ `active-claim-write-refused` on `transition` and `patch`, and
1006
+ `claim-store-unavailable` on any mutation, each with its pinned message, exit,
1007
+ and permitted states, its read-back bound to the item the caller asked to
1008
+ write, and its reason plus findings and remediation forwarded verbatim. A
1009
+ `claim-store-unavailable` refusal that declares `state: "unknown"` stays an
1010
+ unknown outcome, and so does any namespaced envelope the adapter cannot
1011
+ classify. Adapter contract section 6.1 states the rule and the honest-outcome
1012
+ guarantee; five conformance vectors pin it, the differential test replays each
1013
+ through both engines, and no version moved in either domain.
1014
+ - A mutation on a claim-protected ledger now reads the complete ledger twice
1015
+ instead of three times. Journal reconciliation and the mutation engine's
1016
+ pre-lock phase were separate unlocked reads of the same directory inside one
1017
+ claim-lock hold, and reconciliation writes nothing a complete load reads, so
1018
+ the pre-lock phase reuses reconciliation's snapshot. On a 1,500-item
1019
+ provisioned fixture a create fell from about 1.15 s to about 0.89 s and a
1020
+ transition from about 1.17 s to about 0.91 s, matching one full load at about
1021
+ 0.29 s. The read under lock stays: it is what decides the revision
1022
+ compare-and-swap and the lock-closure stability check, and every decision
1023
+ drawn from the shared snapshot is re-made against it. A lock-closure retry
1024
+ still reads fresh. No validation rule changed, and a mutation on a plain
1025
+ directory is unaffected.
1026
+ - A mutation on a large provisioned ledger no longer spends its wall time in
1027
+ process spawns. Git HEAD reconciliation read every committed item with its
1028
+ own `git show`, one subprocess per item, serially; it now reads them with
1029
+ one `git cat-file --batch` subprocess per 16 MiB of tree content. On a
1030
+ 1,500-item fixture a create fell from about 15.4 s to about 1.2 s and a
1031
+ transition from about 15.4 s to about 1.3 s. The reconciliation reads the
1032
+ same bytes for the same items, and no validation is skipped: candidate
1033
+ validation still validates the complete ledger.
1034
+
1035
+ - Every reconciliation finding that blocks a mutation now carries a
1036
+ `remediation` string naming the path to act on and `claim-verify`.
1037
+ `revision-regression`, `legacy-mutation-outcome-unknown`, and
1038
+ `publication-outcome-unknown` previously blocked with no recovery action;
1039
+ they now also carry `expected_path` when it is identifiable.
1040
+
1041
+ - A committed `.wowbagger/layout.json` now binds the ledger's item directory.
1042
+ `create` derives its path from that configuration. Validation rejects parsed
1043
+ items outside it, special or symbolic layout files, and metadata-directory
1044
+ aliases. Malformed configuration fails closed. Ledgers without the file
1045
+ retain the root-level `<id>.md` layout.
1046
+ - A refused legacy mutation and a clean `claim-verify` now leave the ledger
1047
+ working tree byte-identical. The tracked reconciliation log projects only
1048
+ journal entries that record a decision, so per-invocation clock entries no
1049
+ longer dirty it, and a successful legacy mutation now projects its own
1050
+ entries before returning instead of one command later. Batch tooling no
1051
+ longer needs to stage the log after a failure.
1052
+ - `claim-verify` now classifies stale writes as unauthorized revisions, missing
1053
+ Git finalization, worktree synchronization, or pending claimed publication.
1054
+ Working-tree deletions of an authorized Git revision are unauthorized.
1055
+ Findings name the expected item path and give a direct recovery action.
1056
+ - The contracts and the skill now state that a provisioned ledger's claim
1057
+ journal serializes every worktree of one repository, and that a recorded
1058
+ write blocks mutations in the other worktrees until its commit is visible
1059
+ there. `limits.cross_worktree_coordination: false` is documented as "the
1060
+ core never synchronizes checkouts", not as independent worktree writes.
1061
+ Both stale-write remedies, the moving-`expected_revision` trap, and the
1062
+ failed copy-the-item-in workaround are documented and pinned by tests.
1063
+
1064
+ ## 0.1.0-alpha.4 - 2026-08-14
1065
+
1066
+ ### Added
1067
+
1068
+ - `report` validates a ledger and atomically writes a deterministic,
1069
+ self-contained HTML report from `.wowbagger/report.json`. The report includes
1070
+ canonical readiness, semantic-field search, filters, sorting, grouping,
1071
+ three detail levels, terminal history, and optional area-diverse swarm
1072
+ batches. This repository includes a local report configuration and ignores
1073
+ the generated artifact.
1074
+
1075
+ ### Fixed
1076
+
1077
+ - The Claude Code adapter now declares Darwin `supported`. Native Darwin
1078
+ conformance passes all 183 common-vector assertions across all 15 cases, so
1079
+ configured consumer workspaces can invoke the published adapter read path.
1080
+
1081
+ ## 0.1.0-alpha.3 - 2026-08-12
1082
+
1083
+ ### Fixed
1084
+
1085
+ - Published install and upgrade guidance now names this release's immutable Git
1086
+ tag and uses the prerelease `next` npm channel instead of the older `latest`
1087
+ artifact. Item 62.
1088
+ - The installed plugin skill now requires the exact core distribution version
1089
+ that shipped with it, in addition to core contract version 2. This detects an
1090
+ older core that shares the contract number but lacks behavior required by the
1091
+ newer skill. Item 64; item 63 records the rejected capability-schema change.
1092
+
1093
+ ## 0.1.0-alpha.2 - 2026-08-12
1094
+
1095
+ ### Fixed
1096
+
1097
+ - `claim-verify` now reports `git_finalized` and `git_commit` for each successful
1098
+ claimed publication. Reconciliation logs stay inside the configured ledger,
1099
+ including repository-root ledgers. Items 55-57.
1100
+ - A new empty ledger now starts on schema version 2. Existing non-empty
1101
+ schema-version-1 ledgers remain compatible until migration. Item 54.
1102
+ - The installed skill now identifies the active claim as the work-in-flight
1103
+ signal while the item remains in `backlog`. Item 58.
1104
+ - The npm package now ships the mutation and work-claim contract documents that
1105
+ the installed skill references. Item 59.
1106
+ - The npm package now ships the documented schema-version-2 migration
1107
+ entrypoint for package-only consumers. Item 60.
1108
+ - The installed work-claim contract now distinguishes durable reconciliation
1109
+ state from per-publication Git finalization. Item 61.
1110
+
1111
+ - The Claude Code plugin manifest and marketplace metadata now use the same
1112
+ distribution version as `package.json`. The packaging gate rejects release
1113
+ identity drift before npm, Git, or marketplace publication. Item 48.
1114
+ - Capability help now distinguishes the core's unbound default claim profile
1115
+ from one provisioned ledger's work-claim profile. It names
1116
+ `contract_version` as the core version and
1117
+ `operations.work_claim.api_version` as the work-claim API version. Items 47
1118
+ and 51.
1119
+ - `provision --help`, README, and the shipped skill now expose the accessible
1120
+ Git-checkout prerequisite and the pre-provision
1121
+ `claim capabilities --ledger <dir> --json` gate. Item 50.
1122
+ - The isolated consumer dogfood runbook now creates or selects the disposable
1123
+ worktree before agent launch and requires a session-root Git preflight before
1124
+ installation or ledger mutation. Item 52.
1125
+
10
1126
  ## 0.1.0-alpha.1 - 2026-08-11
11
1127
 
12
1128
  ### Behaviour changes