@staix/agent-hub 0.30.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +13 -7
  3. package/docs/agent-notes/README.md +1 -0
  4. package/docs/agent-notes/adapters.md +5 -2
  5. package/docs/agent-notes/budget.md +1 -0
  6. package/docs/agent-notes/bus.md +11 -4
  7. package/docs/agent-notes/daemon.md +16 -8
  8. package/docs/agent-notes/local-worker.md +1 -0
  9. package/docs/agent-notes/tasks.md +7 -3
  10. package/docs/agent-notes/tests.md +6 -4
  11. package/docs/design/adapters.md +52 -0
  12. package/docs/design/agent-hub-architecture.md +46 -0
  13. package/docs/design/bus.md +178 -1
  14. package/docs/design/task-board.md +82 -0
  15. package/docs/dogfood.md +23 -4
  16. package/docs/events.md +3 -0
  17. package/docs/memory.md +17 -1
  18. package/docs/operations.md +355 -78
  19. package/docs/profiles.md +35 -0
  20. package/docs/quickstart.md +24 -5
  21. package/docs/security.md +178 -60
  22. package/docs/specs/2026-09-19-agent-hub-design.md +127 -14
  23. package/docs/specs/2026-09-19-multi-project-design.md +7 -2
  24. package/docs/verification/2026-11-04-1171-channel-client-evidence.md +31 -0
  25. package/docs/verification/2026-11-05-0.31.0.md +182 -0
  26. package/docs/verification/2026-11-05-claude-implementer-2.1.318.md +70 -0
  27. package/docs/verification/2026-11-05-hardcoded-values-classification-rules.md +311 -0
  28. package/docs/verification/2026-11-05-hardcoded-values-ratchet.md +55 -0
  29. package/docs/verification/2026-11-06-0.32.0.md +149 -0
  30. package/docs/verification/2026-11-06-rooms-presence-cost.md +152 -0
  31. package/docs/verification/claude-shell-wrapper-native.py +1 -1
  32. package/docs/verified.json +14 -14
  33. package/package.json +3 -4
  34. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  35. package/plugins/agent-hub/server.js +4393 -3609
  36. package/src/adapters/acp.ts +5 -4
  37. package/src/adapters/channel-peer.ts +72 -3
  38. package/src/adapters/claude-channel-evidence.ts +52 -0
  39. package/src/adapters/claude-channel.ts +61 -18
  40. package/src/adapters/claude-implementer.ts +29 -7
  41. package/src/adapters/claude-print-child.ts +12 -2
  42. package/src/adapters/codex-appserver.ts +13 -12
  43. package/src/adapters/compat.ts +3 -3
  44. package/src/adapters/definitions/builtin-metadata.ts +13 -13
  45. package/src/adapters/definitions/qwen.ts +1 -0
  46. package/src/adapters/local-worker.ts +61 -18
  47. package/src/adapters/pi.ts +48 -6
  48. package/src/adapters/registry-types.ts +12 -0
  49. package/src/adapters/registry.ts +2 -0
  50. package/src/adapters/reviewer-print.ts +50 -11
  51. package/src/broker/server.ts +44 -13
  52. package/src/broker/store.ts +38 -8
  53. package/src/cli/answer-args.ts +50 -0
  54. package/src/cli/attention.ts +17 -2
  55. package/src/cli/backlog-return-args.ts +37 -0
  56. package/src/cli/console-question.ts +110 -0
  57. package/src/cli/console-state.ts +78 -17
  58. package/src/cli/console.ts +54 -1
  59. package/src/cli/handoff-args.ts +15 -5
  60. package/src/cli/handoff-output.ts +15 -2
  61. package/src/cli/help.ts +8 -5
  62. package/src/cli/identity.ts +13 -3
  63. package/src/cli/main.ts +128 -16
  64. package/src/cli/memory.ts +107 -3
  65. package/src/cli/output.ts +29 -6
  66. package/src/cli/pet.ts +4 -4
  67. package/src/cli/results-args.ts +30 -0
  68. package/src/cli/room.ts +20 -10
  69. package/src/cli/runtime-version-warning.ts +50 -0
  70. package/src/cli/status-lines.ts +41 -1
  71. package/src/cli/tail-render.ts +1 -0
  72. package/src/cli/upgrade-interactive.ts +14 -2
  73. package/src/hub/action-class.ts +9 -1
  74. package/src/hub/board.ts +369 -18
  75. package/src/hub/bus.ts +511 -53
  76. package/src/hub/completion-detail.ts +50 -0
  77. package/src/hub/completions-store.ts +168 -0
  78. package/src/hub/completions.ts +120 -0
  79. package/src/hub/config-apply.ts +7 -5
  80. package/src/hub/config-reload.ts +10 -1
  81. package/src/hub/control-client.ts +40 -4
  82. package/src/hub/daemon.ts +1576 -139
  83. package/src/hub/delivery-failure.ts +14 -0
  84. package/src/hub/delivery-hints.ts +14 -4
  85. package/src/hub/delivery-journal.ts +751 -42
  86. package/src/hub/delivery-superseded.ts +4 -1
  87. package/src/hub/envelope.ts +3 -3
  88. package/src/hub/events.ts +6 -0
  89. package/src/hub/handoff.ts +145 -6
  90. package/src/hub/hub-tools.ts +56 -7
  91. package/src/hub/manager.ts +59 -3
  92. package/src/hub/memory-policy.ts +44 -1
  93. package/src/hub/memory-sync-policy.ts +109 -0
  94. package/src/hub/memory-sync-state.ts +145 -0
  95. package/src/hub/no-writes.ts +1 -1
  96. package/src/hub/operational-settings.ts +11 -2
  97. package/src/hub/operational-view.ts +12 -1
  98. package/src/hub/peers.ts +25 -2
  99. package/src/hub/planning.ts +6 -1
  100. package/src/hub/question-desk.ts +201 -0
  101. package/src/hub/question-detach.ts +6 -0
  102. package/src/hub/questions.ts +260 -0
  103. package/src/hub/research-evidence.ts +106 -0
  104. package/src/hub/restart.ts +14 -0
  105. package/src/hub/review-anchor.ts +181 -0
  106. package/src/hub/review-brief.ts +13 -0
  107. package/src/hub/review-comments.ts +115 -0
  108. package/src/hub/review-findings.ts +7 -2
  109. package/src/hub/review-material.ts +76 -0
  110. package/src/hub/routing.ts +3 -1
  111. package/src/hub/task-sweep.ts +2 -0
  112. package/src/hub/tasks.ts +577 -63
  113. package/src/memory/capture.ts +5 -2
  114. package/src/pet/config.ts +25 -13
  115. package/src/rooms/commands.ts +61 -16
  116. package/src/rooms/link.ts +338 -36
  117. package/src/rooms/liveness.ts +11 -0
  118. package/src/rooms/remote-peer.ts +186 -19
  119. package/src/rooms/service.ts +475 -35
  120. package/src/rooms/status.ts +8 -1
  121. package/src/rooms/store.ts +447 -65
  122. package/src/rooms/wire.ts +24 -6
  123. package/src/ui/index.html +142 -9
  124. package/src/ui/needs.ts +10 -4
  125. package/src/ui/plugin-questions.ts +56 -0
  126. package/src/ui/plugin-results.ts +81 -0
  127. package/src/ui/plugin.ts +207 -27
  128. package/src/pet/window.html +0 -114
  129. package/src/pet/window.jxa.js +0 -122
  130. package/src/pet/window.ts +0 -154
@@ -0,0 +1,311 @@
1
+ # Hard-coded values inventory: how a key is classified (#1180, task #738)
2
+
3
+ Date: 2026-11-05. Refs #1180. `scripts/inventory-hardcoded-values.classifications.json` is plain JSON that the generator parses as a map from key to class, so it has no place for a header: this file is that header. It was written **before** any key of the baseline was labelled, from two sources only: the K01 to K18 reasons of [the closure](2026-10-30-operational-settings-closure.md) and the finding of [the ratchet record](2026-11-05-hardcoded-values-ratchet.md) that a reader who knew only the closure agreed with the committed file on 58% of a sample. It is not fitted to the sample that checks it.
4
+
5
+ A key names one literal in one function (`file:function:kind:name=value#n`). Ask the questions in order and stop at the first that answers. The class says **why the value is a constant**, not how important it is.
6
+
7
+ 1. **Test seam.** The literal is the fallback of an injection point that exists for tests, and the code or its name says so: a `/** Test seam ... */` option, a `*ForTest` helper, a port a test replaces, or an environment variable that only a test, a gate or a benchmark sets (`AHUB_CHECK_HANG_S`, `AHUB_*_MS` knobs read with `${VAR:-N}` or `process.env.X ?? N`). A default parameter (`ms = 90000`, `limit = 20`) or a bare `opts.x ?? N` is **not** a test seam: see rule 5.
8
+ 2. **Contract.** The value is part of a representation, a wire or storage shape, or an accepted API: a protocol or schema number, a format or identifier width, a unit conversion (`1000`, `86400000`, `60`), an index or position in a stored or parsed structure, a bound that is part of a control message's or a tool's documented shape, `?? 0` or `Math.max(x, 1)` that gives the meaning of an absent or zero stored value, and a default that is **exported or published**: the default of a configuration key (`DEFAULT_CONFIG`, a `*_SETTING.defaultValue`), a tool schema's `minimum` or `maximum`, a declared model context size. A default that only lives in the function that reads it is not published: it is operational (rule 5). Changing a contract changes what stored or exchanged data means.
9
+ 3. **Safety floor.** The value bounds what comes from outside the hub's own control, or what could exhaust it: a cap on a path, name, id, command, request, model or foreign-hub text, a truncation of such text before it is shown or stored, a limit on a parser, a proof or a diagnostic read, a native-session ownership record limit. Changing it changes what untrusted input the hub accepts or shows.
10
+ 4. **Operational.** Everything else that tunes how the hub behaves without changing what its data means or what it accepts: timeouts, deadlines, polling and batching intervals, retry and backoff numbers, retention and window lengths, TTLs, hub-internal caps on its own queues, caches and lists, display and page sizes, benchmark and smoke-script deadlines and sizes, the numbers inside a backoff expression.
11
+ 5. **The three confusable cases, which the first reader got wrong** (the committed file treats each as operational unless rule 1 to 3 applies on its own):
12
+ - a **hub-internal cap** on the hub's own lists, queues, caches or batches (`slice(0, 50)` of the hub's own rows, a queue cap) is operational, not a safety floor. A cap on text that came from a peer, a model or another project is a safety floor.
13
+ - an **injected default** (`opts.x ?? N`, `config.x ?? N`, `input.x ?? N`) and a **default parameter** (`function f(ms = 90000)`, `list(limit = 20)`) are operational: the injection point or the parameter makes the value tunable, it does not make it a seam. They are a test seam only under rule 1 (an environment knob or a documented test seam), and a contract only under rule 2 (exported or published).
14
+ - a **script or smoke deadline** (`scripts/`, `AbortSignal.timeout(N)`, a benchmark's window or size) is operational.
15
+
16
+ A key that none of these decides is **not labelled**: it stays in the baseline and is reported for a ruling. The rules are the reason a label is written, so a label that needs a new rule adds that rule here first.
17
+
18
+ ## How a batch is checked
19
+
20
+ Before a batch is written, a fresh blind sample of 80 already-decided keys is drawn at random with a recorded seed, each is labelled from the code with only these rules (the committed label is not shown), and the labels are compared with the file. The batch is written only at 85% agreement or more, and the figure and the disagreements go in the batch's PR. A sample that fails the bar sends the disagreement back to these rules before any key is labelled.
21
+
22
+ ## What a batch labels, and what it leaves in the baseline
23
+
24
+ A batch labels only shapes where the committed file is consistent with these rules: module constants, timers, deadlines and `Math.min`/`Math.max` clamps, injected defaults, script and benchmark values, and bounds and truncations in `src/adapters`, `src/models`, `scripts/` and `src/local`. The baseline keeps every length bound and every truncation (`.slice`, `.substring`) in `src/hub`, `src/cli`, `src/ui`, `src/rooms` and `src/broker`: the file labels those inconsistently (in `src/hub`, 91 safety floor, 61 contract and 63 operational for length bounds), so no reading of the code can reach the bar there, and their convention is a separate decision by the conductor (the working rule: a bound on text, ids or input from a peer, model or another project is a safety floor; a wire-shape limit on our own protocol is a contract; the hub's own list caps are operational). The sample that checks a batch is drawn only from the same population.
25
+
26
+ ## Calibration record
27
+
28
+ | Round | Seed | Population | Reader | Agreement | Result |
29
+ | --- | --- | --- | --- | ---: | --- |
30
+ | 1 | 738001 | every decided key | blind reader, these rules | 62 of 80 (77.5%) | below the 85% bar: 18 misses, 12 of them in length bounds, truncations and defaults; the rules were corrected for default parameters and environment knobs and the population narrowed |
31
+ | 2 | 738002 | decided keys outside the `src/hub`, `src/cli`, `src/ui`, `src/rooms`, `src/broker` length bounds and truncations | blind reader, corrected rules | 56 of 80 (70.0%) | below the bar: 24 misses (below) |
32
+ | 3 | 738005 | the 303 decided keys the mechanical rules below cover | the mechanical rules | 78 of 80 (97.5%) | passes, **in-sample**: the rules were written after reading the file's labels |
33
+
34
+ Round 2 misses, by cause (the starting evidence for #739): 10 literals read as a contract that the file calls operational (a `= 0` reduce start, a `?? 2048` max-tokens default, config-shaped `*_s = 90` defaults, a `timeout_s > 86_400` bound, `hops = 2`, `id = 0`, `start = 0`); 7 read as operational that the file calls a safety floor (200-character error and output truncations, `Math.min(8000, ...)` timeout clamps); 4 read as operational that the file calls a test seam (default parameters such as `timeoutMs = 2000`, `idleTimeoutMs = 600_000`, `opts.pollMs ?? 2000`); 2 read as a safety floor that the file calls operational (`MAX_FILES = 4096`, `totalBytes = 512 MiB`); 1 read as operational that the file calls a contract. The file itself is internally consistent only 78 to 88% on these shapes (default-parameter timeouts 78%, `Math.min(<ms>, ...)` 87%, constants named `MAX_*` 50%), and for `src/hub` length bounds and truncations it is 42% and 48%, so no reading of the code reaches 85% on them.
35
+
36
+ ## The mechanical rules of the first batch (#738)
37
+
38
+ A key is labelled by a rule only when the rule is a pure function of its name, kind, value and file, and the committed file agrees with that rule on at least 94% of the keys it already decides. The rules, with how the file agrees with each over every decided key (the whole file, not the sample):
39
+
40
+ | Rule | Label | Decided keys it covers | File agrees |
41
+ | --- | --- | ---: | ---: |
42
+ | R1 a `constant` named `*_MS`, except unit conversions (`DAY_MS`, `MIN_MS`, ...), `*DATE_MAX_MS`, `*WINDOW_MS` and the security exclusion R1x below | operational | 51 | 94% |
43
+ | R1x a `*_MS` constant that bounds replay, revocation or rate: the window in which a replayable request is still accepted, how long a revoked or rotated credential stays valid, or the floor on how often an agent turn may be triggered | safety floor | 0 | none |
44
+ | R2 an inline literal that is the argument of `setTimeout`, `setInterval`, `Bun.sleep`, `sleep`, `AbortSignal.timeout` or `this.backOff` | operational | 134 | 98% |
45
+ | R3 an inline default named `*_s`, `*_min` or `*_ms` (a config-shaped value) | operational | 37 | 100% |
46
+ | R5 a `0` or `1` that starts a reduce, loop or counter (`start`, `id`, `count`, `sum`, `i`, `*.reduce`) | operational | 8 | 100% |
47
+ | R6 a `.slice` or `.substring` of error, output or message text in `scripts/benchmarks/` | safety floor | 28 | 96% |
48
+
49
+ R1x is the conductor's ruling on the review of PR #1212, not a calibrated rule: the committed file had no decided key of that kind (its decided `*GRACE_MS` keys are process-kill grace periods and `RATE_LIMIT_MS` throttles a log line, all operational), so its agreement is not measured. It labels four baseline keys: `tool-request.ts` `MAX_FUTURE_MS` (the replay window of request ids), `broker/store.ts` and `rooms/store.ts` `GRACE_MS` (how long a rotated epoch's frames stay accepted, which bounds how long a revoked member's key still works) and `schedules.ts` `MIN_EVERY_MS` (the floor that stops a schedule triggering an agent turn more often than every 15 minutes, a spend guard).
50
+
51
+ R4 (a `maxTokens` or `contextWindow` default, 97%) and R7 (a length bound in `src/adapters`, `src/models`, `src/local`, 91%) agree with the file, but no baseline key was labelled by R4, and R7's five baseline keys have no exact agreeing precedent, so they stay in the baseline. Taken together the rules agree with 303 of the 311 decided keys they cover (97.4%) and with 97% and 98% on the two halves of a by-file hold-out (files split by a hash of their path), which is the same data seen differently, not an independent reader.
52
+
53
+ Not labelled, on purpose: every length bound and truncation in `src/hub`, `src/cli`, `src/ui`, `src/rooms` and `src/broker`; `Math.min`/`Math.max` clamps of a timeout; default-parameter timeouts (`timeoutMs = 2000`); `MAX_*` and `*_BYTES` constants; unit conversions. #739 settles their convention first and then relabels the inconsistent existing entries by name.
54
+
55
+ ## The `src/hub` convention for bounds and truncations (#739)
56
+
57
+ Ruled by the conductor on 2026-11-05. It decides bounds (`x.length > N`, `Object.keys(x).length > N`, a byte limit) and truncations (`.slice(0, N)`, `.substring`) in `src/hub`. Ask in order:
58
+
59
+ 1. A validation bound on an argument of a **documented tool or control message** is a **contract**, whether or not `hub-tools.ts` repeats the number: it is part of that message's shape.
60
+ 2. A bound or truncation on **free text, ids or raw input that no message shape defines** (a peer's body, a model's output, another project's or GitHub's text, a person's input) is a **safety floor**.
61
+ 3. A cap on **the hub's own lists, batches or reports**, which the hub trims, pages or reports as truncated, is **operational**.
62
+ 4. A bound on the **shape of a documented file format the person writes** (config, profile, role, extension, text projection) is a **contract**, like a documented message.
63
+
64
+ Truncations (`.slice`, `.substring`) are decided by what is cut:
65
+
66
+ 5. A **shortened id, hash or commit** (`commit.slice(0, 12)`, `sha256.slice(0, 16)`, a log timestamp used as an evidence id) is a **contract**: a displayed identity format that other tools and people match on.
67
+ 6. The **hub's own list** cut to N (`pending.slice(0, 64)`, `history.slice(-5)`) is **operational**, and so is a **display cap on the hub's own report** (`pool.walk.length > 16`, `paths.length > 20`, a held-rows cap).
68
+ 7. **Text cut for a log line, notice or reply** is a **safety floor** only when the cut text **originates outside the hub process**: a model, a peer, a person, another project, a native hook process, or a subprocess, network, git or file-parse error that can carry external content. The hub's own composed message, or its own internal error text logged locally, is **operational**. Ask where the text comes from, not where it goes.
69
+
70
+ Rulings of #741 (the conductor, 2026-11-06), added after a fresh blind sample (seed 741001) agreed on 70% and the misses fell into six shapes. They extend lines 1 to 7; where one differs, the later line decides its shape:
71
+
72
+ 8. A cap on **the hub's own cache, set, map or concurrency limiter** is **operational**, and so is the line 3 reading of lists, batches and reports. Exception: decide by **what one insert is caused by, not by the name**. A set or map that gains one entry per event **another process controls** (a peer's envelope, a remote room frame, a native hook's tool call, a file a person or agent writes, an acknowledgement a caller names) is a **safety floor**: it bounds memory against outside input. A set the hub fills only from its own decisions (a check run, a launch candidate, an approval, a prepared review) is operational.
73
+ 9. A **silent truncation of a tool argument** (the hub cuts it and goes on, with no refusal) is a **contract** when the limit is stated in that tool's schema (`maxLength` in `hub-tools.ts`) or in `docs/operations.md`; otherwise it is a **safety floor**. The same argument in two tools is decided per tool: `hub_backlog_split` states `title` 300 and `detail` 8000, `hub_task_propose` does not, so its cut is a safety floor.
74
+ 10. **Error or stack text** cut for a log, notice or reply is a **safety floor**, unless the text is a **fixed phrase the hub composed**: then the cut is a display cap and **operational**.
75
+ 11. A cap on **what a read tool returns** (edges, lines, bytes of one result) is a **contract**: it is the documented shape of that result.
76
+ 12. A bound on **a field of a documented message or envelope** (a task handoff key's project id, the `refs.paths` of a deferred envelope, a hub task envelope's `refs.backlog`) is a **contract** under line 1, as is a shortened id or hash (`lease.holder.slice(0, 8)`).
77
+ 13. A bound on something **parsed from git, GitHub or a subprocess** (a path inventory, a control line, a config key listing, a documents list, a tail of runner output) is a **safety floor**: it fails closed against content the hub does not write.
78
+
79
+ A number is never evidence by itself. Many unrelated schema entries share `32`, `64`, `256` or `4096`, so "the same number appears in `hub-tools.ts`" decides nothing: the first measurement of #739 counted 280 of 378 `src/hub` bounds as mirrors of a schema by number, and only a handful really repeat a tool schema's limit once the call sites are read.
80
+
81
+ ### What the committed file did before this change (measured on `8e44babc`)
82
+
83
+ | Shape | Decided keys | operational | safety floor | contract |
84
+ | --- | ---: | ---: | ---: | ---: |
85
+ | `src/hub` length bounds | 213 | 63 | 91 | 59 |
86
+ | `src/hub` truncations (`.slice`) | 151 | 73 | 47 | 31 |
87
+
88
+ Among the 213, those that refuse or validate input (171) were 77 safety floor, 53 contract and 41 operational, and the file labelled the same kind of check differently by file: `tasks.ts` 19 of 20 contract, `sdlc.ts` 14 of 22 safety floor, `test-integrity.ts` 6 of 6 safety floor. Across the whole decided file 214 expressions appear in more than one place and 28 of them carried conflicting labels (133 keys), mostly default-parameter timeouts and `Math.min`/`Math.max` clamps, which this change does not touch.
89
+
90
+ ### Existing labels changed by this convention (44, by key, old to new)
91
+
92
+ All 173 decided `src/hub` validation bounds (outside `hub-tools.ts`) were read at their call sites. 44 contradicted the convention and are changed; every other decided bound already agrees with it and was left alone, including `deferred-hub.ts` `env.body.length > 1_000_000` and `delivery-hints.ts` `row.id.length > 256` (a peer's raw body and a hub-built hint, no message shape: safety floor). Group membership is a reading of each call site, reviewed by the conductor.
93
+
94
+ #### A. Argument of a documented tool or control message: to contract (19)
95
+
96
+ Reason: Each is an argument of a documented tool or control message (`taskOpBody`, `uiAction`, `recoveryOp`, `onMessage`, the check runner's `files`, the planning `deps` list). The bound is part of that message's shape, whether or not `hub-tools.ts` repeats the number.
97
+
98
+ | Key | Old | New |
99
+ | --- | --- | --- |
100
+ | `check-runner.ts` `request.files.length > 32=32` (in `start`) | operational | contract |
101
+ | `daemon.ts` `a.files.length > 32=32` (in `taskOpBody`) | safety floor | contract |
102
+ | `daemon.ts` `a.delivery_id.length > 256=256` (in `taskOpBody`) | safety floor | contract |
103
+ | `daemon.ts` `a.delivery_id.length > 256=256` (in `taskOpBody`) | safety floor | contract |
104
+ | `daemon.ts` `a.reason.length > 2000=2000` (in `taskOpBody`) | safety floor | contract |
105
+ | `daemon.ts` `a.superseded_by.length > 2000=2000` (in `taskOpBody`) | safety floor | contract |
106
+ | `daemon.ts` `a.release.length > 100=100` (in `taskOpBody`) | safety floor | contract |
107
+ | `daemon.ts` `liveIssues.length > 32=32` (in `uiAction`) | operational | contract |
108
+ | `daemon.ts` `a.to.length > 32=32` (in `uiAction`) | operational | contract |
109
+ | `daemon.ts` `msg.operationId.length > 128=128` (in `recoveryOp`) | operational | contract |
110
+ | `daemon.ts` `msg.launchId.length <= 128=128` (in `onMessage`) | operational | contract |
111
+ | `daemon.ts` `msg.launchId.length <= 128=128` (in `onMessage`) | operational | contract |
112
+ | `daemon.ts` `msg.launchId.length <= 128=128` (in `onMessage`) | operational | contract |
113
+ | `daemon.ts` `msg.reason.length > 2000=2000` (in `onMessage`) | operational | contract |
114
+ | `daemon.ts` `msg.id.length > 256=256` (in `onMessage`) | operational | contract |
115
+ | `daemon.ts` `msg.reason.length > 2000=2000` (in `onMessage`) | operational | contract |
116
+ | `daemon.ts` `(msg.liveIssues ?? []).length > 32=32` (in `onMessage`) | operational | contract |
117
+ | `daemon.ts` `(msg.liveIssues ?? []).length > 32=32` (in `onMessage`) | operational | contract |
118
+ | `planning.ts` `value.length > 64=64` (in `planningPatch`) | operational | contract |
119
+
120
+ #### B. Text or replies from GitHub, another project or a person: to safety floor (6)
121
+
122
+ Reason: Each bounds text or a reply that comes from GitHub, another project or a person, which no tool or control message defines, so it limits untrusted input.
123
+
124
+ | Key | Old | New |
125
+ | --- | --- | --- |
126
+ | `dogfood-read.ts` `r.pull_requests.length <= 100=100` (in `validRun`) | operational | safety floor |
127
+ | `github-import.ts` `row.title.length>300=300` (in `<anonymous>`) | operational | safety floor |
128
+ | `github-import.ts` `row.body.length>8000=8000` (in `<anonymous>`) | operational | safety floor |
129
+ | `sdlc-queue.ts` `p.baseRefName.length > 200=200` (in `<anonymous>`) | operational | safety floor |
130
+ | `sdlc-queue.ts` `threads.nodes.length > 100=100` (in `finalSnapshot`) | operational | safety floor |
131
+ | `sdlc-queue.ts` `checks.nodes.length > 100=100` (in `finalSnapshot`) | operational | safety floor |
132
+
133
+ #### C. The hub's own derived list: to operational (1)
134
+
135
+ Reason: A cap on the hub's own derived list, which the hub builds itself and reports as truncated, so it tunes a list size and is not an input bound.
136
+
137
+ | Key | Old | New |
138
+ | --- | --- | --- |
139
+ | `task-code-symbols.ts` `brief.reviewable.length > 500=500` (in `buildTaskCodeSymbols`) | safety floor | operational |
140
+
141
+ #### D. Shape of a documented file the person writes: to contract (18)
142
+
143
+ Reason: Each is the bound on the shape of a documented file format the person writes (config, profile, role, extension or text projection), which is a contract like a documented message (the conductor's wording of the convention).
144
+
145
+ | Key | Old | New |
146
+ | --- | --- | --- |
147
+ | `github-import.ts` `value.length<=100=100` (in `selectionText`) | operational | contract |
148
+ | `path-policy.ts` `raw.length > 64=64` (in `policyPaths`) | safety floor | contract |
149
+ | `config-tier.ts` `part.length > 64=64` (in `<anonymous>`) | safety floor | contract |
150
+ | `text-projection.ts` `Object.keys(raw).length > 16=16` (in `projectionConfig`) | safety floor | contract |
151
+ | `text-projection.ts` `argv.length > 16=16` (in `projectionConfig`) | safety floor | contract |
152
+ | `text-projection.ts` `arg.length > 2000=2000` (in `<anonymous>`) | safety floor | contract |
153
+ | `profiles.ts` `Object.keys(data.classes).length > 32=32` (in `resolveProfile`) | safety floor | contract |
154
+ | `profiles.ts` `Object.keys(data.role_text).length > 8=8` (in `resolveProfile`) | operational | contract |
155
+ | `profiles.ts` `data.example_checks.length > 16=16` (in `resolveProfile`) | operational | contract |
156
+ | `profiles.ts` `data.review_checklist.length > 16=16` (in `resolveProfile`) | safety floor | contract |
157
+ | `profiles.ts` `value.length > 500=500` (in `<anonymous>`) | operational | contract |
158
+ | `planning.ts` `Object.keys(input).length > 64=64` (in `planningWipLimits`) | operational | contract |
159
+ | `roles.ts` `sanitize(body).length > 2000=2000` (in `parseRole`) | safety floor | contract |
160
+ | `roles.ts` `files.length > 64=64` (in `loadRoleCatalog`) | safety floor | contract |
161
+ | `roles.ts` `Object.keys(raw).length > 64=64` (in `catalogAssignments`) | safety floor | contract |
162
+ | `roles.ts` `ids.length > 2=2` (in `catalogAssignments`) | safety floor | contract |
163
+ | `roles.ts` `roles.length > 2=2` (in `catalogSection`) | safety floor | contract |
164
+ | `roles.ts` `text.length > 2000=2000` (in `taskRoleText`) | safety floor | contract |
165
+
166
+
167
+ ### Additional relabels (47, by key, old to new)
168
+
169
+ A second round, after the first sample showed the convention did not decide truncations. Each change was asserted against its old label when applied; nothing else changed. The 17 truncations that fit no line (for example `action-class.ts` `text.slice(i, i + 256)`, `permission-payload.ts` `full.slice` 600, `envelope.ts` `noteLine`) keep their labels.
170
+
171
+ #### Shortened id, hash or commit: to contract (6)
172
+
173
+ | Key | Old | New |
174
+ | --- | --- | --- |
175
+ | `ask.ts` `l.slice=10` (in `id`) | operational | contract |
176
+ | `daemon.ts` `publicStatus.generation.slice=8` (in `generation`) | operational | contract |
177
+ | `deliverables.ts` `createHash(sha256).update(${format}\0${path}).digest(hex).slice=12` (in `deliverableId`) | operational | contract |
178
+ | `registry.ts` `createHash(sha256).update(root).digest(hex).slice=24` (in `idFor`) | operational | contract |
179
+ | `research.ts` `createHash(sha256).update(projectId).digest(hex).slice=16` (in `projectKey`) | operational | contract |
180
+ | `task-code-group.ts` `r.head.slice=8` (in `head`) | operational | contract |
181
+
182
+ #### The hub's own list cut to N: to operational (10)
183
+
184
+ | Key | Old | New |
185
+ | --- | --- | --- |
186
+ | `activity.ts` `u.locations.slice=12` (in `updateActivity`) | safety floor | operational |
187
+ | `tasks.ts` `(Array.isArray(v) ? v : typeof v === string ? [v] : []).map(text).filter((p): p is string => !!p).slice=50` (in `textList`) | safety floor | operational |
188
+ | `tasks.ts` `holders.slice=8` (in `detail`) | safety floor | operational |
189
+ | `tasks.ts` `history.slice=-5` (in `publicHistory`) | safety floor | operational |
190
+ | `tasks.ts` `[...this.incomingBacklog].sort((a, b) => a - b).slice=64` (in `incoming`) | contract | operational |
191
+ | `tasks.ts` `[...pending].sort((a, b) => a - b).slice=64` (in `ids`) | contract | operational |
192
+ | `tasks.ts` `pool.walk.slice=16` (in `walk`) | safety floor | operational |
193
+ | `tasks.ts` `paths.slice=20` (in `list`) | safety floor | operational |
194
+ | `tasks.ts` `[...new Set([taskNode, ...symbols.nodes, ...about])].slice=64` (in `saveNative`) | safety floor | operational |
195
+ | `text-projection.ts` `(process.env.PATH ?? /usr/bin:/bin).split(:).slice=32` (in `choices`) | safety floor | operational |
196
+
197
+ #### Text cut for a log line, notice or reply: to safety floor (origin test noted per entry) (21)
198
+
199
+ | Key | Old | New | Origin of the cut text |
200
+ | --- | --- | --- | --- |
201
+ | `ask.ts` `raw.slice=2000` (in `answer`) | operational | safety floor | model output |
202
+ | `conflicts.ts` `String(r.title).slice=100` (in `<anonymous>`) | operational | safety floor | another agent's task title |
203
+ | `daemon.ts` `sanitize(row.key).slice=160` (in `retainedConfigAfterReload`) | operational | safety floor | a rejected config key the person wrote |
204
+ | `daemon.ts` `sanitize(row.reason).slice=1200` (in `retainedConfigAfterReload`) | operational | safety floor | a rejected config reason built from the person's config value |
205
+ | `daemon.ts` `${v.title ? ${v.title}: : }${v.text}.replace(/\s+/g, ).slice=160` (in `memoryLine`) | operational | safety floor | memory text written by peers and the model |
206
+ | `daemon.ts` `${v.title ?? }\n${v.text}.slice=1500` (in `note`) | operational | safety floor | memory note text written by peers and the model |
207
+ | `daemon.ts` `${c.title ?? }\n${c.text}.slice=600` (in `note`) | operational | safety floor | memory note text written by peers and the model |
208
+ | `daemon.ts` `JSON.stringify(v).slice=60` (in `peerArg`) | operational | safety floor | the caller's tool argument echoed in a refusal |
209
+ | `daemon.ts` `sanitize(e.reason ?? delivery retries exhausted).replace(/\s+/g, ).slice=300` (in `reason`) | operational | safety floor | a delivery retry reason, which can carry a native adapter's error text |
210
+ | `daemon.ts` `e.env.body.slice=200` (in `<anonymous>`) | operational | safety floor | a peer's message body logged |
211
+ | `daemon.ts` `v.text.slice=600` (in `text`) | operational | safety floor | memory text written by peers and the model |
212
+ | `daemon.ts` `msg.tool.slice=64` (in `tool`) | operational | safety floor | a native hook process's tool name |
213
+ | `daemon.ts` `msg.toolUseId.slice=128` (in `toolUseId`) | operational | safety floor | a native hook process's tool use id |
214
+ | `daemon.ts` `msg.sessionId.slice=128` (in `sessionId`) | operational | safety floor | a native hook process's session id |
215
+ | `daemon.ts` `String(msg.question).slice=80` (in `title`) | operational | safety floor | a person's question to ahub ask |
216
+ | `daemon.ts` `String(msg.question).slice=300` (in `text`) | operational | safety floor | a person's question to ahub ask |
217
+ | `deliverables.ts` `extra[0]!.slice=32` (in `own`) | operational | safety floor | an unknown field name from the caller's deliverable |
218
+ | `packs.ts` `(error as Error).message.slice=200` (in `reason`) | operational | safety floor | a project role-pack file's parse error, which echoes the file's content |
219
+ | `progress.ts` `tool.slice=80` (in `name`) | operational | safety floor | a native hook process's tool name |
220
+ | `progress.ts` `item.name.slice=80` (in `name`) | operational | safety floor | a progress item sent by a peer |
221
+ | `progress.ts` `item.source.slice=40` (in `source`) | operational | safety floor | a progress item sent by a peer |
222
+
223
+ #### Display caps on the hub's own report: to operational (5)
224
+
225
+ | Key | Old | New |
226
+ | --- | --- | --- |
227
+ | `tasks.ts` `pool.walk.length > 16=16` (in `walk`) | safety floor | operational |
228
+ | `tasks.ts` `paths.length > 20=20` (in `list`) | safety floor | operational |
229
+ | `tasks.ts` `result.length < 32=32` (in `outsideIds`) | safety floor | operational |
230
+ | `daemon.ts` `guarded.length > 200=200` (in `prepareConfiguration`) | safety floor | operational |
231
+ | `delivery-hints.ts` `result.rows.length >= 32=32` (in `heldDeliveryHints`) | safety floor | operational |
232
+
233
+ #### Bounds on the shape of the person's config (`config-reload.ts`): to contract (5)
234
+
235
+ | Key | Old | New |
236
+ | --- | --- | --- |
237
+ | `config-reload.ts` `Object.keys(v).length > 256=256` (in `table`) | operational | contract |
238
+ | `config-reload.ts` `v.length > 4096=4096` (in `text`) | operational | contract |
239
+ | `config-reload.ts` `v.length > 128=128` (in `peers`) | safety floor | contract |
240
+ | `config-reload.ts` `v.length > 16_384=16_384` (in `openValue`) | operational | contract |
241
+ | `config-reload.ts` `v.length > 1024=1024` (in `openValue`) | safety floor | contract |
242
+
243
+ #### Three omissions found by the second sample: to contract (3)
244
+
245
+ The first list of argument bounds missed three that the convention (line 1) already covers; found when the blind reader labelled them contract.
246
+
247
+ | Key | Old | New | Why |
248
+ | --- | --- | --- | --- |
249
+ | `workflow.ts` `p.criteria.length > 32=32` (in `criteriaReceipt`) | operational | contract | the `criteria` argument of `hub_sdlc_record` (schema `maxItems: 32`) |
250
+ | `workflow.ts` `c.refs.length > 8=8` (in `<anonymous>`) | operational | contract | the `refs` of one criterion of `hub_sdlc_record` (schema `maxItems: 8`) |
251
+ | `claude-launch-proof.ts` `row.launchId.length > 128=128` (in `readClaudeLaunchProof`) | operational | contract | the `launchId` control field, the same bound that `daemon.ts` `onMessage` already labels contract |
252
+
253
+ ### Calibration of the `src/hub` convention (#739)
254
+
255
+ The population is the 365 decided `src/hub` bounds and truncations (`hub-tools.ts` excluded). Each round is a blind 80-sample read with only the convention, the committed labels hidden.
256
+
257
+ | Round | Seed | File state | Agreement | Bounds | Truncations |
258
+ | --- | --- | --- | ---: | ---: | ---: |
259
+ | 1 | 739101 | after the first 44 relabels, lines 1 to 4 only | 56 of 80 (70.0%) | 36 of 44 (82%) | 20 of 36 (56%) |
260
+ | 2 | 739202 | after 47 more relabels, lines 1 to 7 | 71 of 80 (88.8%) | 42 of 47 (89%) | 29 of 33 (88%) |
261
+
262
+ Round 2 clears the 85% bar. It is **in-sample in one respect**: lines 5 to 7 and the 47 relabels were written after round 1's misses, so the sample measures whether the convention now decides what it was extended to decide, not an independent reader. A fresh seed drawn from the relabelled file is the check that matters, and it was drawn only after the relabels were applied.
263
+
264
+ Round 2's nine misses: three are keys the first list left out (`workflow.ts` criteria 32, criterion refs 8 and `claude-launch-proof.ts` launchId 128, relabelled contract above); `check-runner.ts` `lines.slice(-40)` (a cut of a subprocess's output, which line 7 calls safety floor, labelled by me operational) and `version.stdout.trim().slice(0, 80)` (the converse); `daemon.ts` `schedules pass failed: ...message.slice(0, 160)` and `tasks.ts` `task.title.slice(0, 120)` (a memory title from the task's own title, where whether the origin is outside the hub is a judgement); `execution-budget.ts` `input.id.length > 200` and `delivery-hints.ts` `held.rows.length > 32`. The remaining disagreements are about where text originates, and line 7 is the place to sharpen if a later sample falls short.
265
+
266
+ No baseline key is labelled in this change. That is #741 (below).
267
+
268
+ ## Batch 1 of #741: 149 `src/hub` length bounds (#741)
269
+
270
+ Date: 2026-11-06. Refs #1180. The population is the 187 baseline keys in `src/hub` that are a length or size bound and not a truncation. The 38 left in the baseline are the keys no line decides without a ruling or a caller trace: lower bounds that only say "not empty" or "at least N" (`< 1`, `>= 2`, `< 8`, `before.size < 1`), bounds whose message the key does not name (`daemon.ts` `values.length > 256`, `tool-request.ts` scope and bucket 4096, `operational-view.ts` metadata lists, `specs.ts` `base.length > 200`), research-campaign and experiment bounds in `board.ts` (`owners`, `hostEvidence`, runner receipts), and read-side display caps in `publicReadValue`. A key left here is reported for a ruling, not guessed. Truncations (`.slice`, `.substring`) are batch 2.
271
+
272
+ ### Calibration
273
+
274
+ | Round | Seed | File state | Reader | Agreement | Result |
275
+ | --- | --- | --- | --- | ---: | --- |
276
+ | 1 | 741001 | after #739, lines 1 to 7 | blind reader, lines 1 to 7 | 56 of 80 (70.0%) | below the bar: 24 misses in six shapes (below) |
277
+ | 2 | 741002 | after the 17 relabels below, lines 1 to 13 | blind reader, lines 1 to 13 | 68 of 80 (85.0%) | meets the 85% bar |
278
+
279
+ Both samples are drawn from the decided `src/hub` bounds and truncations (`hub-tools.ts` excluded), without replacement across rounds (round 2 excludes round 1's 80 keys). The reader read each key at its call site with the committed label hidden and wrote every label before scoring.
280
+
281
+ Round 1's misses, by shape: 6 bounds on a message or envelope field the file called safety floor or operational (line 12); 5 bounds on content parsed from git, GitHub or a subprocess (line 13); 4 caches, sets and limiters (line 8); 4 cuts of error or stack text (line 10); 3 silent truncations of a tool argument (line 9); 2 caps on a read tool's output (line 11). The conductor ruled each; the rulings are lines 8 to 13 above.
282
+
283
+ Round 2's 12 misses. Five are **relabelled** in this PR (the rule decides and the file contradicted it, so they follow #739's practice of changing a contradicting entry by name): `plan-conformance.ts` `planPaths.length > 1000` (operational to safety floor, rule 13: it is the same check as `completePlanInventory`, on the plan paths compared with a PR's file list read from GitHub), `usage.ts` transcript size 32 MiB (operational to safety floor, rule 13: it bounds a file a Claude process writes), `github-import.ts` 2 MiB read (operational to safety floor, rule 13: GitHub's output), `deferred-hub.ts` `env.to.length > 256` (safety floor to contract, rule 12: a field of the deferred envelope, like its `refs.paths`) and `deliverable-files.ts` `message.replace(path, "the path").slice(0, 160)` (safety floor to operational, rule 10: a guardPath refusal the hub composed). One is **decided and left**: `delivery-journal.ts` `Object.keys(queues).length > 4096` stays safety floor, because `queues` is a field of the journal snapshot the hub writes and reads back, which no document names, so rule 12 does not apply and rule 13 reads a damaged file as outside content. Six **stay open for batch 2**: `settings.ts` `presetValue` 4096, `dogfood-read.ts` `line.slice(0, 24)`, `claude-launch-proof.ts` `executionCwd` 4096, `board.ts` `failure.head.length > 300`, `code-facts.ts` `a.at.length < 9` and `spec-read.ts` `issues.size > 3`: no line decides them without a ruling.
284
+
285
+ ### Existing labels changed by the rulings (17, by key, old to new; the five round 2 relabels are listed in the round 2 paragraph above)
286
+
287
+ | Rule | Key | Old | New |
288
+ | --- | --- | --- | --- |
289
+ | 8 | `check-runner.ts` `this.entries.size >= 3` (in `start`) | safety floor | operational |
290
+ | 8 | `code-facts.ts` `this.cache.size>128` (in `query`) | safety floor | operational |
291
+ | 8 | `memory-observations.ts` `this.pending.size > 2048` (in `delivery`) | safety floor | operational |
292
+ | 8 | `daemon.ts` `injected.size > 512` (in `readTranscript`, one entry per hook line a native process writes) | operational | safety floor |
293
+ | 8 | `limits.ts` `this.recent.size > 1000` (in `admit`, one entry per distinct message a peer sends) | operational | safety floor |
294
+ | 8 | `daemon.ts` `retainedCliAudits.size > 4096` (in `drainCliAuditsNow`, one entry per audit a CLI process sends) | operational | safety floor |
295
+ | 10 | `daemon.ts` `sanitize(error.message).slice=1000` (in `error`, a fixed Pi version refusal) | safety floor | operational |
296
+ | 12 | `deferred-hub.ts` `refs.paths.length > 256` (in `validateDeferredEnvelope`) | safety floor | contract |
297
+ | 12 | `tasks.ts` `projectId.length > 200` (in `handoffStateByKey`) | safety floor | contract |
298
+ | 12 | `bus.ts` `backlog.length > 64` (in `queuedBacklogIds`) | safety floor | contract |
299
+ | 12 | `daemon.ts` `g.lease.holder.slice=8` (in `goalLine`) | operational | contract |
300
+ | 13 | `plan-conformance.ts` `evidence.paths.length > 1000` (in `completePlanInventory`) | operational | safety floor |
301
+ | 13 | `code-facts.ts` `keys.length>128` (in `nativeEnv`) | operational | safety floor |
302
+ | 13 | `code-facts.ts` `control.length>64` (in an anonymous stream handler) | operational | safety floor |
303
+ | 13 | `sdlc-release-authorization.ts` `value.documents.length > 128` (in `docsVerdict`) | contract | safety floor |
304
+ | 13 | `check-runner.ts` `lines.length > 40` (in `cut`) | operational | safety floor |
305
+ | 13 | `check-runner.ts` `Buffer.byteLength(result) > 4096` (in `cut`) | operational | safety floor |
306
+
307
+ (The 17 are the 15 of the first pass and two found when rule 8 was read by cause. `daemon.ts` `error` is the Pi refusal, a fixed phrase, and the other `sanitize(error.message).slice=1000` keeps safety floor.)
308
+
309
+ ### The 149 keys labelled, by label
310
+
311
+ Each key was read at its call site and decided by the rule named. 25 operational (rule 8 for the hub's own caches and display caps, rule 6 for a hub-built report), 77 safety floor (rule 13 for content parsed from git, GitHub or a subprocess, the config or environment values the hub reads and fails closed on, rule 8 for sets one outside event fills, rule 2 for free text no message defines), 47 contract (rule 1 and 12 for a documented message or envelope field, rule 4 for a documented file the person writes, rule 11 for a read tool's result). The keys are in `scripts/inventory-hardcoded-values.classifications.json`; the baseline file lost exactly those 149 and gained none (1155 to 1006).
@@ -0,0 +1,55 @@
1
+ # Hard-coded values inventory: why it drifted, and the ratchet that stops it (#1180, task #719)
2
+
3
+ Date: 2026-11-05. Refs #1180. The issue stays open: its AC1 (zero unknown keys) is not met by this change. This record exists so the follow-up that classifies the rest starts from measurements, not from a guess.
4
+
5
+ ## Cause
6
+
7
+ Not a generator bug. `scripts/inventory-hardcoded-values.ts` and `scripts/inventory-hardcoded-values.classifications.json` were committed together by #764 on 2026-10-24, and nothing ever ran the generator again: no step of `scripts/check.sh`, no workflow and no test. The file was edited by hand twice (#1099 changed one value, #1167 removed 11 keys), and every other change to `src/` and `scripts/` added constants nobody classified.
8
+
9
+ Measured by running the generator at past commits:
10
+
11
+ | Commit | Date | Unknown keys |
12
+ | --- | --- | ---: |
13
+ | `7c1d2065` (#764, the commit that added the inventory) | 2026-10-24 | 22 |
14
+ | `b454e527` | 2026-10-25 | 63 |
15
+ | `438e9b23` | 2026-10-28 | 527 |
16
+ | `9680a4db` | 2026-11-02 | 1065 |
17
+ | `8511d01a` (#1099) | 2026-11-02 | 1087 |
18
+ | main at `4b34fab1` | 2026-11-05 | 1438 |
19
+
20
+ The 22 at #764's own head are the memory-code files that landed in the same week. The 1282 in the issue and the 1438 here are the same drift counted at two commits. At this commit 3243 inventory entries exist, 1805 were decided, 1438 were unknown and 36 committed keys matched nothing (the code they named had moved or changed).
21
+
22
+ ## What this change does
23
+
24
+ 1. Removes the 36 stale keys.
25
+ 2. Classifies 216 unknown keys by precedent in the file itself, and nothing else. A key is adopted only when every already-decided key sharing its parts agrees, taking the tiers in this order and stopping at the first that decides:
26
+ - same file, kind, name and value (82 keys): leave-one-out accuracy over the committed decisions 99.4%;
27
+ - same file, kind and name (44 keys): 97.6%;
28
+ - same kind, name and value, with at least two agreeing precedents (72 keys): 95.7%;
29
+ - same kind and name, with at least two agreeing precedents (18 keys): 94.1%.
30
+ The same two weaker tiers with a single precedent measured 92.6% and 89.2%, which is why they need two. The 216 are 109 contract, 81 operational, 23 safety floor and 3 test seam.
31
+ 3. Puts the other 1222 unknown keys in `scripts/inventory-hardcoded-values.baseline.json`, the list of known-unclassified keys. They are not classified. `bun scripts/inventory-hardcoded-values.ts --check` fails on any unknown key that is not in that list, on a baseline key that has since been classified, and on a baseline key that matches no entry. The baseline only shrinks. `scripts/check.sh` runs the check with the other once-only steps.
32
+
33
+ ## What this change does not do, and why
34
+
35
+ It classifies none of the 1222 remaining keys. The attempt was measured first. 190 of them were labelled by reading each literal's source line against the K01-K18 reasons of `2026-10-30-operational-settings-closure.md`, then 80 already-decided entries (seeded random draw, labels hidden) were labelled the same way and compared with the committed file: **47 of 80 agreed (58%)**. The disagreements were systematic, not noise:
36
+
37
+ | Committed class | I labelled | Count | Typical case |
38
+ | --- | --- | ---: | --- |
39
+ | operational | safety floor | 12 | a hub-internal cap constant (`MAX_STATES = 256`, `tasks.length > 500`, a 1024-entry map) |
40
+ | operational | contract | 7 | a saturating `Math.max(0, ...)`, a `limit < 1` check |
41
+ | operational | test seam | 6 | a smoke script's `deadline`, an injected `opts.x ?? N` default |
42
+ | contract | safety floor | 5 | a short id or text slice bound |
43
+ | safety floor | operational | 2 | a `termMs`/`killMs` in a review-isolation stop |
44
+
45
+ So the file's operative rule for `operational` is wider than the prose of the K groups: a hub-internal cap, an injected default and a script deadline all count as operational, and the prose does not say so. A rule pass built from the K-group text was 65% accurate on the 1805 decided entries and a text-feature classifier 71%, so neither decides anything. Writing 1222 labels at 58% would be the blind classification the issue forbids.
46
+
47
+ ## How the remaining 1222 get classified
48
+
49
+ As a separate task, in batches a person can review, under these rules:
50
+
51
+ - Before the first batch, write the rule for each confusable case into the header of the classifications file or this record: hub-internal caps, injected `opts.x ?? N` defaults, script deadlines, short id and hash slices, saturating zero bounds.
52
+ - Each batch is labelled by those written rules, never by feel.
53
+ - Each batch needs a fresh blind sample of 80 already-decided entries labelled by the same rules, and an agreement of 85% or more with the committed file, before the batch is written.
54
+ - Classifying a key means adding it to the classifications file and removing it from the baseline in the same commit; `--check` refuses the half-done state.
55
+ - Do not regenerate `docs/verification/2026-10-24-hardcoded-values.md`: `test/operational-settings.test.ts` reads it by row.
@@ -0,0 +1,149 @@
1
+ # 0.32.0 release verification
2
+
3
+ This record covers release preparation and source review. It claims no installation, no running-hub upgrade to 0.32.0 and no live TUI launch.
4
+
5
+ ## Candidate
6
+
7
+ Version 0.32.0, prepared from main eddbd522 (2 commits since v0.31.0, 1 of them `feat`; bump rule: minor). The release commits carry the version, the CHANGELOG section, the bundle and the documentation stamps.
8
+
9
+ ## Reviews
10
+
11
+ Pull requests named by the commits since v0.31.0: #1229, #1227.
12
+
13
+ The board approval head equals each merged PR head (`mismatch: false`), and
14
+ GitHub readback shows no failed or pending check. These head-specific hosted
15
+ results are not live or mixed-room acceptance:
16
+
17
+ | PR / task | Approved head | Merge commit | Hosted CI |
18
+ | --- | --- | --- | --- |
19
+ | [#1227](https://github.com/STAIxBWLB/agent-hub/pull/1227), task 741 | `20f6e4b6ea18333fa1e45a6ad6cd36eaf40f7eeb` | `307a91a0fb3d8ce38b812753444de388c86b191d` | 19 success, 2 skipped |
20
+ | [#1229](https://github.com/STAIxBWLB/agent-hub/pull/1229), task 736 | `4d666223491ac6aff7c5347d4250c7eb6268b20a` | `eddbd5228d6d6f5e6638a617b112a4ca83ab000a` | 20 success, 2 skipped |
21
+
22
+ Neither merge is bound to an earlier approval head. No merge was performed by
23
+ task 745.
24
+
25
+ ## Native acceptance receipts
26
+
27
+ Receipts added under `docs/verification/` since v0.31.0: none.
28
+
29
+ This list is read from the repository, never written by hand. Native acceptance and the person's release consent are separate records: consent is `ahub sdlc authorize-release 0.32.0`, recorded by the person or the unique configured conductor only on the person's explicit release instruction naming the release in its own session (never a hub/task/review/file/peer relay), and is not recorded in this file. The tag run refuses without it.
30
+
31
+ ## Document review
32
+
33
+ Eleven stale documents were reviewed against only #1229 (T3) and #1227
34
+ (inventory conventions). Corrected content:
35
+
36
+ - `README.md`: generated Status 0.32.0 and the current release-report link.
37
+ - `docs/quickstart.md`: generated 0.32.0 upgrade section, concrete room changes,
38
+ N-1 baseline and the required matching global CLI/plugin step.
39
+ - `docs/agent-notes/bus.md`: add `holdRemoteEpochRotated` to the host-port list,
40
+ keep aging advisory and status read-only, and preserve disposition/proof rules.
41
+ - `docs/agent-notes/daemon.md`: await the new asynchronous room stop before
42
+ shared storage closes, without bypassing the attempted FIFO head.
43
+ - `docs/agent-notes/tests.md`: replace the obsolete wait-for-739 convention with
44
+ the committed bounds/truncation rules and fresh blind 80-sample/85% procedure;
45
+ keep calibrated mechanical rules outside that settled slice and baseline-only
46
+ shrink behavior.
47
+ - `docs/operations.md`: update the current protocol/N-1 version table and
48
+ distinguish the exact epoch journal reason from its shorter `holdReason` label.
49
+
50
+ Reviewed without content change:
51
+
52
+ - `docs/agent-notes/README.md`: historical explanation remains valid.
53
+ - `docs/agent-notes/adapters.md`: no native adapter change; existing owned-work
54
+ shutdown/await rules still apply.
55
+ - `docs/agent-notes/budget.md`: quota, pause and context contracts are unchanged.
56
+ - `docs/agent-notes/tasks.md`: room holds do not mutate task authority or routing.
57
+ - `docs/security.md`: T3 already states advisory-only liveness, legacy broker
58
+ behavior, bounded FIFO stop, signed identity and local rotation fences.
59
+
60
+ These 11 reviewed documents were restamped through the official
61
+ `release-prepare restamp ... --reviewed` path after the reviewed content commit
62
+ `2f4c372a6ce48dc01acb4a002f6a46e9557cbd17`. Following the approved fixture
63
+ exception, the tests note was reviewed again and its final stamp points to
64
+ `4ad77748c96383bdef8e55a11ff53808a1359500`; the other ten retain `2f4c372a`.
65
+ Fresh benchmarks, local-worker, models and help stamps were not refreshed.
66
+ All watched path definitions remain unchanged.
67
+
68
+ ## Checks
69
+
70
+ The official generator was dry-run first, then executed on dgx02 from committed
71
+ `eddbd5228d6d6f5e6638a617b112a4ca83ab000a`, on exact branch `release/0.32.0`,
72
+ after fetching the existing published v0.31.0 tag and a frozen root install.
73
+ Bun 1.4.2 generated the bundle/manifest and the separately retargeted fixture
74
+ lock. Approved artifact-return exception `981f5052` returned only the bundle and
75
+ manifest plus task source/generator outputs and fixture lock; no node_modules,
76
+ target or other build output returned. Producer hashes:
77
+
78
+ - `server.js`: `6f48c2f68e062798eab50b00b14c3319f03f06465f194d8b133f5641607e4bce`.
79
+ - Plugin manifest: `a8c5f3b2295ac58c935eb6df65617dfc887f1fd357cc989ee0400752d8232606`.
80
+ - N-1 `bun.lock`: `de66be194ed847153324d9715422355e0984d49fa7c903ac15451a49db171a91`.
81
+
82
+ npm readback confirms 0.31.0 is the only published 0.31.x patch and pins agent-mem
83
+ 0.21.0. The fixture manifest/matrix now match; protocol floor 0.25.0 is unchanged.
84
+ The initial full run at `a182b06b` exposed a fixture-only positive-host gap:
85
+ the now-retargeted published 0.31.0 plugin already requires native parent-row
86
+ evidence, while the fixture injected it only for current source. The unchanged
87
+ published bundle correctly attached tools-only under the test runner parent.
88
+ Approved exception `67147dad` bootstraps that published import with one exact
89
+ `/bin/ps` raw parent-row observation and delegates every other subprocess call.
90
+ Pinned-runtime inspection showed Bun 1.4.2 does not rebind static builtin named
91
+ exports after a default-export mutation and syncBuiltinESMExports. Approved
92
+ amendment `06f31614` therefore uses Bun's built-in `mock.module` only inside the
93
+ published-leg child for `child_process` and `node:child_process`. Every real
94
+ function is exported; only `spawnSync` gets the same exact ps predicate and
95
+ other calls delegate. It adds no runtime dependency or product change.
96
+ Both fixture forms use the shared synthetic row constant; published bytes,
97
+ classifier, marker, parent check and every assertion/timeout remain unchanged.
98
+ This is synthetic host setup, not real native-launch acceptance. The original
99
+ red run was intentionally stopped after the known setup failure (697 seconds,
100
+ exit 143); it is not a completed full-suite result. Pinned Bun 1.4.2 validation
101
+ of the corrected setup passed as recorded below.
102
+
103
+ At committed `b9c5e1ffb13f0991bfe66b1dd5bcd8621f8538b9`, dgx02 used frozen
104
+ root and published-fixture installs, Bun 1.4.2 and typecheck. The focused actual
105
+ published 0.31.0 client/unchanged plugin and protocol-floor 0.25.0 legs passed
106
+ in both directions, followed by production evidence-classifier tests: 7 pass,
107
+ 0 fail, 79 assertions across two files in 2.09 seconds. These are common-control
108
+ and MCP interoperability results under synthetic native-host observations;
109
+ they are not mixed-room or real-Claude acceptance.
110
+
111
+ The same committed-head run then executed
112
+ `node scripts/check-docs.mjs --require-fresh && scripts/check.sh --changed`.
113
+ The changed gate selected the full suite and passed:
114
+
115
+ ```text
116
+ 12014 pass
117
+ 101 skip
118
+ 0 fail
119
+ 311749 expect() calls
120
+ Ran 12115 tests across 934 files. [2010.22s]
121
+ check: no current-invocation agent-hub daemon leaks
122
+ check: OK
123
+ remote_dev: verify dev/agent-hub/.worktrees/release-0.32.0 b9c5e1ffb13f exit 0 in 2067s
124
+ ```
125
+
126
+ Documentation was fresh with 0 stale documents; roles, inventory (3281 entries,
127
+ 2276 classified, 1005 baseline), bundle freshness and package contents (795 files,
128
+ 0.32.0) passed. The run's total wall time includes the focused leg above.
129
+ This following report-only commit records that completed result; it does not
130
+ inherit a new full-suite claim for itself. Final-head documentation/delta checks
131
+ and hosted CI are recorded in the PR, and exact-head CI remains the merge gate.
132
+
133
+ ## Known gaps, not blocking
134
+
135
+ - No installation, running-hub upgrade, real account, live TLS/proxy or two-machine
136
+ room run is performed by this preparation. No mixed published-version room
137
+ execution is claimed: the N-1 room directions in CHANGELOG follow unchanged
138
+ codecs/source comparison and isolated current-version broker tests.
139
+ - An offline stop snapshot is a bounded attempt behind the existing FIFO head,
140
+ not a guarantee under backpressure. Legacy brokers without ephemeral support
141
+ do not refresh or age; upgrade the broker for that liveness behavior.
142
+ - A 0.31.0 CLI drops additive advisory/hold fields and outstanding preview IDs,
143
+ including in JSON. A 0.32.0 client accepts absent metadata from a 0.31.0 daemon
144
+ and keeps its prior state-only/fingerprint-confirmation behavior. New displays
145
+ require the matching CLI and daemon.
146
+ - #1204 remains open for T4 visibility, deferred-inbound wake and full codec
147
+ features. #1173 still waits for supported activation/fresh host attestation in
148
+ agent-mem#281. Existing live gates for #1053, #1056 and #1196 remain separate.
149
+ - No merge, tag, npm publication or release-consent record is made by this task.