wowbagger 0.1.0-alpha.4 → 0.1.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,7 +5,257 @@ A change to what a command accepts, refuses, emits, or writes is a behaviour
5
5
  change even when the commit that carried it was labelled refactor, docs, or
6
6
  consolidation. The first tagged release inherits this file.
7
7
 
8
- ## Unreleased
8
+ ## 0.1.0-alpha.5 - 2026-08-16
9
+
10
+ ### Added
11
+
12
+ - **The report draws the whole ledger as a 3D dependency graph.** It sits below
13
+ the evidence layer, under the decision surface. Every item is a node labelled
14
+ `#N`, coloured by readiness or terminal status and sized by the same
15
+ transitive unblocking leverage the recommended order uses; `depends_on` edges
16
+ are straight and arrowed, `parent` edges are curved and unarrowed, and both
17
+ run from the prerequisite to the item it releases. Hovering or clicking a node
18
+ shows its number, title, status, age, leverage, and the same reasons line the
19
+ ranked list prints for it. The renderer is `3d-force-graph` 1.80.0 over
20
+ Three.js r183, vendored at `vendor/3d-force-graph/` with its upstream SHA-256
21
+ recorded beside it and pinned by a test, and inlined at generation time: the
22
+ report stays one self-contained file and fetches nothing at generation or view
23
+ time. It costs roughly 1.3 MB of report size. A browser without WebGL gets the
24
+ section's plain explanation and a per-node roster instead; no
25
+ decision-relevant content exists only in the 3D view.
26
+
27
+ ### Changed
28
+
29
+ - **An invalid ledger can now be diagnosed with the documented commands.** One
30
+ invalid item still refuses every read and every guarded mutation on that
31
+ ledger, but the refusals no longer hide what the operator has to read.
32
+ `inspect` keeps refusing exit 3 `ledger-invalid` — handing back a revision
33
+ from a ledger the core has not judged would read as a mutation precondition,
34
+ and there is no flag that skips validation — and its refusal now carries
35
+ `error.details.item`, the same lossless snapshot the success envelope
36
+ defines, for the item the request selected, whenever no validation error
37
+ names that item's path. A faulted item is withheld; `validate` already names
38
+ its repair. `claim-verify` now reports `result.ledger_validation`, carrying
39
+ `valid` and `errors` exactly as the bare `validate` result does, plus a
40
+ `remediation` when the ledger is invalid. Its claim answer is unchanged:
41
+ `findings`, `state`, and the exit status still describe claim state alone, so
42
+ a consistent journal over an invalid ledger is still exit 0 with
43
+ `findings: []` — it just no longer pretends that is a clear road. The report
44
+ costs no extra ledger read. Fixtures extend item #104's misplaced-item
45
+ scenario.
46
+ - **The adapter forwards `inspect` refusals instead of calling them protocol
47
+ errors.** Both the engine and the independent oracle demanded a canonical
48
+ mutation request before they would accept any error details, which no read
49
+ command has, so every `inspect` `item-not-found` and `ledger-invalid` refusal
50
+ was mapped to `core-protocol-error`. The precondition now applies only to
51
+ mutation commands. The same surfaces accept the `expected_path` and
52
+ `remediation` that item #104 added to a validation error, and the optional
53
+ `details.item` on an `inspect` `ledger-invalid` refusal — on `inspect` only;
54
+ a mutation refusal that carries one is still rejected.
55
+
56
+ - **The report's content security policy now also forbids `connect-src`.** The
57
+ report has never opened a connection; the policy now says so.
58
+
59
+ - **The `item-outside-layout` validation error now names the expected path and
60
+ the relocation that repairs it.** It keeps its stable code and its actual
61
+ `path`, and gains `expected_path` plus a `remediation`; its message names
62
+ both paths. A committed item outside the configured items directory refuses
63
+ every read and every guarded mutation on that ledger, including ones that
64
+ never touch the misplaced item, so the refusal has to say where the item
65
+ belongs. The claim fence is not involved: `claim-verify` reports no finding
66
+ on such a ledger, refuting the PropertyCompass2 PR #2184 claim that a
67
+ root-misplaced item makes the fence report `stale-write-detected` with
68
+ `actual_revision: null`. Fixtures pin both configuration orders — layout
69
+ bound first, and layout bound after the item was already committed at the
70
+ root.
71
+ - **The core contract version is now `3`.** Every core command envelope
72
+ (`capabilities`, `inspect`, `create`, `transition`, `patch`, `mint-id`,
73
+ `report`) carries `contract_version: 3`, the shipped adapters require core
74
+ contract version 3, and the installed skill's version check gates on 3. A
75
+ version 1 or version 2 consumer fails closed against this core, which is the
76
+ point of the bump. Version 3 is version 2 plus four deltas against published
77
+ `0.1.0-alpha.4`: the widened `date-before-created` / `date-before-updated`
78
+ issue shape carrying `item_created` and `item_updated` (the delta that forced
79
+ the bump — a version 2 consumer validating issue members exactly refuses the
80
+ six-member shape); the patch field set widening from `number`/`priority` to
81
+ `priority`/`depends_on`/`related`; number as the core-assigned immutable item
82
+ identity on schema version 2, with `create` refusing a supplied number and
83
+ `inspect` accepting `--number`; and `create` deriving its published path from
84
+ a committed `.wowbagger/layout.json`. The mutation contract's "Contract
85
+ versions" section carries the full enumeration. The legacy work-claim,
86
+ ledger-publication, and ledger-mutation envelopes and
87
+ `result.operations.work_claim.api_version` are separate version domains and
88
+ stay at 1; the adapter contract stays at 2.
89
+
90
+ ### Added
91
+
92
+ - The README and the installed skill warn that `git mv` refuses a freshly
93
+ created item, because `create` writes an untracked file and the `git add -A`
94
+ behind it in an unchecked batch commits the item at the ledger root instead.
95
+ Both state the safe sequence: plain `mv`, then `git add`, checking every exit
96
+ code before the commit. The warning sits in the `0.1.0-alpha.4` boundary text
97
+ that already tells consumers that core ignores the layout file.
98
+ - **`patch` can replace an item body.** `set.body` takes a JSON string that
99
+ replaces the whole body under `create`'s body rules, so a consumer whose
100
+ items mirror an external card updates them through the managed path instead
101
+ of hand-editing the Markdown. A body patch rewrites no frontmatter byte —
102
+ anchors, aliases, comments, quoting, styles, member order, and extension
103
+ members all survive, and only `updated` changes, as it does for every patch.
104
+ A body may be set in the same `set` as `priority`, `depends_on`, or
105
+ `related`, in one compare-and-swap write. `null` is refused at `/set/body`:
106
+ the body is a region of the file, so removing it means `""`, not null. A
107
+ claimed item, a stale revision, and a non-string body refuse as before. This
108
+ widens the patch request schema inside core contract version 3 and does not
109
+ move the version.
110
+ - One envelope rule now covers every `--json` response. The mutation contract
111
+ states the response domains (core, work-claim, ledger-publication,
112
+ ledger-mutation, and bare result), the dispatch steps a generic consumer
113
+ follows, which domain each command's success and each refusal class answers
114
+ in, and the exact root members of each shape. Both sanctioned exceptions are
115
+ stated with their reasons: `validate` and `ready` stay bare results because
116
+ scripts and fixtures depend on those bytes, and a claim-fenced refusal to
117
+ `create`, `transition`, or `patch` answers in the `ledger-mutation` domain
118
+ with `command: "<command>-v1"` and `contract_version: 1` because it is the
119
+ work-claim contract refusing, not the core contract. The work-claim contract
120
+ now names all three of its `namespace` values.
121
+ `spec/fixtures/envelope-domains/manifest.json` pins all 37 response classes,
122
+ and `test/envelope-dispatch.test.js` walks every one of them through the
123
+ documented dispatch rule and rejects drift in either direction. No emitted
124
+ byte changed and no contract version moved.
125
+ - `report` now renders a sequencing dashboard instead of a state snapshot. The
126
+ HTML opens with **Work next**, the ready set in a recommended order with the
127
+ factors that placed each entry printed beside it; then **Attention**, naming
128
+ blockers by number, the oldest open work with its age, and started work past
129
+ this ledger's own 85th-percentile cycle time; then an evidence layer with
130
+ aging buckets, weekly arrivals against completions, accept-to-complete cycle
131
+ time, and a Monte Carlo forecast as 50 and 85 percent bands. State counts,
132
+ item cards, and swarm batches remain, below that decision surface. Relations
133
+ and readiness reasons inside the drill-down now name items by number.
134
+ Ordering is a report-layer derivation, recomputed from ledger bytes at render
135
+ time and never persisted: `ready --json`, its four-step order, and the
136
+ mutation contract are unchanged. The report file stays self-contained with no
137
+ external runtime dependency.
138
+ - Report configuration accepts two more `fields` mappings, `class` and `due`.
139
+ `class` is a class of service from `expedite | fixed-date | standard |
140
+ intangible`; `expedite` lifts an item above every other ready item, an absent
141
+ value means `standard`, and an unrecognised value is ranked as standard and
142
+ reported in the report rather than dropped. `due` is an ISO calendar date
143
+ ordered by proximity. Both ride the existing extension-member channel, so no
144
+ core field carries them.
145
+ - The commit-per-mutation invariant is documented. On a provisioned ledger,
146
+ every mutation must be committed to Git before the next mutating command,
147
+ and `claim-verify` is the reconciliation procedure for the exit 6
148
+ `publication-reconciliation-required` refusal. The mutation contract, the
149
+ work-claim contract, the README, and the installed skill's claimed and
150
+ unclaimed loops all state the rule and the loop it implies. The mutation
151
+ contract also records why validating against working-tree bytes was
152
+ rejected.
153
+ - `claim capabilities --ledger <dir> --json` now advertises
154
+ `result.backend.write_serialization`. A provisioned Git-journal backend
155
+ reports `scope: "all-worktrees-of-one-repository"` and
156
+ `blocks_until: "peer-commit-visible-in-this-checkout"`; an unprovisioned
157
+ backend reports `scope: "none"`. This makes the serialization the shared
158
+ Git-common-directory journal already performed discoverable instead of
159
+ implied. The core `capabilities` envelope is unchanged; this change is not
160
+ one of the version 3 deltas.
161
+
162
+ - A `date-before-created` or `date-before-updated` issue now carries
163
+ `item_created` and `item_updated` after `related_ids` — the target item's own
164
+ dates at refusal time, both dates on both codes, on `transition` and `patch`
165
+ alike. One refusal now states the whole acceptable date window, so correcting
166
+ the request no longer costs an `inspect` round-trip. No other issue code
167
+ changes shape; a consumer that validates issue members exactly must accept
168
+ six members for these two codes. This widening is the reason the core
169
+ contract version moves to 3 (see Changed, above).
170
+ The mutation contract and the installed skill now also state that `create`
171
+ derives `created` from the ULID timestamp, which is UTC, with the
172
+ across-midnight example that produces this refusal.
173
+
174
+ ### Documentation
175
+
176
+ - **`create` stays journal-silent, and the work-claim contract now says why.**
177
+ Section 3.1 already stated that `create` records no claim-journal entry and
178
+ therefore never blocks a sibling worktree. It now records the decision to
179
+ keep that asymmetry and the three reasons behind it: create's publication is
180
+ already atomic, no-clobber, and byte-verified; journaling create would
181
+ serialize every worktree on the highest-volume mutation; and the remaining
182
+ exposure window closes at the item's first `transition` or `patch`. The
183
+ window is stated honestly — until that first journal-visible mutation an
184
+ out-of-protocol overwrite of a created item is not detected, and a commit
185
+ alone does not close the window, because reconciliation compares only the
186
+ revisions the journal expects. `test/create-journal-asymmetry.test.js` pins
187
+ both halves end to end. No behaviour changed.
188
+
189
+ ### Fixed
190
+
191
+ - A claim-fence refusal no longer reaches the agent as
192
+ `mutation-outcome-unknown`. Both adapter engines classified every response
193
+ with the core envelope validator, so a fenced refusal — `namespace:
194
+ "ledger-mutation"`, `command: "<command>-v1"`, `contract_version: 1`, `state:
195
+ "unchanged"` — failed core validation and became "the mutation may have been
196
+ applied; inspect current state before retrying", on every fenced refusal on a
197
+ provisioned ledger, about a write that provably never ran. The adapter now
198
+ dispatches on the response domain first, exactly as the mutation contract's
199
+ section 2 rule requires, and validates a fenced refusal on the work-claim
200
+ contract's terms: `claimed-item-write-refused` on `create`,
201
+ `active-claim-write-refused` on `transition` and `patch`, and
202
+ `claim-store-unavailable` on any mutation, each with its pinned message, exit,
203
+ and permitted states, its read-back bound to the item the caller asked to
204
+ write, and its reason plus findings and remediation forwarded verbatim. A
205
+ `claim-store-unavailable` refusal that declares `state: "unknown"` stays an
206
+ unknown outcome, and so does any namespaced envelope the adapter cannot
207
+ classify. Adapter contract section 6.1 states the rule and the honest-outcome
208
+ guarantee; five conformance vectors pin it, the differential test replays each
209
+ through both engines, and no version moved in either domain.
210
+ - A mutation on a claim-protected ledger now reads the complete ledger twice
211
+ instead of three times. Journal reconciliation and the mutation engine's
212
+ pre-lock phase were separate unlocked reads of the same directory inside one
213
+ claim-lock hold, and reconciliation writes nothing a complete load reads, so
214
+ the pre-lock phase reuses reconciliation's snapshot. On a 1,500-item
215
+ provisioned fixture a create fell from about 1.15 s to about 0.89 s and a
216
+ transition from about 1.17 s to about 0.91 s, matching one full load at about
217
+ 0.29 s. The read under lock stays: it is what decides the revision
218
+ compare-and-swap and the lock-closure stability check, and every decision
219
+ drawn from the shared snapshot is re-made against it. A lock-closure retry
220
+ still reads fresh. No validation rule changed, and a mutation on a plain
221
+ directory is unaffected.
222
+ - A mutation on a large provisioned ledger no longer spends its wall time in
223
+ process spawns. Git HEAD reconciliation read every committed item with its
224
+ own `git show`, one subprocess per item, serially; it now reads them with
225
+ one `git cat-file --batch` subprocess per 16 MiB of tree content. On a
226
+ 1,500-item fixture a create fell from about 15.4 s to about 1.2 s and a
227
+ transition from about 15.4 s to about 1.3 s. The reconciliation reads the
228
+ same bytes for the same items, and no validation is skipped: candidate
229
+ validation still validates the complete ledger.
230
+
231
+ - Every reconciliation finding that blocks a mutation now carries a
232
+ `remediation` string naming the path to act on and `claim-verify`.
233
+ `revision-regression`, `legacy-mutation-outcome-unknown`, and
234
+ `publication-outcome-unknown` previously blocked with no recovery action;
235
+ they now also carry `expected_path` when it is identifiable.
236
+
237
+ - A committed `.wowbagger/layout.json` now binds the ledger's item directory.
238
+ `create` derives its path from that configuration. Validation rejects parsed
239
+ items outside it, special or symbolic layout files, and metadata-directory
240
+ aliases. Malformed configuration fails closed. Ledgers without the file
241
+ retain the root-level `<id>.md` layout.
242
+ - A refused legacy mutation and a clean `claim-verify` now leave the ledger
243
+ working tree byte-identical. The tracked reconciliation log projects only
244
+ journal entries that record a decision, so per-invocation clock entries no
245
+ longer dirty it, and a successful legacy mutation now projects its own
246
+ entries before returning instead of one command later. Batch tooling no
247
+ longer needs to stage the log after a failure.
248
+ - `claim-verify` now classifies stale writes as unauthorized revisions, missing
249
+ Git finalization, worktree synchronization, or pending claimed publication.
250
+ Working-tree deletions of an authorized Git revision are unauthorized.
251
+ Findings name the expected item path and give a direct recovery action.
252
+ - The contracts and the skill now state that a provisioned ledger's claim
253
+ journal serializes every worktree of one repository, and that a recorded
254
+ write blocks mutations in the other worktrees until its commit is visible
255
+ there. `limits.cross_worktree_coordination: false` is documented as "the
256
+ core never synchronizes checkouts", not as independent worktree writes.
257
+ Both stale-write remedies, the moving-`expected_revision` trap, and the
258
+ failed copy-the-item-in workaround are documented and pinned by tests.
9
259
 
10
260
  ## 0.1.0-alpha.4 - 2026-08-14
11
261
 
package/README.md CHANGED
@@ -14,7 +14,7 @@ putting a database or hosted service inside your repository.
14
14
  > scope is deliberately narrow: cooperative writers in one working copy, one
15
15
  > item at a time. A Claude Code adapter and plugin ship from this
16
16
  > repository; the adapter answers the negotiation surface of the harness-neutral
17
- > contract and passes all 183 assertions across all 15 cases on native Darwin.
17
+ > contract and passes all 196 assertions across all 15 cases on native Darwin.
18
18
  > The Claude Code adapter declares Darwin `supported`; all other shipped adapter
19
19
  > platform declarations remain `unverified`. The shipped core mutation contract
20
20
  > and adapter contract are version 2; their frozen version 1 definitions are not
@@ -36,7 +36,7 @@ Install the core CLI, then verify it:
36
36
  ```sh
37
37
  npm install -g wowbagger@next # public npm prerelease
38
38
  # or, from this release's Git tag:
39
- # npm install -g github:lstutzman/wowbagger#v0.1.0-alpha.4
39
+ # npm install -g github:lstutzman/wowbagger#v0.1.0-alpha.5
40
40
  wowbagger capabilities --json
41
41
  ```
42
42
 
@@ -54,6 +54,38 @@ core contract version 2. It refuses an absent or incompatible core. It will not
54
54
  fall back to editing ledger files by hand, because that would bypass validation
55
55
  and atomic publication.
56
56
 
57
+ Decide where items live **before your first `create`**. A ledger publishes items
58
+ to `<ledger>/<id>.md` unless a committed `<ledger>/.wowbagger/layout.json` binds
59
+ a subdirectory:
60
+
61
+ ```sh
62
+ mkdir -p path/to/ledger/.wowbagger path/to/ledger/items
63
+ echo '{"layout_version":1,"items_directory":"items"}' > path/to/ledger/.wowbagger/layout.json
64
+ ```
65
+
66
+ `layout_version` must be `1` and `items_directory` names the committed item
67
+ directory. `create` then publishes atomically to `<items_directory>/<id>.md`,
68
+ and validation rejects parsed items outside it. Commit the directory the file
69
+ names — `create` publishes into an existing directory and does not make one.
70
+ Nothing is renamed after a create. Cores at `0.1.0-alpha.4` and earlier ignore
71
+ the file and publish every item at the ledger root.
72
+
73
+ On such a core, relocating an item by hand has a trap. `create` writes an
74
+ untracked file, so `git mv` refuses the path it just published; an unchecked
75
+ batch then runs `git add -A` and commits the item at the ledger root, where it
76
+ silently stays. Use plain `mv`, then `git add` both paths, and check the exit
77
+ code of every command before the commit:
78
+
79
+ ```sh
80
+ mv path/to/ledger/<id>.md path/to/ledger/items/<id>.md || exit 1
81
+ git add path/to/ledger || exit 1
82
+ ```
83
+
84
+ A committed item outside the configured items directory then fails validation
85
+ and refuses every read and every guarded mutation on that ledger, including
86
+ ones that never touch it. The refusal names the expected path and the
87
+ relocation that repairs it.
88
+
57
89
  For an isolated consumer pilot, create or select the disposable worktree before
58
90
  the agent starts. Then launch a new session with that worktree as its project
59
91
  root. Follow the [isolated dogfood pilot runbook](docs/isolated-dogfood-pilot.md);
@@ -72,7 +104,7 @@ two supported install routes:
72
104
  - **npm registry** — `npm install -g wowbagger@next` installs the current
73
105
  prerelease.
74
106
  - **git tag** —
75
- `npm install -g github:lstutzman/wowbagger#v0.1.0-alpha.4` installs this
107
+ `npm install -g github:lstutzman/wowbagger#v0.1.0-alpha.5` installs this
76
108
  release. Installing at a ref installs the core and every adapter that ref
77
109
  carries.
78
110
 
@@ -136,7 +168,7 @@ Upgrade the pieces you installed:
136
168
 
137
169
  ```sh
138
170
  npm install -g wowbagger@next # public npm registry
139
- npm install -g github:lstutzman/wowbagger#v0.1.0-alpha.4 # immutable Git release
171
+ npm install -g github:lstutzman/wowbagger#v0.1.0-alpha.5 # immutable Git release
140
172
  git pull && npm ci # or: a direct checkout
141
173
  ```
142
174
 
@@ -153,12 +185,14 @@ wowbagger capabilities --json
153
185
  ```
154
186
 
155
187
  The plugin requires its exact core distribution version and top-level core
156
- `contract_version: 2`. Direct API consumers must check the contract version
188
+ `contract_version: 3`. Direct API consumers must check the contract version
157
189
  they support; installed plugin users must also keep the plugin and core
158
190
  distribution versions equal.
159
191
 
160
192
  The shipped adapter selects only adapter contract version 2 and requires core
161
- contract version 2. A v1-only consumer receives
193
+ contract version 3. The adapter contract and the core contract are separate
194
+ version domains: the adapter stays at 2 while the core moves to 3. A v1-only
195
+ consumer receives
162
196
  `unsupported-adapter-contract-version`; it does not receive v2 behavior. The
163
197
  schema-2 transport is available. Ledger migration remains a separate quiesced
164
198
  maintenance operation. The
@@ -192,6 +226,11 @@ core, these are the changes most likely to touch you:
192
226
  `core.status: "triage"`, and the refusal for a caller-supplied `status`
193
227
  names the accepting transition (triage to backlog) that makes an item
194
228
  ready.
229
+ - **Bind a subdirectory layout in the ledger, not in each runner.** Commit
230
+ `<ledger>/.wowbagger/layout.json` with
231
+ `{"layout_version":1,"items_directory":"items"}`. `create` then derives
232
+ `<ledger>/items/<id>.md`; validation rejects parsed items outside `items/`.
233
+ Without the file, the compatible layout remains `<ledger>/<id>.md`.
195
234
 
196
235
  ## Why the name?
197
236
 
@@ -257,9 +296,9 @@ This checkout ships three adapter packages on one shared entrypoint runtime:
257
296
  [`adapters/claude-code/`](adapters/claude-code/), [`adapters/codex/`](adapters/codex/),
258
297
  and [`adapters/opencode/`](adapters/opencode/). Each answers the section 3.3 bootstrap
259
298
  wire with its own identity and honest host declaration. The native Darwin
260
- Claude Code report passes all 183 assertions across all 15 cases — run
299
+ Claude Code report passes all 196 assertions across all 15 cases — run
261
300
  `node spec/run-adapter-implementation.js` to see the evidence. Codex and
262
- OpenCode share the version 2 engine and execute all 183 assertions with
301
+ OpenCode share the version 2 engine and execute all 196 assertions with
263
302
  `--target codex` or `--target opencode`, but both target reports remain `fail`
264
303
  pending target-specific evidence. Invocation forwarding, path and limit guards,
265
304
  approval, and context all enter through the shared shipped engine. Platform
@@ -333,7 +372,9 @@ relative `--out` overrides resolve from the caller's working directory.
333
372
  "fields": {
334
373
  "area": "/priority_area",
335
374
  "complexity": "/complexity",
336
- "rank": "/priority_rank"
375
+ "rank": "/priority_rank",
376
+ "class": "/class",
377
+ "due": "/due"
337
378
  },
338
379
  "swarm": { "eligible_complexities": ["small", "medium"] }
339
380
  }
@@ -341,9 +382,65 @@ relative `--out` overrides resolve from the caller's working directory.
341
382
 
342
383
  `repository.logo`, `fields`, and `swarm` are optional. Field values resolve
343
384
  from parsed frontmatter with RFC 6901 JSON Pointers. A swarm requires mapped
344
- `area` and `complexity` fields. The report shows canonical readiness, filters,
345
- sorting, grouping, three detail levels, terminal history, and area-diverse
346
- ready batches. It contains no external runtime dependency.
385
+ `area` and `complexity` fields. The report fetches nothing at view time.
386
+
387
+ The report opens with **Work next**: the ready set in a recommended order,
388
+ each entry carrying the factors that placed it. Below it sit **Attention**
389
+ (blocked work naming its blockers, the oldest open work, and started work past
390
+ this ledger's own 85th-percentile cycle time), the **evidence layer**
391
+ (aging buckets, weekly arrivals against completions, accept-to-complete cycle
392
+ time, and a Monte Carlo forecast as 50 and 85 percent bands), and the **ledger
393
+ graph**. State counts, item cards, filters, grouping, detail levels, terminal
394
+ history, and area-diverse ready batches all remain, demoted below that
395
+ decision surface.
396
+
397
+ ### The ledger graph
398
+
399
+ Below the evidence layer the report draws the whole ledger as a force-directed
400
+ 3D graph. Every item is a node, labelled `#N`, coloured by readiness for open
401
+ items and by terminal status for closed ones, and sized by the same transitive
402
+ unblocking leverage the recommended order uses. Edges run from a prerequisite
403
+ or a parent to the item it releases: a `depends_on` edge is straight and
404
+ arrowed, a `parent` edge is curved and unarrowed. Hovering or clicking a node
405
+ opens a card with its number, title, status, age, leverage, and the same
406
+ reasons line the ranked list prints for it.
407
+
408
+ The renderer is [`3d-force-graph`](https://github.com/vasturiano/3d-force-graph)
409
+ over Three.js, vendored into `vendor/3d-force-graph/` at a pinned version
410
+ `1.80.0` with its upstream SHA-256 recorded in `VERSIONS.json` and pinned by a
411
+ test. It is inlined into the report at generation time. Nothing is fetched from
412
+ a CDN, at generation time or at view time, and the report's content security
413
+ policy forbids every remote load. The bundle costs roughly 1.3 MB of the
414
+ report's size; the report stays one self-contained file you can attach, open
415
+ offline, and share.
416
+
417
+ Without WebGL the graph section says so and expands its own roster instead: one
418
+ row per node, carrying that node's number, title, status, age, leverage, and
419
+ reasons. No decision-relevant content exists only in the 3D view.
420
+
421
+ The recommended order is a report-layer derivation. It is recomputed from
422
+ ledger bytes at render time, never persisted, never a mutation, and it does not
423
+ change `ready`: the core still selects and sorts by priority, created date,
424
+ then ID. Ordering runs as separate, visible steps rather than one opaque score
425
+ — expedite class, then due proximity, then transitive unblocking leverage over
426
+ `depends_on`, then epic enablement from `parent`, then priority, then age, then
427
+ the mapped `complexity` as a WSJF-style size denominator — and every step that
428
+ placed an entry is printed beside it.
429
+
430
+ Two mapped fields carry the value dimensions the schema deliberately does not:
431
+
432
+ - **`class`** — a class of service, one of `expedite`, `fixed-date`,
433
+ `standard`, or `intangible`. `expedite` lifts an item above every other ready
434
+ item. An absent value means `standard`. An unrecognised value is ranked as
435
+ standard and reported by number in the report, never silently dropped.
436
+ - **`due`** — an ISO calendar date. The nearest due date sorts first and an
437
+ overdue one sorts first of all; an item with no due date sorts behind every
438
+ dated one at that step.
439
+
440
+ Both ride the ordinary extension-member channel, so the core neither reads nor
441
+ validates them. `complexity` weighs `xs`/`s`/`small` as 1, `m`/`medium` as 2,
442
+ `l`/`large` as 3, and `xl`/`extra-large` as 5; any other value carries no
443
+ weight and is shown as written.
347
444
 
348
445
  This repository keeps its report configuration in
349
446
  `ledger/.wowbagger/report.json`. Generate the ignored local report with:
@@ -363,6 +460,23 @@ nothing else — under the same lock and compare-and-swap. See
363
460
  [the mutation contract](docs/mutation-contract.md) for the JSON request,
364
461
  response, recovery, and scope details.
365
462
 
463
+ ### Diagnosing an invalid ledger
464
+
465
+ One invalid item refuses every read and every guarded mutation on that ledger,
466
+ including commands that never touch it. That refusal is the diagnosis, and no
467
+ command asks you to parse the Markdown by hand:
468
+
469
+ - `validate --json` lists every error. An error whose repair the validator can
470
+ derive also carries `expected_path` and a `remediation` naming the repair.
471
+ - `inspect` still refuses with exit 3 `ledger-invalid` — a revision from an
472
+ unjudged ledger must never look like a mutation precondition — but the
473
+ refusal carries `error.details.item`, the complete snapshot of the item you
474
+ asked for, whenever no validation error names that item's path. A faulted
475
+ item is withheld; `validate` already names its repair.
476
+ - `claim-verify --json` reports `result.ledger_validation`. Exit 0 with
477
+ `findings: []` and `ledger_validation.valid: false` says the claim journal is
478
+ consistent and validation alone is blocking every mutation.
479
+
366
480
  `provision` binds one ledger namespace to the repository. `claim` manages
367
481
  durable acquire, read, renew, and release decisions. `publish-claimed` accepts
368
482
  the exact candidate item bytes and fences their publication against the active
@@ -383,6 +497,51 @@ refuses rather than guessing. The shipped plugin skill also requires its exact
383
497
  core distribution version. Direct checkout use—`./bin/wowbagger.js` from a
384
498
  clone—remains supported and is what this repository's own ledger uses.
385
499
 
500
+ ## Commit each mutation before the next one
501
+
502
+ On a **provisioned** ledger — one where `provision` has bound a namespace and
503
+ `claim capabilities` reports `mode: "merge-coordinated"` — there is one
504
+ operating rule:
505
+
506
+ **Commit each mutation to Git before running the next mutating command.**
507
+
508
+ The durable claim store validates every recorded mutation against Git `HEAD`,
509
+ not against working-tree bytes. That is what makes a recorded mutation durable
510
+ rather than a local edit one `git checkout` away from vanishing. An uncommitted
511
+ mutation is an unreconciled mutation, so the next `create`, `transition`, or
512
+ `patch` refuses instead of writing on top of it.
513
+
514
+ The loop that works:
515
+
516
+ ```sh
517
+ ./bin/wowbagger.js create --ledger path/to/ledger --input request.json --json
518
+ git add path/to/ledger && git commit -m "Record the mutation"
519
+ ./bin/wowbagger.js claim-verify --ledger path/to/ledger --json
520
+ ./bin/wowbagger.js transition --ledger path/to/ledger --input next.json --json
521
+ ```
522
+
523
+ Skip the commit and the next command returns exit 6:
524
+
525
+ ```json
526
+ {"ok":false,"namespace":"ledger-mutation","command":"create-v1","contract_version":1,
527
+ "state":"unchanged","error":{"code":"claim-store-unavailable",
528
+ "message":"The durable claim store is unavailable.",
529
+ "details":{"reason":"publication-reconciliation-required","findings":[{
530
+ "code":"stale-write-detected","reason":"git-finalization-required",
531
+ "expected_path":"wb_....md",
532
+ "remediation":"Commit wb_....md in Git, then run claim-verify."}]}}}
533
+ ```
534
+
535
+ `state: "unchanged"` is exact — nothing was written. **`claim-verify` is the
536
+ reconciliation procedure.** Read `details.findings`, do what each
537
+ `remediation` string says, run `claim-verify` until it returns exit 0, then
538
+ repeat the refused command.
539
+
540
+ Full rules, the other blocking finding codes, and why validating against
541
+ working-tree bytes was rejected are in
542
+ [the mutation contract](docs/mutation-contract.md) section 12 and
543
+ [the work-claim contract](docs/work-claim-contract.md).
544
+
386
545
  ## Verify a checkout
387
546
 
388
547
  The development workflow is intentionally self-hosted: edit code and ledger
@@ -486,7 +645,7 @@ It is the durable work ledger beneath those systems.
486
645
  - Stabilize the machine-readable command contract and compatibility evidence.
487
646
  - Ship Claude Code and Codex adapters. **Claude Code, Codex, and OpenCode
488
647
  packages share the version 2 engine; the Claude Code manifest declares Darwin
489
- `supported` after passing all 183 native assertions. Other adapter targets and
648
+ `supported` after passing all 196 native assertions. Other adapter targets and
490
649
  platform declarations remain unverified.**
491
650
  - Document the generic tool contract for other agent harnesses.
492
651
  - Implement merge-coordinated work claims for cooperating Git worktrees.
@@ -4,7 +4,7 @@
4
4
  "adapter_version": "0.1.0",
5
5
  "adapter_contract_versions": [2],
6
6
  "bootstrap_wire_version": 1,
7
- "required_core_contract_version": 2,
7
+ "required_core_contract_version": 3,
8
8
  "entrypoints": {
9
9
  "describe": {
10
10
  "kind": "command",
@@ -4,7 +4,7 @@
4
4
  "adapter_version": "0.1.0",
5
5
  "adapter_contract_versions": [2],
6
6
  "bootstrap_wire_version": 1,
7
- "required_core_contract_version": 2,
7
+ "required_core_contract_version": 3,
8
8
  "entrypoints": {
9
9
  "describe": {
10
10
  "kind": "command",
@@ -4,7 +4,7 @@
4
4
  "adapter_version": "0.1.0",
5
5
  "adapter_contract_versions": [2],
6
6
  "bootstrap_wire_version": 1,
7
- "required_core_contract_version": 2,
7
+ "required_core_contract_version": 3,
8
8
  "entrypoints": {
9
9
  "describe": {
10
10
  "kind": "command",