wowbagger 0.1.0-alpha.7 → 0.1.0-alpha.8

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 (51) hide show
  1. package/CHANGELOG.md +193 -1
  2. package/README.md +194 -16
  3. package/adapters/claude-code/wowbagger-adapter.json +1 -1
  4. package/adapters/codex/wowbagger-adapter.json +1 -1
  5. package/adapters/opencode/wowbagger-adapter.json +1 -1
  6. package/docs/adapter-contract.md +66 -6
  7. package/docs/host-contract.md +287 -0
  8. package/docs/mutation-contract.md +558 -17
  9. package/docs/work-claim-contract.md +56 -24
  10. package/package.json +14 -1
  11. package/schemas/bare-ready-result.json +27 -0
  12. package/schemas/bare-validation-result.json +23 -0
  13. package/schemas/common.json +470 -0
  14. package/schemas/core-capabilities-response.json +346 -0
  15. package/schemas/core-envelope.json +141 -0
  16. package/schemas/core-inspect-response.json +37 -0
  17. package/schemas/core-inspect-workbench-response.json +340 -0
  18. package/schemas/core-list-error-response.json +242 -0
  19. package/schemas/core-list-query.json +98 -0
  20. package/schemas/core-list-response.json +140 -0
  21. package/schemas/core-report-response.json +379 -0
  22. package/schemas/core-transition-error-response.json +852 -0
  23. package/schemas/core-transition-request.json +47 -0
  24. package/schemas/core-transition-response.json +49 -0
  25. package/schemas/index.json +108 -0
  26. package/schemas/ledger-mutation-refusal.json +288 -0
  27. package/schemas/report-config-v1.json +140 -0
  28. package/schemas/report-config-v2.json +242 -0
  29. package/skills/wowbagger/SKILL.md +151 -38
  30. package/src/adapter/core-probe.js +87 -7
  31. package/src/claim-capabilities.js +4 -5
  32. package/src/claim-coordinator.js +15 -2
  33. package/src/claim-prospective.js +151 -0
  34. package/src/claim-publication.js +49 -9
  35. package/src/claim-sync.js +66 -0
  36. package/src/cli.js +671 -49
  37. package/src/extension-provision.js +33 -0
  38. package/src/git-autocommit.js +36 -6
  39. package/src/git-reconciliation.js +53 -9
  40. package/src/launch.js +52 -0
  41. package/src/lifecycle.js +153 -0
  42. package/src/limits.js +39 -0
  43. package/src/list.js +365 -0
  44. package/src/mutation.js +145 -118
  45. package/src/projection.js +39 -0
  46. package/src/report-graph.js +122 -21
  47. package/src/report-html.js +219 -51
  48. package/src/report-view.js +189 -0
  49. package/src/report.js +145 -15
  50. package/src/validate.js +4 -1
  51. package/src/workbench.js +117 -0
package/CHANGELOG.md CHANGED
@@ -7,6 +7,160 @@ consolidation. The first tagged release inherits this file.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 0.1.0-alpha.8 - 2026-08-23
11
+
12
+ ### Added
13
+
14
+ - **A published launch seam for a host that cannot run a shell.** The package
15
+ now declares `exports`, and its main entry `wowbagger` exposes
16
+ `CORE_SCRIPT_PATH`, `MINIMUM_NODE_MAJOR`, and `resolveCoreLaunch(argv)`, which
17
+ returns the exact process tuple a direct-core host needs: an absolute Node
18
+ executable, an argument array whose first element is the absolute
19
+ `bin/wowbagger.js`, and `shell: false`. A host that resolves its own runtime
20
+ gets `resolveCoreLaunch(argv, { nodeExecutable })`, and a relative or bare
21
+ executable name is refused rather than left for PATH to answer. The script
22
+ path is also resolvable on its own as `wowbagger/wowbagger.js` for a host that
23
+ wants the path without importing anything. Deep imports that already worked —
24
+ `wowbagger/src/limits.js` and every other published path — keep resolving
25
+ through an explicit `"./*"` subpath. No command accepts, refuses, emits, or
26
+ writes anything different.
27
+
28
+ - **The machine contract ships as JSON Schema, not only as prose and fixtures.**
29
+ Seventeen JSON Schema 2020-12 documents under `schemas/` are published with
30
+ the package and resolvable as `wowbagger/schemas/<file>.json`, with
31
+ `schemas/index.json` naming each one's response domain and that domain's
32
+ version. They cover the core envelope and its four exact root shapes, the
33
+ capabilities envelope with every advertised limit as a constant, the two bare
34
+ results, the list query and its page and refusals, the default and workbench
35
+ inspect projections, the transition request, success, and every documented
36
+ refusal with its mutation state, the `ledger-mutation` fence refusals, report
37
+ configuration versions 1 and 2, and the report responses including a named
38
+ view. Every schema fixes its root members exactly and pins the version of its
39
+ own domain, so a core envelope refuses a namespaced refusal, a version 4
40
+ envelope, or an extra root member, and a version 1 report configuration
41
+ refuses a version 2 one. Exactness reaches the members too: an item core must
42
+ carry both relation lists, because the core view always emits them, and a
43
+ refusal raised before a commit is established pins its state to `unchanged`
44
+ rather than admitting the indeterminate state that only an interrupted write
45
+ can report. The validator is a test-only dependency; the runtime still has
46
+ exactly one dependency.
47
+
48
+ ### Documentation
49
+
50
+ - **`docs/host-contract.md` publishes the direct-core host boundary.** One
51
+ document now states what a UI plugin or other non-agent consumer needs and
52
+ what it must supply itself: the package resolution seam and the four-part
53
+ launch — Node.js 20 or later, an absolute Node executable, the absolute
54
+ `wowbagger.js`, an argument array, and `shell: false`; bounded stdin or a
55
+ host-created request file, never shell source and never inline unbounded argv
56
+ JSON; captured stdout and stderr; the owning-host path rule for a worktree, a
57
+ plain folder, direct SSH, and WSL, with no cross-runtime path guessing;
58
+ namespace-first response dispatch and the exit table; every advertised limit
59
+ with its exact value; the once-only dispatch sequence for a lost response; and
60
+ the seventeen packaged JSON Schemas by domain. It states that a missing
61
+ executable is a host-level result rather than malformed Wowbagger JSON, that
62
+ the host owns executable discovery, working directory, timeout, cancellation,
63
+ process-tree containment, stream caps, and routing, and that Wowbagger will
64
+ not add automatic transitions, mirrored ledger state, operation identity,
65
+ remote routing, or a daemon. The full `inspect` read is documented as
66
+ deliberately unbounded, with the reason. The README, the installed skill, and
67
+ `SPEC.md` section 10 point at it, and it ships in the npm package.
68
+ - **The public version and lifecycle vocabulary agrees with the runtime.**
69
+ `SPEC.md` section 10 said core mutation contracts 1 and 2 were defined and
70
+ that the runtime emitted version 2; it now says 1 through 5 and version 5. The
71
+ mutation contract's status line said versions 1, 2, and 3 with the runtime on
72
+ 3; it now says 1 through 5 and version 5. `deferred` has been a real status
73
+ since it shipped, but `SPEC.md` omitted it from the status field, the
74
+ lifecycle table, the transition table, the terminal-date invariants, the
75
+ decision-action list, and the terminal-decision table, and the mutation
76
+ contract omitted the `deferred` date from the lossless core view; all seven
77
+ now name it, along with the `resolve`, `defer`, and `undefer` decision actions
78
+ the validator has always accepted. No behaviour changed: these were prose
79
+ omissions, and the tests that guard them read the vocabularies from
80
+ `src/lifecycle.js` and `src/validate.js` rather than retyping them.
81
+ - **Response loss is a named contract instead of folklore.** The mutation
82
+ contract, the adapter contract, the README, and the installed skill now carry
83
+ the same sequence for a mutation whose response never arrived: dispatch once,
84
+ never replay, invalidate the inspected revision, reconnect, then re-read the
85
+ ledger. The mutation contract's new section 10 table separates the outcomes a
86
+ caller may act on — committed success, proven non-write, committed recovery,
87
+ unknown publication — from the two that establish nothing, a signalled or
88
+ timed-out transport and a missing envelope, and states that a later item state
89
+ never proves that the lost dispatch caused it. Adapter contract section 6.2
90
+ documents the `mutation_outcome: "unknown"` envelope and its per-command
91
+ `recovery` object exactly as the adapter emits them, and states that exit 4
92
+ `revision-conflict` is a proven non-write that is never relabelled response
93
+ loss. There is no operation ID, durable outcome store, or replay endpoint;
94
+ adding correlation requires a new contract decision. No command accepts,
95
+ refuses, emits, or writes anything different. Two conformance vectors now pin
96
+ the core's own exit-6 `write-outcome-unknown` and `post-commit-recovery-required`
97
+ envelopes at the adapter's process-outcome seam, taking the adapter vector set
98
+ to 212 assertions.
99
+ - **The exit tables state where `report` actually lands.** Both contracts filed
100
+ exit 1 as a bare-result-only condition and exit 3 as every invalid ledger,
101
+ while `report` answers an invalid ledger, an unreadable input, and a failed
102
+ publication at exit 1. The mutation contract now carries an exit 1 row naming
103
+ those codes, lists `report-config-invalid` and `report-view-not-found` in its
104
+ exit 2 row, and scopes exit 3 to every command except `report`; the host
105
+ contract's exit 1 and exit 3 conditions say the same. Two guards assert the
106
+ rows against the codes the runtime emits.
107
+
108
+ ### Changed
109
+
110
+ - **The report filters by facet groups instead of one value at a time.** The
111
+ drill-down's single-value mapped-field selects and its All/Ready/Blocked/
112
+ Ineligible buttons are gone. In their place is one group of checkbox chips per
113
+ dimension of the open set — Readiness, Status, Kind, and every configured
114
+ mapped field, so a mapped `class: bug` is a chip rather than a value hidden in
115
+ a dropdown. Values inside a group are alternatives, groups narrow each other,
116
+ and the search box is one more condition; every chip states the count it would
117
+ leave, measured against the search and the other groups but never against its
118
+ own, so two selections in one group cannot make their siblings read zero. A
119
+ visible result count, per-chip selected state, and `Clear filters` are new, and
120
+ opening a Work next or Attention row still clears whatever detached its card,
121
+ facets included.
122
+ - **The ledger graph filters by lifecycle status.** One chip group above the
123
+ stage carries the statuses the ledger holds, all selected, with `Select all`
124
+ and `Clear`. Deselecting a status drops its nodes, every link incident to one,
125
+ and their labels, reheats the layout in place, and takes a hidden node off the
126
+ hover card; the roster and node count follow the same selection, and an empty
127
+ selection draws an empty graph that says so. The legend, the WebGL-less
128
+ roster, camera interaction, and the no-fetch contract are unchanged, and
129
+ nothing here reads or writes the ledger.
130
+
131
+ ### Fixed
132
+
133
+ - **A report failure before publication says so, and never arrives causeless.**
134
+ An output path the filesystem cannot resolve — a `--out` under a regular
135
+ file, an unreadable directory on the way to it — was a raw runtime error
136
+ escaping into the command's catch-all, answered as `report-write-failed` with
137
+ empty `details` even though nothing had been rendered or replaced. It is now
138
+ `report-read-failed` with `details.operation` naming which resolution failed,
139
+ `details.path` naming the path the caller configured or passed, and
140
+ `details.cause` naming the filesystem's own code. An error no report path
141
+ throws on purpose still answers `report-write-failed`, because nothing
142
+ reached the output path, but it now carries `{operation, cause}` instead of
143
+ `{}`. Every `details.cause` is a bounded token — an error code, or the
144
+ error's kind when the runtime gave no code — so a publication failure no
145
+ longer republishes a runtime message, and the paths, credentials, and
146
+ run-specific values a message carries stay out of the envelope. Atomic
147
+ publication is unchanged: a report already at the selected path survives
148
+ every one of these refusals.
149
+ - **An empty named view stops blaming the reader.** A view whose criteria
150
+ matched nothing rendered the drill-down's `No items match these filters.` and
151
+ the graph's `No status is selected`, sending a reader to controls that could
152
+ not bring an item back. A named artifact with no items now states
153
+ `No ledger item matches this view's criteria.` and, in the graph, that it has
154
+ nothing to draw — visibly, without waiting for scripting. A report that holds
155
+ items keeps the filter and status copy, which is the honest answer when the
156
+ reader is the one who narrowed it, and the base report's bytes are unchanged.
157
+ - **`core-report-response.json` named a member the report never emitted.** The
158
+ published `ledger-invalid` refusal required `details.validation_errors`,
159
+ which is the mutation commands' member name; `report` has always emitted
160
+ `details.errors`. A consumer validating a real refusal against the shipped
161
+ schema failed on the runtime's own bytes. The schema now states `errors`, and
162
+ a live invalid-ledger report run is validated against it.
163
+
10
164
  ## 0.1.0-alpha.7 - 2026-08-18
11
165
 
12
166
  ### Changed
@@ -157,6 +311,44 @@ consolidation. The first tagged release inherits this file.
157
311
 
158
312
  ### Added
159
313
 
314
+ - **`inspect` answers a bounded per-item lifecycle affordance projection.**
315
+ `inspect --ledger <dir> (--id <id> | --number <n>) --workbench --as-of
316
+ YYYY-MM-DD --json` returns, from one complete validated ledger snapshot,
317
+ `result.workbench`: the projection version, the as-of date, the ledger
318
+ snapshot witness, an `observation` member, a bounded item summary, and one
319
+ `transition_options` entry for every lifecycle target the native edge table
320
+ allows out of that item's kind and status. Each option names its target
321
+ status, its generated decision action or `null`, whether a caller-supplied
322
+ summary and rationale are required, the minimum legal transition date
323
+ `max(created, updated)`, its observed enabled state, and the observed
324
+ precondition issues and multi-item blockers in the exact `transition`
325
+ vocabulary. A workbench can now show a person which transitions an item can
326
+ take without duplicating lifecycle logic and without submitting a mutating
327
+ probe.
328
+
329
+ The read is an observation, not a lease, and says so in the response:
330
+ `observation.authority` is `observed-snapshot` and `observation.rechecked_by`
331
+ names what a later `transition` rechecks under lock — revision, lock, claim
332
+ fence, reconciliation, and candidate validation. It writes no item, lock,
333
+ claim journal, reconciliation log, or Git state, and it takes no lock.
334
+ `transition` and the projection share one lifecycle definition
335
+ (`src/lifecycle.js`), and a differential guard dispatches every projected
336
+ option and every unadvertised target through the real mutation, so an
337
+ advertised affordance cannot drift from what the mutation does.
338
+
339
+ Every variable-size field is bounded and says what it left out: the projected
340
+ title, the relation lists, each option's issues and blockers, and the related
341
+ IDs inside an issue. `--workbench` requires `--as-of`, an unpaired `--as-of`
342
+ is refused rather than ignored, and an `inspect` invocation without
343
+ `--workbench` is byte-identical to before. `capabilities` advertises
344
+ `operations.inspect.workbench` and the three exact bounds
345
+ `max_workbench_title_characters`, `max_workbench_collection_entries`, and
346
+ `max_workbench_response_bytes`; the core contract version stays 5, and the
347
+ projection is negotiated by its own `projection_version`. An invalid ledger is
348
+ `ledger-invalid` at exit 3 with no projection attached, and a projection that
349
+ would exceed its response bound is refused whole with
350
+ `workbench-response-too-large` at exit 2.
351
+
160
352
  - **The conformance suite now measures real core outcomes end to end.** A new
161
353
  equivalence case, `16-core-outcome-e2e`, carries nine hand-authored scenarios
162
354
  that each run the direct real core in one isolated temporary workspace and,
@@ -570,7 +762,7 @@ consolidation. The first tagged release inherits this file.
570
762
  every read and every guarded mutation on that ledger, including ones that
571
763
  never touch the misplaced item, so the refusal has to say where the item
572
764
  belongs. The claim fence is not involved: `claim-verify` reports no finding
573
- on such a ledger, refuting the PropertyCompass2 PR #2184 claim that a
765
+ on such a ledger, refuting an earlier consumer report that a
574
766
  root-misplaced item makes the fence report `stale-write-detected` with
575
767
  `actual_revision: null`. Fixtures pin both configuration orders — layout
576
768
  bound first, and layout bound after the item was already committed at the
package/README.md CHANGED
@@ -20,8 +20,8 @@ agent to use those guarantees instead of hand-editing your Markdown.
20
20
 
21
21
  **Start here:** [install the core and set up a ledger](#start-here).
22
22
 
23
- > **Status: alpha, published, and self-hosted.** `0.1.0-alpha.7` is on npm under
24
- > the `next` tag and on this repository's `v0.1.0-alpha.7` tag. It is the
23
+ > **Status: alpha, published, and self-hosted.** `0.1.0-alpha.8` is on npm under
24
+ > the `next` tag and on this repository's `v0.1.0-alpha.8` tag. It is the
25
25
  > version this repository runs its own backlog on. The API is not frozen and the
26
26
  > version will move before a stable release.
27
27
  >
@@ -39,7 +39,7 @@ agent to use those guarantees instead of hand-editing your Markdown.
39
39
  > version is **3**. Three adapter packages ship — Claude Code, Codex, and
40
40
  > OpenCode — on one shared engine at adapter contract version 2. Only the Claude
41
41
  > Code adapter declares a `supported` platform, Darwin, from a native run of all
42
- > 210 conformance assertions across all 16 cases. Every other adapter and
42
+ > 212 conformance assertions across all 16 cases. Every other adapter and
43
43
  > platform declaration is `unverified`; do not infer support because the CLI
44
44
  > starts.
45
45
  >
@@ -60,8 +60,8 @@ Install the core CLI, then verify it:
60
60
  ```sh
61
61
  npm install -g wowbagger@next # public npm registry
62
62
  # or, from this release's Git tag:
63
- # npm install -g github:lstutzman/wowbagger#v0.1.0-alpha.7
64
- wowbagger --version # 0.1.0-alpha.7
63
+ # npm install -g github:lstutzman/wowbagger#v0.1.0-alpha.8
64
+ wowbagger --version # 0.1.0-alpha.8
65
65
  wowbagger capabilities --json
66
66
  ```
67
67
 
@@ -75,7 +75,7 @@ In Claude Code, add the plugin:
75
75
  The plugin drives the installed core rather than bundling one, so a mismatch is
76
76
  detectable instead of silent. Its skill reads `wowbagger --version` and
77
77
  `capabilities`; it requires the same distribution version as the plugin and core
78
- `contract_version: 4`. It refuses an absent or incompatible core. It will not
78
+ `contract_version: 5`. It refuses an absent or incompatible core. It will not
79
79
  fall back to editing ledger files by hand, because that would bypass validation
80
80
  and atomic publication.
81
81
 
@@ -253,7 +253,7 @@ two supported install routes:
253
253
  registry requires a `latest` tag), so a bare install resolves to the same
254
254
  bytes.
255
255
  - **git tag** —
256
- `npm install -g github:lstutzman/wowbagger#v0.1.0-alpha.7` installs this
256
+ `npm install -g github:lstutzman/wowbagger#v0.1.0-alpha.8` installs this
257
257
  release. Installing at a ref installs the core and every adapter that ref
258
258
  carries.
259
259
 
@@ -325,7 +325,7 @@ Upgrade the pieces you installed:
325
325
 
326
326
  ```sh
327
327
  npm install -g wowbagger@next # public npm registry
328
- npm install -g github:lstutzman/wowbagger#v0.1.0-alpha.7 # immutable Git release
328
+ npm install -g github:lstutzman/wowbagger#v0.1.0-alpha.8 # immutable Git release
329
329
  git pull && npm ci # or: a direct checkout
330
330
  ```
331
331
 
@@ -342,12 +342,12 @@ wowbagger capabilities --json
342
342
  ```
343
343
 
344
344
  The plugin requires its exact core distribution version and top-level core
345
- `contract_version: 4`. Direct API consumers must check the contract version
345
+ `contract_version: 5`. Direct API consumers must check the contract version
346
346
  they support; installed plugin users must also keep the plugin and core
347
347
  distribution versions equal.
348
348
 
349
349
  The shipped adapter selects only adapter contract version 2 and requires core
350
- contract version 4. The adapter contract and the core contract are separate
350
+ contract version 5. The adapter contract and the core contract are separate
351
351
  version domains: the adapter stays at 2 while the core moves to 4. A v1-only
352
352
  consumer receives `unsupported-adapter-contract-version`; it does not receive v2
353
353
  behavior. The schema-2 transport is available. Ledger migration remains a
@@ -463,8 +463,8 @@ TMPDIR=/tmp node spec/run-adapter-implementation.js --target codex
463
463
  TMPDIR=/tmp node spec/run-adapter-implementation.js --target opencode
464
464
  ```
465
465
 
466
- The native Darwin and Linux Claude Code reports each pass all 210 assertions across all 16
467
- cases and report `"status": "pass"`. Codex and OpenCode execute the same 210
466
+ The native Darwin and Linux Claude Code reports each pass all 212 assertions across all 16
467
+ cases and report `"status": "pass"`. Codex and OpenCode execute the same 212
468
468
  assertions through the same engine, but both target reports remain `"fail"`
469
469
  pending target-specific evidence, and every platform declaration on those two
470
470
  manifests stays `unverified`. The Kimi and OpenAI-compatible harness adapters
@@ -705,6 +705,35 @@ Repeating it creates no second commit, so a lost response and a failed commit
705
705
  recover the same way. [docs/mutation-contract.md](docs/mutation-contract.md)
706
706
  section 13 is the full contract.
707
707
 
708
+ ### When the response is lost
709
+
710
+ A mutation you dispatched can lose its response: the process is signalled or
711
+ times out, a stream arrives truncated, or the transport to the machine that owns
712
+ the ledger drops. None of that observes the ledger, so none of it says whether
713
+ the write applied.
714
+
715
+ Only a complete observed result establishes an outcome. Exit 0 with state
716
+ `committed` is a success; a complete refusal with state `unchanged` is a proven
717
+ non-write; exit 6 `post-commit-recovery-required` says the item is published and
718
+ cleanup remains; exit 6 `write-outcome-unknown` says publication was attempted
719
+ and the visible bytes are indeterminate. Anything else — signal, timeout,
720
+ truncated output, no envelope, no response at all — is unresolved.
721
+
722
+ Unresolved is answered by one sequence, never a retry: Dispatch once, never
723
+ replay, invalidate the inspected revision, reconnect, then re-read the ledger.
724
+ Re-reading is `validate` plus `inspect` of the ID you already know, compared
725
+ against what you observed before you dispatched. What you read back is current
726
+ ledger state; it never proves that the lost dispatch caused it. A person reviews
727
+ that comparison before any new mutation, and the new mutation is built on the
728
+ current revision, not resent.
729
+
730
+ Exit 4 is not response loss. A `revision-conflict` proves the write did not run;
731
+ re-inspect and decide again.
732
+
733
+ There is no operation ID and no replay endpoint, because nothing replays.
734
+ [docs/mutation-contract.md](docs/mutation-contract.md) section 10 is the full
735
+ contract.
736
+
708
737
  ### When the item was changed outside the protocol
709
738
 
710
739
  An `unauthorized-revision` finding means someone edited the item without going
@@ -771,6 +800,18 @@ graph**. State counts, item cards, filters, grouping, detail levels, terminal
771
800
  history, and area-diverse ready batches all remain, demoted below that decision
772
801
  surface.
773
802
 
803
+ The drill-down filters are **facet groups**: Readiness, Status, Kind, and one
804
+ group for every configured mapped field, each a fieldset of checkbox chips.
805
+ Values inside a group are alternatives and groups narrow each other, so
806
+ `ready` or `blocked` in Readiness plus `bug` in Class means ready-or-blocked
807
+ bugs; the search box is one more condition on the same answer. Every chip
808
+ carries the count it would leave, measured against the search and the other
809
+ groups but never against its own, so two selections in one group cannot make
810
+ their siblings read zero. The result count states how much of the open set is
811
+ showing, and **Clear filters** gives every selection back. A Work next or
812
+ Attention row still reaches its card: opening one clears the facets and the
813
+ search first, because a row that names an item promises the item can be seen.
814
+
774
815
  The evidence layer is inline SVG, drawn at generation time and embedded in the
775
816
  file: an aging heatmap, weekly arrivals against completions, throughput with a
776
817
  four-week mean, a cumulative flow area, accept-to-complete cycle time, and a
@@ -789,6 +830,14 @@ arrowed, a `parent` edge is curved and unarrowed. Hovering or clicking a node
789
830
  opens a card with its number, title, status, age, leverage, and the same
790
831
  reasons line the ranked list prints for it.
791
832
 
833
+ Above the stage sits one chip group of the lifecycle statuses the ledger holds,
834
+ all selected, with **Select all** and **Clear**. Deselecting a status drops its
835
+ nodes, every link that touched one of them, and their labels together, then
836
+ reheats the layout in place: nothing is reloaded and the ledger is never
837
+ touched. The roster and the node count follow the same selection, and clearing
838
+ every status draws an empty graph that says it is empty rather than quietly
839
+ showing the last one.
840
+
792
841
  The renderer is [`3d-force-graph`](https://github.com/vasturiano/3d-force-graph)
793
842
  over Three.js, vendored into `vendor/3d-force-graph/` at a pinned version
794
843
  `1.80.0` with its upstream SHA-256 recorded in
@@ -827,6 +876,134 @@ validates them. `complexity` weighs `xs`/`s`/`small` as 1, `m`/`medium` as 2,
827
876
  `l`/`large` as 3, and `xl`/`extra-large` as 5; any other value carries no
828
877
  weight and is shown as written.
829
878
 
879
+ ### Named custom report views
880
+
881
+ A named custom view is a second self-contained report generated from the same
882
+ complete ledger. Every section of it — statistics, **Work next**, **Attention**,
883
+ the evidence layer, the graph, the open-item drill-down, terminal history, and
884
+ the swarm batches — describes one configured subset, so the file is honest to
885
+ share as a scoped report. Excluded items are absent from the bytes rather than
886
+ hidden by a stylesheet. The base report stays available and unchanged.
887
+
888
+ Report configuration `report_version: 2` accepts every version 1 member and adds
889
+ one more, `views`. A version 1 configuration keeps generating its base report
890
+ unchanged, and a version 2 configuration with no view selected publishes the
891
+ same base report from its inherited base members:
892
+
893
+ ```json
894
+ {
895
+ "report_version": 2,
896
+ "repository": { "name": "Example repository" },
897
+ "title": "Ledger report",
898
+ "output": "../../ledger-report.html",
899
+ "fields": {
900
+ "area": "/priority_area",
901
+ "complexity": "/complexity",
902
+ "class": "/class",
903
+ "security": "/security"
904
+ },
905
+ "views": {
906
+ "security-blockers": {
907
+ "title": "Security blockers",
908
+ "output": "../../reports/security-blockers.html",
909
+ "filters": {
910
+ "readiness": ["blocked"],
911
+ "status": ["backlog", "in-progress"],
912
+ "kind": ["task"],
913
+ "fields": {
914
+ "class": ["bug"],
915
+ "security": ["high", "critical"]
916
+ }
917
+ }
918
+ }
919
+ }
920
+ }
921
+ ```
922
+
923
+ Generate one view by name:
924
+
925
+ ```sh
926
+ wowbagger report --ledger <dir> --view <name> --as-of YYYY-MM-DD --json
927
+ ```
928
+
929
+ One invocation generates one artifact; no flag generates them all. `--out <file>`
930
+ overrides the selected output for that invocation, view or base alike, and the
931
+ configured paths are still validated when it is present.
932
+
933
+ A view name is a portable identifier matching `^[a-z][a-z0-9-]{0,63}$`. Names are
934
+ case-sensitive and `views` holds at most 64 views. Each view takes exactly
935
+ `title`, `output`, and `filters`. Unknown members fail closed.
936
+
937
+ `filters` takes exactly `readiness`, `status`, `kind`, and `fields`, and at least
938
+ one of `readiness`, `status`, `kind`, or `fields` must be present. The semantics
939
+ are the drill-down's: **OR within one filter group; AND across groups**, so
940
+ `readiness: ["blocked"]` with `class: ["bug"]` means blocked bugs. `readiness`
941
+ takes `ready`, `blocked`, or `ineligible`; `status` takes `triage`, `backlog`,
942
+ `in-progress`, `done`, `killed`, `archived`, or `deferred`; `kind` takes `task`
943
+ or `epic`.
944
+
945
+ A `fields` key must also be a configured report field. Each field filter is a
946
+ non-empty array of unique JSON strings, finite numbers, or booleans, and matching
947
+ preserves JSON scalar type and value: stringification is not equality, so a
948
+ mapped `2` does not answer a filter for `"2"`. An item carrying no mapped value
949
+ for a field matches no value selected for that field. No title-text inference,
950
+ regular expression, arbitrary JSON pointer, or body search belongs in a view
951
+ filter.
952
+
953
+ Wowbagger validates the complete ledger and computes readiness against the
954
+ complete ledger before it filters, so excluding a blocker never makes blocked
955
+ work read as ready. What a view derives from its retained set does change:
956
+ statistics, ranking leverage, epic enablement, evidence, attention, graph
957
+ membership, and the swarm batches are all view-scoped, so the same item can
958
+ report smaller numbers here than in the base report. That is why the artifact
959
+ names its view and its fixed criteria at the top. A retained item that names an
960
+ excluded dependency, parent, blocker, or related item still prints that item's
961
+ number instead of a raw ULID, and the excluded item gets no row, no graph node,
962
+ no history entry, and no hidden payload.
963
+
964
+ Inside the file, the interactive facets and the graph status chips narrow the
965
+ retained subset further and can never reveal an excluded item. **Clear filters**
966
+ restores the complete custom-view subset, never the base ledger.
967
+
968
+ The base output and every view output must be pairwise distinct after path
969
+ resolution, and each must resolve outside the ledger under the same no-follow
970
+ containment rule the base output already obeys. A colliding or contained output
971
+ is `report-config-invalid` before anything is rendered, `--out` present or not.
972
+
973
+ A named success adds exactly one member to the existing report result,
974
+ `result.view`, and its `item_count` and `ready_count` describe the filtered
975
+ subset. A base report's result gains no `view` member:
976
+
977
+ ```json
978
+ {
979
+ "ok": true,
980
+ "command": "report",
981
+ "contract_version": 5,
982
+ "result": {
983
+ "report_version": 2,
984
+ "as_of": "2026-08-21",
985
+ "output": "/absolute/reports/security-blockers.html",
986
+ "item_count": 12,
987
+ "ready_count": 0,
988
+ "view": "security-blockers"
989
+ }
990
+ }
991
+ ```
992
+
993
+ `--view` requires `report_version: 2`. A missing or unknown name is
994
+ `report-view-not-found` at exit 2 and leaves every existing output untouched. An
995
+ invalid filter value, an unmapped `fields` key, and a colliding output are
996
+ `report-config-invalid` at exit 2. An empty matched subset is not a failure: it
997
+ publishes a valid report with zero items and explicit empty-state copy. Failed
998
+ publication preserves the prior artifact, so never read an existing output as
999
+ fresh. `wowbagger capabilities --json` advertises the whole surface at
1000
+ `result.operations.report`, so no consumer has to probe by generating a file.
1001
+
1002
+ A custom view is scoped output and **not a security boundary**: it applies no
1003
+ redaction and no access control, and the artifact states plainly that it is a
1004
+ filtered subset of the named repository ledger. Automation reads the JSON result,
1005
+ never the generated HTML and never human output.
1006
+
830
1007
  This repository keeps its report configuration in
831
1008
  `ledger/.wowbagger/report.json`. Generate the ignored local report with the
832
1009
  current UTC date:
@@ -846,6 +1023,7 @@ they disagree with anything above:
846
1023
  | [docs/mutation-contract.md](docs/mutation-contract.md) | Response domains and dispatch, capabilities, inspect, create, transition, patch, the extension declaration, the frontmatter ownership table, errors and recovery, and commit-per-mutation. |
847
1024
  | [docs/work-claim-contract.md](docs/work-claim-contract.md) | Provisioning, claim CAS rules, claimed publication, reconciliation, revision adoption, and the difference between strict fenced and merge-coordinated backends. |
848
1025
  | [docs/adapter-contract.md](docs/adapter-contract.md) | The harness adapter boundary: negotiation, forwarding, guards, approval, and honest outcome mapping. |
1026
+ | [docs/host-contract.md](docs/host-contract.md) | The direct-core host boundary: package resolution, the shell-free process tuple, bounded transport, owning-host paths, response dispatch, advertised limits, and the packaged JSON Schemas. |
849
1027
  | [docs/openai-compatible-integration.md](docs/openai-compatible-integration.md) | What an OpenAI-compatible host can do today, and what a compatibility claim would require. |
850
1028
  | [docs/schema-2-migration.md](docs/schema-2-migration.md) | The quiesced schema version 1 to 2 migration runbook. |
851
1029
  | [skills/wowbagger/SKILL.md](skills/wowbagger/SKILL.md) | The shipped agent instructions. |
@@ -1011,10 +1189,10 @@ It is the durable work ledger beneath those systems.
1011
1189
  **Shipped: the policy-input contract and the report's mapped fields keep
1012
1190
  consumer vocabulary out of the schema.**
1013
1191
  - Stabilize the machine-readable command contract and compatibility evidence.
1014
- **In progress at core contract version 4; the version is not frozen.**
1192
+ **In progress at core contract version 5; the version is not frozen.**
1015
1193
  - Ship Claude Code and Codex adapters. **Claude Code, Codex, and OpenCode
1016
1194
  packages share the version 2 engine; the Claude Code manifest declares Darwin
1017
- `supported` after passing all 210 native assertions. Other adapter targets and
1195
+ `supported` after passing all 212 native assertions. Other adapter targets and
1018
1196
  platform declarations remain unverified.**
1019
1197
  - Document the generic tool contract for other agent harnesses. **Shipped as the
1020
1198
  adapter contract and the OpenAI-compatible integration guide.**
@@ -1023,8 +1201,8 @@ It is the durable work ledger beneath those systems.
1023
1201
  publication, Git reconciliation, and explicit revision adoption. It
1024
1202
  deliberately reports `safe_exclusive_dispatch: false`; direct writes and other
1025
1203
  uncoordinated paths remain bypasses.**
1026
- - Treat any PropertyCompass adoption as a later, separately-scoped consumer
1027
- project. **Recorded as its own ledger item; not a wowbagger deliverable.**
1204
+ - Keep consumer-specific adoption and migration outside the core Wowbagger
1205
+ project. **They are not Wowbagger deliverables.**
1028
1206
 
1029
1207
  ## License
1030
1208
 
@@ -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": 4,
7
+ "required_core_contract_version": 5,
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": 4,
7
+ "required_core_contract_version": 5,
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": 4,
7
+ "required_core_contract_version": 5,
8
8
  "entrypoints": {
9
9
  "describe": {
10
10
  "kind": "command",