@junghanacs/entwurf 0.23.1 → 0.25.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 (107) hide show
  1. package/BASELINE.md +1 -0
  2. package/CHANGELOG.md +628 -0
  3. package/DELIVERY.md +24 -1
  4. package/FAQ.md +47 -0
  5. package/README.md +5 -3
  6. package/VERIFY.md +20 -2
  7. package/demo/README.md +5 -2
  8. package/docs/acp-backend-rail.md +49 -2
  9. package/docs/setup-clean-host.md +8 -7
  10. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +38 -5
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/context.js +19 -5
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/callback-env.js +68 -0
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/control-send-receipt.js +101 -0
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +6 -2
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +17 -1
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +4 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +53 -6
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +38 -3
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +54 -22
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +20 -6
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-placement.js +24 -4
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +19 -10
  23. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +35 -42
  24. package/mcp/entwurf-bridge/dist/protocol.js +0 -7
  25. package/mcp/entwurf-bridge/dist/scripts/doctor-pi-provider.js +40 -3
  26. package/mcp/entwurf-bridge/dist/scripts/herdr-rails.mjs +72 -0
  27. package/mcp/entwurf-bridge/dist/scripts/probe-bridge-command.js +1 -0
  28. package/mcp/entwurf-bridge/src/index.ts +46 -5
  29. package/mcp/entwurf-bridge/test.sh +2 -2
  30. package/package.json +11 -9
  31. package/pi-extensions/entwurf-control.ts +83 -154
  32. package/pi-extensions/lib/acp/backend.ts +16 -6
  33. package/pi-extensions/lib/acp/context.ts +30 -10
  34. package/pi-extensions/lib/acp/session-store.ts +2 -2
  35. package/pi-extensions/lib/callback-env.ts +78 -0
  36. package/pi-extensions/lib/compaction-send-guard.ts +6 -1
  37. package/pi-extensions/lib/control-send-receipt.ts +108 -0
  38. package/pi-extensions/lib/entwurf-v2-contract-schema.ts +1 -1
  39. package/pi-extensions/lib/entwurf-v2-contract.ts +6 -2
  40. package/pi-extensions/lib/entwurf-v2-production.ts +19 -1
  41. package/pi-extensions/lib/entwurf-v2-runner.ts +23 -1
  42. package/pi-extensions/lib/entwurf-v2-send.ts +82 -6
  43. package/pi-extensions/lib/entwurf-v2-surface.ts +42 -3
  44. package/pi-extensions/lib/fresh-call-composition.ts +56 -22
  45. package/pi-extensions/lib/herdr-fresh-call.ts +25 -7
  46. package/pi-extensions/lib/herdr-placement.ts +24 -4
  47. package/pi-extensions/lib/mux-fresh-call.ts +23 -8
  48. package/pi-extensions/meta-bridge-omp.ts +35 -48
  49. package/protocol.js +0 -8
  50. package/run.sh +306 -53
  51. package/scripts/check-acp-backend-preflight.ts +117 -9
  52. package/scripts/check-acp-carrier-augment.ts +6 -4
  53. package/scripts/check-acp-event-mapper.ts +17 -6
  54. package/scripts/check-acp-prompt-builder.ts +17 -9
  55. package/scripts/check-acp-prompt-lifecycle.ts +23 -6
  56. package/scripts/check-acp-session-reuse.ts +64 -51
  57. package/scripts/check-acp-session-store.ts +31 -8
  58. package/scripts/check-acp-stop-reason.ts +20 -2
  59. package/scripts/check-acp-stream-hooks.ts +22 -5
  60. package/scripts/check-acp-usage-accounting.ts +33 -9
  61. package/scripts/check-entwurf-bridge-boot.ts +16 -1
  62. package/scripts/check-entwurf-v2-surface.ts +289 -10
  63. package/scripts/check-fresh-call-dispatch.ts +4 -5
  64. package/scripts/check-gate-qualification.ts +51 -9
  65. package/scripts/check-herdr-activation.ts +73 -0
  66. package/scripts/check-herdr-fresh-call.ts +30 -6
  67. package/scripts/check-herdr-placement.ts +51 -0
  68. package/scripts/check-herdr-plugin-build.ts +11 -4
  69. package/scripts/check-herdr-plugin.ts +745 -38
  70. package/scripts/check-herdr-sandbox.ts +14 -2
  71. package/scripts/check-install-container.sh +1 -1
  72. package/scripts/check-mux-launcher-fence.ts +37 -0
  73. package/scripts/check-pi-launch.ts +8 -2
  74. package/scripts/check-release-gate-outcomes.ts +165 -0
  75. package/scripts/doctor-pi-provider.ts +44 -4
  76. package/scripts/herdr-rails.mjs +75 -0
  77. package/scripts/lib/mutation-qualify.ts +1 -1
  78. package/scripts/meta-bridge-state.py +18 -1
  79. package/scripts/mutants/acp-backend-preflight.json +20 -0
  80. package/scripts/mutants/bridge-boot-resume.json +26 -0
  81. package/scripts/mutants/bridge-command-boot.json +38 -0
  82. package/scripts/mutants/control-send-receipt.json +235 -0
  83. package/scripts/mutants/herdr-activation.json +16 -0
  84. package/scripts/mutants/herdr-fresh-call.json +15 -4
  85. package/scripts/mutants/herdr-plugin-build.json +7 -11
  86. package/scripts/mutants/herdr-plugin.json +177 -9
  87. package/scripts/mutants/meta-retire.json +22 -0
  88. package/scripts/mutants/mux-fresh-call.json +21 -8
  89. package/scripts/mutants/omp-fresh.json +5 -9
  90. package/scripts/mutants/pack-install.json +4 -4
  91. package/scripts/mutants/pi-floor.json +17 -0
  92. package/scripts/mutants/release-gate.json +48 -0
  93. package/scripts/mutants/retired-sent-surface.json +32 -0
  94. package/scripts/mutants/v2-surface.json +7 -7
  95. package/scripts/probe-bridge-command.ts +1 -0
  96. package/scripts/smoke-acp-session-reuse-live.ts +23 -5
  97. package/scripts/smoke-agy-install-state.sh +2 -2
  98. package/scripts/smoke-codex-fresh-live.ts +94 -13
  99. package/scripts/smoke-copilot-mcp-state.sh +1 -1
  100. package/scripts/smoke-herdr-fresh-call-live.ts +71 -36
  101. package/scripts/smoke-herdr-raw-install-live.sh +90 -5
  102. package/scripts/smoke-meta-install-state.sh +107 -1
  103. package/scripts/smoke-mux-lifecycle-live.ts +9 -2
  104. package/scripts/smoke-omp-fresh-live.ts +4 -2
  105. package/scripts/smoke-omp-mcp-state.sh +1 -1
  106. package/scripts/smoke-pi-provider-state.sh +108 -2
  107. package/scripts/smoke-setup-verdict.sh +18 -2
package/CHANGELOG.md CHANGED
@@ -4,6 +4,634 @@ All notable changes to this project will be documented here. Format follows [Kee
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.25.0 - 2026-09-23
8
+
9
+ ### Added
10
+
11
+ - **The Herdr status pane stopped reporting presence and started reporting capability, in four
12
+ blocks that never merge (#120 P1-A).** `herdr plugin pane open --plugin junghan0611.entwurf
13
+ --entrypoint status` now draws HERDR INTEGRATION, ACTIVATION, RAILS and CITIZENS, and each block
14
+ carries three things that do not merge: `owner` (who is speaking — Herdr's listing is Herdr's
15
+ word about Herdr, the ledger is Entwurf's word about its own install, the rails table is this
16
+ package's own declaration), `observedAt` (`now`, `install-time`, or `static`) and its own
17
+ `outcome`. **There is deliberately no aggregate** — no reducer, no `overall`, no readiness word:
18
+ three of the four blocks answer at different times, so one token over them would have to pick one
19
+ time and lie about the other two, and a host whose Herdr integration is current, whose ledger was
20
+ written last week, and whose runtime has since been deleted is exactly the case a summary word
21
+ erases. A block that could not be read says so in its own voice with the gatherer's named code
22
+ rather than degrading to an empty section, because an empty section and a failed read look
23
+ identical on a screen and only one of them is safe to act on. The render is a pure data-to-data
24
+ leaf (`plugins/herdr/lib/capability-report.mjs`: no `fs`, no `spawnSync`, no `process.env`, no
25
+ dynamic import, no clock) and the gathering that must open files and import an installed module
26
+ lives in `activation-evidence.mjs`, so it stays visible WHICH artifact answered — the checkout or
27
+ the runtime. The measured blocker repaired on the way in: the writer's ledger is XDG **state**,
28
+ not XDG **data**, and the first candidate crashed before drawing any block on a normal
29
+ writer-produced ledger. Herdr `outdated|needs-repair` is block [1]'s own named red while
30
+ `not-installed` stays a reported skip; only `phase=active` grants a runtime fallback, and
31
+ `activating|deactivating` remain visible install-time receipts that grant no binary. THREE READS
32
+ / ONE OPEN is restored behind exactly one spawn callsite. Receipts: `check-herdr-plugin` 48
33
+ assertions, and that lane 24/24 mutants killed in the implementer's scratch mirror and again in
34
+ the coordinator's body.
35
+
36
+ ### Changed
37
+
38
+ - **A control-socket send now reports the acceptance boundary it actually got, and that
39
+ vocabulary is closed (#120 P2).** `entwurf_v2` answered `sent` whether the receiver was idle or
40
+ busy, and prose in four places called the busy path an interrupt or an immediate delivery.
41
+ Neither queue is an interrupt: a busy receiver drains steering after each turn and follow-ups
42
+ only when its inner loop ends, so a later steer overtakes every earlier follow-up and there is no
43
+ order between the two. Sender-visible acceptance is now its own closed axis — `sent |
44
+ queued-steer | queued-follow-up | accepted-unknown-boundary` — preserved through send →
45
+ structured error → runner → surface, while the route/release `SendFinalOutcome` is unchanged.
46
+ `sent` means the turn was TRIGGERED, not that the model has seen the text, and both queues are
47
+ volatile process memory an abort drops. A fallback socket preserves that boundary; a fallback
48
+ mailbox names its own durable enqueue receipt; dirty-lock receipts keep the actual boundary, file
49
+ and refusal reason instead of flattening them. The receiver wire no longer emits a bare
50
+ `delivered:true` or the unused `deliveredAs`. The oracle is the installed `pi-agent-core` itself
51
+ (`test/pi-queue.oracle.test.ts`, now on `check:full` with its exact core pin gated to the pi
52
+ floor): cross-mode overtaking, one-at-a-time drain, no mid-flight merge, and abort loss, proven
53
+ with zero model, network or ACP child turn. `DELIVERY.md`, `docs/acp-backend-rail.md`, the demo
54
+ README and the `entwurf-dev` skill were corrected to those same four words. Three proof holes
55
+ were closed before acceptance: dirty-lock end-to-end field loss, a `test/**` oracle that was not
56
+ on the public floor, and a reachability checker a comment could satisfy.
57
+
58
+ - **pi runtime floor 0.86.0 → 0.87.1 (#120 P4, landed in two measured steps).** Four exact dev
59
+ pins (including P2's direct `pi-agent-core` queue oracle), three closed peer ranges
60
+ (`>=0.87.1 <0.88`), the
61
+ eight-package install/workspace constellation (seven `pi-*` packages plus `chord`), the
62
+ pack-install version matcher, and the support declarations in README, the Herdr install docs, mux
63
+ docs, setup docs, VERIFY and the ACP support matrix now name one floor.
64
+ `[QK:PI-FLOOR-SINGLE-SOURCE]` closes a measured static hole: before this, `check-dep-versions`
65
+ did not read `pnpm-workspace.yaml`, so one stale release-age row failed only later, during
66
+ installation. The five declared 0.87 breaking changes were re-measured rather than relabelled and
67
+ production contact is zero — and the three type fences are what confirm that, not grep
68
+ (`tsc --noEmit`, `-p scripts`, `-p mcp`, all rc=0). The installed 0.87 extension context still
69
+ exposes `isIdle()` / `signal` at the same coordinates and still has no `isCompacting`; the
70
+ `pi-agent-core` queue drain coordinates MOVED (`agent.js` `:98-99` → `:96-97`, `agent-loop.js`
71
+ steering drain `:159` → `:186`, follow-up drain `:162-166` → `:191-197`) while all four
72
+ queue-behaviour QKs stayed green; the sandbox CLI still refuses an unregistered
73
+ `--entwurf-control`. A deterministic gate now writes a REAL 0.87 session JSONL through the
74
+ installed public `SessionManager` with zero model, network or child process and proves basename
75
+ id = header id = manager id, so `check-herdr-placement` re-measures that axis on every run rather
76
+ than carrying a hand-made receipt. **0.86.1 was skipped with a reason:** its coding-agent
77
+ CHANGELOG has no Breaking Changes section at all and `packages/agent` 0.86.1 is an empty entry,
78
+ so it touches none of our surfaces and its provider/CLI improvements arrive inside 0.87.0 anyway.
79
+ ROADMAP's dated ledger carries the per-file sha256 pairs (written with real `packages/...`
80
+ paths, after a prior entry recorded an empty-file hash for a path that did not exist) and the
81
+ per-breaking-change contact receipts.
82
+ **The second step, 2026-09-23: 0.87.0 → 0.87.1, and it is the floor this release declares.**
83
+ 0.87.0 was already superseded on npm (`dist-tags.latest` said 0.87.1) while the prepared
84
+ candidate still named 0.87.0, and no gate reads upstream `latest`, so a person found the gap
85
+ rather than a check. The patch carries **no Breaking Changes section in any of the seven
86
+ package changelogs**, and the contact measurement says why: all six load-bearing files this
87
+ repo tracks are byte-identical across the two tags (their sha256 pairs match the values the
88
+ 0.87.0 entry above recorded, so the ledger cross-checks itself), `packages/agent` changed only
89
+ its CHANGELOG and `package.json`, and the whole `coding-agent/src` diff is three files. Each
90
+ was judged, not skimmed: `--mode` now rejects an invalid value instead of ignoring it, and
91
+ every `--mode` this repo passes pi is `rpc` or `json` (the other `--mode` hits are OMP's CLI,
92
+ a different vendor under the same flag name); the xAI default moved to Grok 4.7 and nothing
93
+ here depends on a provider default; the third is compaction summary wording, which entwurf
94
+ does not own on any rail (#94). Because `pi-agent-core` shipped no source change, the P2
95
+ queue coordinates could not move — and that was re-measured on the installed 0.87.1 rather
96
+ than inferred: `agent.js:96-97`, `agent-loop.js:186` and `:191-197` are where they were. The
97
+ per-floor hand receipts were re-taken too, additively: the sandbox CLI still refuses an
98
+ unregistered `--entwurf-control` byte-identically, and the extension context still declares
99
+ `isIdle()` at `:233` and `signal` at `:237` with no `isCompacting`. **The floor has exactly one
100
+ knob**, which is what made this a small change: `check-dep-versions` derives the peer range
101
+ and `check_pi_runtime_version` derives its FLOOR from the same exact devDep pin, so a
102
+ candidate cannot pin 0.87.1 while advertising 0.87.0. Moving it reddened four surfaces on cue
103
+ — and the gates named two omissions the author had missed: `[QK:MUTANT-FIND-MATCHES-SUBJECT]`
104
+ caught two `pack-install` pin-matcher mutants at `matched 0×`, and the prose scan caught
105
+ `demo/README.md`'s `current floor` and ROADMAP's `pi <version> fence`. The 0.87.0 numbers
106
+ above and in ROADMAP's two dated ledgers are preserved as the measurements of their own day;
107
+ one blind pass had rewritten two ledger receipts and was reverted.
108
+
109
+ - **The Codex first-admission acceptance stays a release MUST, and what a cut must supply to it is
110
+ now written where a cut is taken from.** Reclassifying its absent prerequisite as a SKIP (see
111
+ Fixed) could have been read as demoting the step to an on-demand axis; that boundary is decided
112
+ the other way and gated. #87 made a newly admitted harness's visible-fresh receipt a MUST **after
113
+ measuring what the on-demand wording permitted** — a harness could be admitted as a full citizen
114
+ that `entwurf_fresh_call` cannot open and still pass the whole floor — so demoting Codex would
115
+ reopen exactly that hole. (VERIFY's standing "on-demand host axis" sentence is about
116
+ `smoke-codex-native-push-live`, the operator-loaded-thread probe, and is unchanged.) A SKIP is
117
+ therefore not a softening: `--cut` refuses it as `cut: BLOCKED (MUST SKIP)`, asked of the shared
118
+ shell authority rather than re-implemented, so a cut without this receipt is still impossible and
119
+ a host with no Codex app-server cannot take one — the intended cost of that release stop rather
120
+ than an accident. What changed is that the reproducible green is now stated: VERIFY says
121
+ `release-gate --cut` inherits the operator-supplied triple, and the release skill's P5 carries it
122
+ on the invocation (measured: `release_gate` exports no scrub and unsets nothing, so an exported
123
+ triple already reaches the step — no plumbing was added). **The instructions obey the
124
+ no-inference rule they teach.** The first draft of that P5 derived the pid with
125
+ `pgrep -f 'codex.*app-server' | head -1`, which is the inference the step exists to refuse,
126
+ written onto the page an operator follows, and on a host running several app-servers it hands the
127
+ gate an arbitrary process to verify as "the operator's answer". Each value is now STATED and
128
+ guarded by an explicit `: "${VAR:?...}"` required-value check before any LIVE cost. Three claims
129
+ keep the boundary from drifting back, one per direction, each with its own exact-once mutant:
130
+ `[QK:CODEX-OPERATOR-PREREQ-ABSENT-SKIPS]` (which pins the filter → guard → `skipLive` shape as one
131
+ contiguous sequence, reading the source with comments stripped — a promise in prose is precisely
132
+ where this defect hid for a release, and a first candidate mutant SURVIVED a weaker regex before
133
+ that cell was tightened), `[QK:CODEX-OPERATOR-PREREQ-WRONG-FAILS]`, and
134
+ `[QK:CODEX-OPERATOR-PREREQ-STATED-NOT-SEARCHED]`. The installed-config preflights are
135
+ deliberately OUTSIDE this split and still FAIL on absence: a missing Codex birth, user
136
+ MCP/status/terminal-title config, runtime, bridge, tmux seat or an unanswered launch directory is
137
+ host state `setup` and the Codex doctors own, not a per-invocation input.
138
+
139
+ - **The Herdr plugin manifest version moves 0.4.1 → 0.5.0 for this cut, and nothing else reads
140
+ that literal.** `plugins/herdr/herdr-plugin.toml` is the sole declaration (measured: one hit
141
+ repository-wide); `check-herdr-plugin` asserts semver SHAPE, not a value
142
+ (`scripts/check-herdr-plugin.ts:82`), and `min_herdr_version` stays the measured 0.9.0 floor.
143
+ `plugins/herdr/runtime-lock.json` returns to the `herdr-checkout` candidate carrier for the cut,
144
+ because `certifyLockCoherence` requires an npm lock to name a version that is BOTH already
145
+ published and equal to this checkout's `package.json` — and the version bump lands before the
146
+ publish. That is the same order `dd84ac0`/`3c3a67d`/`47f6d36` recorded: `v0.23.1` and `v0.24.0`
147
+ were both tagged on that carrier and the npm pin returned afterwards. The lock never moves onto an
148
+ unpublished version, and the 0.25.0 npm pin plus its published sha512 is a follow-up commit, not
149
+ part of this cut. The build gate and the README's release sentence move with it, because they read
150
+ the committed lock beside it.
151
+
152
+ - **The Herdr plugin README is rebuilt around the two questions a visitor actually
153
+ arrives with: which route do I want, and what do I type first.** It now opens by saying what the
154
+ plugin is *not* (a second workbench), then a route chooser, the Herdr built-ins this plugin
155
+ depends on versus what Entwurf adds, a quick start that runs from a raw box in nine steps to the
156
+ first status pane, and only then the direct/mux comparison, boundaries, runtime and developer
157
+ detail. Four facts were repaired against source while re-reading it: the install narration is
158
+ four usage notes, not two (`lib/build.mjs:217-227,292-294`); the receipt section named the
159
+ superseded npm 0.23.1 pin instead of the 0.24.0 lock at `deb73f1`; `check-bridge` asserts
160
+ **eight** garden verbs, not seven (`run.sh:4708` `EXPECT_TOOLS`); and an unmeasured "~45 s,
161
+ hundreds of MB" claim was dropped. The route paragraph no longer says the two routes install the
162
+ same *runtime* — they install the same capability package, but this plugin pins an exact
163
+ `@junghanacs/entwurf@<version>` plus its published sha512 while a direct `npm install -g`
164
+ follows whatever version npm is asked for. The neighbouring-plugin table is now explicitly
165
+ "observed mechanism families, not a census or a compatibility matrix", and every family-wide
166
+ property assertion in it (pane-bound addresses, workspace-scoped identity, receipts as a
167
+ non-goal, "structurally the closest to Entwurf") was narrowed to implementation-specific: the
168
+ differentiator stated in prose is Entwurf's record-backed garden id and the no-argument same-id
169
+ `entwurf_resume_call`, not a uniqueness claim about durable addresses. Docs ship with the
170
+ plugin, so the manifest moves with them. Receipts: `check-herdr-plugin` 32 ok,
171
+ `check-herdr-plugin-build` 14 ok, `check-herdr-plugin-profile` 14 ok, `check-dep-versions` ok
172
+ (pi 0.86.0 across 9 ranges, unchanged), `check-node-floor-coherence` ok, all four relative links
173
+ resolve, `git diff --check` clean.
174
+ - **The plugin runtime lock is pinned to npm `@junghanacs/entwurf@0.24.0`** and its published
175
+ sha512, closing the candidate window 0.24.0 shipped in — the second of the two steps the 0.24.0
176
+ entry below names. The integrity was measured here twice, independently: `npm view
177
+ @junghanacs/entwurf@0.24.0 dist.integrity` and `openssl dgst -sha512` over the preserved
178
+ candidate `make` accepted (`/tmp/entwurf-release-candidate-0.24.0.LXYL1S`, sha256
179
+ `fd750bcb…`) agree, and the registry tarball's sha256 equals the candidate's, so the lock names
180
+ the exact bytes the release gate consumed. Lock and `check-herdr-plugin-build` moved together
181
+ (the measured forward of `47f6d36`, version literals and the `IDENTITY` fixture's sha512 moved
182
+ to 0.24.0), the plugin README drops the candidate sentence, and `herdr-plugin.toml` already says
183
+ 0.4.0. Receipts: `check-herdr-plugin-build` 14/14, `check-herdr-runtime-bootstrap` 31 ok with
184
+ `source=npm … version 0.24.0 … npm-coherent=true`, `check-herdr-plugin` 32 ok,
185
+ `check-gate-manifests` 739/59 unchanged, `pnpm check` exit 0 (57 s). Registry smoke: a
186
+ sandboxed `pi install npm:@junghanacs/entwurf@0.24.0` resolves the bridge to the registry
187
+ install and `--list-models entwurf` lists the curated Claude and Cortex anchors; dist-tags
188
+ `latest=0.24.0`, `repair=0.12.8-repair.1` preserved.
189
+ - **`smoke-herdr-raw-install-live` says the eighth verb too.** Its verb cell keeps an
190
+ independent copy of the shipped `EXPECT_TOOLS` set on purpose, so an upstream verb reddens it
191
+ until somebody says so there — and `entwurf_callback` had not been said. First run on the
192
+ public remote at `deb73f1` with the npm 0.24.0 runtime: every acquisition, identity, [7]/[8a]
193
+ 0-token citizen and teardown/reinstall cell green, only that copy red; with the eighth verb
194
+ named the smoke is PASS (`ref=main`, runtime `@junghanacs/entwurf@0.24.0`, observed sha256
195
+ `fd750bcb…`). The comment's stale `run.sh:5187` line reference now names the constant instead.
196
+
197
+ ### Removed
198
+
199
+ - **The sender-side `entwurf-sent` transcript box, which had no producer (#120 P3).** Repository
200
+ measurement found one definition, one renderer registration and **zero** producers, while two
201
+ live comments named producers that did not exist. The shipped `ENTWURF_SENT_MESSAGE_TYPE` export
202
+ and the dead `SentBoxData` / builder / renderer / registration are gone; the independent
203
+ receive-side `entwurf-message` producer and renderer are untouched. The remaining receive-side
204
+ comment now states the true boundary — a sender receives an acceptance-boundary text receipt, and
205
+ only a receiver can claim a transcript box was rendered. Two claims keep it retired: AST-based
206
+ `[QK:NO-RENDERER-WITHOUT-PRODUCER]`, so a comment or a string literal cannot satisfy the producer
207
+ proof, and `[QK:RETIRED-SENT-SURFACE-ABSENT]`, which seals both identifiers and the stale
208
+ `entwurf sent` / `sender-side UI box` prose. The two mutants went into their own
209
+ `retired-sent-surface` lane rather than mixing into P2 receipt semantics, and historical
210
+ ROADMAP/demo evidence was kept as history with a dated correction rather than rewritten.
211
+
212
+ ### Fixed
213
+
214
+ - **`showTurnDuration` was never ours, and entwurf had been forcing the operator's own display
215
+ off.** The native meta-bridge install owned a list of Claude Code policy scalars that close
216
+ background autonomy and suggestion surfaces — and this one had been sitting in it, pinned to
217
+ `false`. It closes nothing: it reports how long a turn took, which is operator INFORMATION, so the
218
+ policy reason the rest of that list stands on never applied to it. GLG's ruling is that the
219
+ operator owns the scalar and entwurf must neither own it nor force it. The symptom had been
220
+ misread as drift: `./run.sh doctor-meta-bridge` was FAIL on the source host for exactly one
221
+ managed scalar, with `~/.claude/settings.json` saying `true` and the writer requiring `false`, and
222
+ the install-state carries no per-scalar provenance to say whose value that was. The actual cause
223
+ is measurable and is **two owners on one key**: `check-keyset-overlap` against the operator's own
224
+ `agent-config` settings fragment failed on `showTurnDuration` and on nothing else, because both
225
+ sides claimed it. Dropping our claim is what resolves that collision, with **zero change to
226
+ agent-config**. The key is RETIRED rather than deleted, through the same one-shot path #94 built
227
+ for compaction, because an existing install-state already carries its entry — measured on the
228
+ source host, `original {existed: true, value: false}` under a live `true`, i.e. the operator
229
+ turned the display on after our install — and `uninstall()` walks every ledger entry through
230
+ `restore_entry`, so deleting the constant would have written `false` back over that choice.
231
+ `[QK:META-RETIRE-TURN-DURATION-OPERATOR-OWNED]` measures the premise instead of quoting it: it
232
+ asks the SSOT whether the key is claimed, pins the retired comparison value by type as well as
233
+ equality, drives the real relinquish leaf on the exact host shape above, and proves the ledger row
234
+ is consumed so no later inverse can reach the key. Its mutant drifts the scalar back under
235
+ management. **The migration ran on the host and was measured on both sides:** before
236
+ `showTurnDuration=true`, after `showTurnDuration=true`, the ledger entry gone (15 managed keys
237
+ → 14), and `doctor-meta-bridge` now PASS with 33 ok where it had been exit 1 on that one axis.
238
+
239
+ - **A release MUST step reported a missing operator input as a FAILURE, so the aggregate gate was
240
+ structurally red on every host at every cut.** `smoke-codex-fresh-live` takes three values only
241
+ the operator can supply — the app-server pid and two model tiers — and `release_gate()` supplies
242
+ none of them, by design: entwurf neither starts nor supervises that app-server. Its own skip line
243
+ promised four prerequisites in one breath (`LIVE=1` plus the three env names), but only the first
244
+ could produce a SKIP; the other three were `ok()` assertions, and `ok()` throws. So the step
245
+ declined as a defect. `scripts/lib/step-outcome.sh` had already written the rule this broke —
246
+ *"a step that RAN AND BROKE and a step that NEVER RAN are different facts, and a release record
247
+ that blurs them is worthless: the first is a defect to fix, the second is a prerequisite to
248
+ supply"* — and the candidate receipt below shows the cost: `MUST PASS=23 FAIL=1`,
249
+ `cut: BLOCKED (MUST FAIL)`, with a prose classification standing in for a floor. **The repair is
250
+ the classification, not the requirement.** An ABSENT input is now a protocol SKIP naming each
251
+ missing one; a PRESENT but wrong one — a malformed or dead pid, a process whose argv is not a
252
+ Codex app-server, a foreign uid, an app-server sharing the gate's own session — stays a FAIL.
253
+ Both arms were measured, and neither opens a window or spends a turn: unset triple under `LIVE=1`
254
+ exits 97 with its `[entwurf:skip]` line, `ENTWURF_CODEX_APP_SERVER_PID=abc` exits 1 naming the
255
+ value. No inference was added and no default invented for a value nobody gave.
256
+
257
+ - **The Herdr status pane died before drawing a single block on the carrier every candidate window
258
+ rides.** `plugins/herdr/lib/activation-evidence.mjs` located the installed ledger reader by
259
+ reading the package directory name off `runtime-lock.json` — but that file is a discriminated
260
+ union and only its `npm` arm carries a `name`, so on the `herdr-checkout` arm
261
+ (`{source, repository}`) the name was `undefined` and `path.join` answered it with a `TypeError`
262
+ raised OUTSIDE the `try` that was supposed to name every failure this module can have. Measured on
263
+ this cut: with that lock committed, `check-herdr-plugin` reported `block was ""` and every one of
264
+ its 48 assertions stood on a pane that had crashed. The carrier is not an edge case — `v0.23.1`
265
+ and `v0.24.0` were both tagged on it. The gatherer now takes `checkoutRoot` and asks
266
+ `readCheckoutPackageSpec`: the manifest is the one name both lock arms already agree about, since
267
+ the npm arm is certified coherent with it and the checkout arm packs that very manifest. The join
268
+ moved inside the same `try`, so a locator that ever yields a non-string is a named
269
+ `activation-reader-unavailable` rather than a process death. `VERIFY.md` had already written this
270
+ rule down for `smoke-herdr-raw-install-live`; the lesson had not crossed to the gatherer `79391fb`
271
+ added. `[QK:HPL-PACKAGE-NAME-FROM-MANIFEST]` measures the premise instead of quoting it — it
272
+ writes a checkout-arm lock and asks the real reader what `.name` is — then requires the module to
273
+ ask the manifest and never the lock, with comments stripped first so prose cannot satisfy it; its
274
+ mutant drifts the call back to `readRuntimeLock`.
275
+
276
+ - **`doctor-pi-provider` called every Herdr-plugin host red while its bridge booted and served all
277
+ eight verbs (#120 P1-B).** The install-state's `ownership` field is a PREIMAGE classification —
278
+ what stood at the key BEFORE that install — so `absent` means "we created it", not "nobody owns
279
+ it"; reading it as an ownership verdict is what produced the false red. The doctor now reads the
280
+ recorded `command` beside it, because that string and not the bare name is the canonical managed
281
+ command (`register-pi-provider.py` writes the same string into the settings entry and into the
282
+ record), and in plugin mode it is an absolute bridge under the certified stable runtime, since a
283
+ Herdr-plugin host has nothing `entwurf` on PATH. For a known managed preimage carrying a recorded
284
+ command the comparison now runs REGARDLESS of whether the effective command happens to be the
285
+ bare name: keeping that decision inside a `!isBare` branch is how real drift escaped — a record
286
+ naming the absolute plugin bridge with the settings repointed at bare `entwurf-bridge` is drift,
287
+ and a bare bridge that boots would otherwise have carried it to green. A record that names no
288
+ command, or one whose ownership is a word this doctor does not know, falls through to the
289
+ original logic unchanged; the state is rewritten by `./run.sh setup`, never by its reader.
290
+
291
+ - **Three qualification control reds, which were gate defects rather than product ones (#120).**
292
+ The first committed body at `bad2677` ran 69m42s and ended 746/777, with the remaining 31 voided
293
+ only by baseline-red controls. `[QK:MUTANT-GATES-INSIDE-FULL-FLOOR]`: `check-control-send-receipt`
294
+ and `check-entwurf-v2-runner` were valid narrow mutant coordinates but absent from `check:full`,
295
+ so they are now wired into `check:contracts` (~1.5 s combined, measured) while keeping their
296
+ source-adjacent discovery. `[QK:LAUNCHFENCE-EXPOSED-SMOKE-WIRED]`: P1's new XDG data-root cell
297
+ moved `smoke-herdr-raw-install-live.sh` into launcher-fence scrutiny, and the fence's env-block
298
+ parser understood only TypeScript object literals, not a continued shell `env` command — it now
299
+ reads that actual invocation and grants the exemption only when `HOME=` is relocated in the same
300
+ block, with a negative cell deleting that assignment to prove the exemption disappears.
301
+ `smoke-setup-verdict` S-8's intermittent second-run idempotence red had its output discarded by a
302
+ `| grep -q`, leaving no causal receipt; the second setup output is now captured and printed on
303
+ failure. No retry, sleep, enlarged timeout or speculative behaviour change was added, and S-8 is
304
+ explicitly NOT claimed fixed — it did not recur in the accepted body, and if it returns the next
305
+ receipt carries the output instead of one context-free FAIL line.
306
+
307
+ - **Two stale oracles in `smoke-codex-fresh-live`, both surfaced by the LIVE floor of this cut.**
308
+ (a) The step read a control-socket receipt as successful only when it said `sent|delivered`.
309
+ `6d90d73` widened two sibling LIVE smokes to the closed four-value axis and named the reason in
310
+ `smoke-herdr-fresh-call-live.ts:89` — *"the old literal `→ sent` pin would have gone red on the
311
+ common case and passed only when the caller happened to be idle"* — and this third one was missed.
312
+ Measured on this exact step: the fixture addresses the initial Pi mid-turn, the rail answered
313
+ `queued-follow-up`, and a release-gate MUST went red on a delivery that had succeeded. Before P2
314
+ it passed on the busy path only because the rail said `sent` either way, which is the dishonesty
315
+ P2 removed; `delivered` was never a member of the set. (b) The initial-Pi exact-once audit still
316
+ expected the PRE-0.24.0 callback shape — an `entwurf_v2` carrying the nonce as arguments — while a
317
+ pi sibling's first action has been the zero-argument `entwurf_callback` since 0.24.0, as this
318
+ smoke's own launch receipt says in the same run: *"its first action is the zero-argument callback
319
+ verb (codex is the one arm that still sends the nonce as arguments)"*. A sibling obeying the
320
+ shipped framing was therefore reported as an unexpected call. Only the pi branch moved; the Codex
321
+ audit keeps `entwurf_v2`, because codex really is still the argument-carrying arm
322
+ (`codex-callback-env-unsupported`). This is the class `e506716` closed for
323
+ `smoke-herdr-raw-install-live`, and it stayed hidden in 0.24.0 because that cut's run died earlier
324
+ at the Pi→Codex template relay. Neither repair weakens an assertion: `{}` is matched by
325
+ `isDeepStrictEqual`, so it pins the verb's zero-argument contract rather than accepting any
326
+ arguments, the exact-once count is unchanged, a refusal still fails, and the callback-nonce
327
+ correlation was never carried by that audit — it is proven earlier and from the DELIVERED body by
328
+ `the initial Pi callback carries the exact launch nonce`, which reads the fixture's own mailbox,
329
+ requires exactly one exact match and refuses duplicates.
330
+
331
+ ### Verification
332
+
333
+ Frozen candidate `69a6f1d`, host `oracle`, 2026-09-23 KST. Two receipts, kept apart on purpose.
334
+
335
+ - **Deterministic floor:** `pnpm run check:full` exit 0, 607 s.
336
+ - **Aggregate LIVE floor**, `LIVE=1 ./run.sh release-gate <scratch> --cut` with ambient identity
337
+ carriers stripped, scratch `/tmp/entwurf-release-gate-0.25.0-final.HghiXh`:
338
+ **MUST PASS=23 FAIL=1 SKIP=0**, **BEHAVIOR PASS=1 FAIL=0 SKIP=0**, `cut: BLOCKED (MUST FAIL)`.
339
+ Inside it, `check-gate-qualification` killed **778/778** with `[gate-qualification] ok` and both
340
+ purity axes green — measured on these exact bytes, which is why the aggregate was re-run after the
341
+ two gate commits above: `scripts/smoke-codex-fresh-live.ts` is the subject of three committed
342
+ mutants (`CODEX-LIVE-EVIDENCE-RECORD-PATH`, `CODEX-LIVE-EVIDENCE-RECOVERY-FAILSAFE`,
343
+ `CODEX-LIVE-RECOVERY-SOURCE-ONLY`), and all three killed at `sha256=40c7852fc273…`.
344
+ - **The one MUST red is the shape of that step, not a defect.** `release_gate()` supplies no
345
+ `ENTWURF_CODEX_APP_SERVER_PID`, and `smoke-codex-fresh-live` refuses to infer one — VERIFY: *"The A
346
+ cell takes no inferred server or models"* — so it declines inside every aggregate on every host.
347
+ Its pair is the standalone run on the SAME frozen `69a6f1d`: `LIVE=1
348
+ ENTWURF_CODEX_APP_SERVER_PID=3969303 ENTWURF_CODEX_FRESH_MODEL=gpt-5.6-luna
349
+ ENTWURF_CODEX_FRESH_PI_MODEL=openai-codex/gpt-5.6-terra ./run.sh smoke-codex-fresh-live` →
350
+ **66 assertions ok, exit 0**, `initial-pi=3/3 completed exact`, `codex=3/3 completed exact`.
351
+ The Pi tier is terra rather than luna, and that is a recorded tier choice, not a gate change: the
352
+ 0.23.1 follow-up already measured luna losing relay fidelity where terra preserves it, and on this
353
+ cut luna pre-substituted the `<placeholder>` template the smoke audits for byte equality
354
+ (ROADMAP 0.24.0 follow-up 3). The receipt this cell is accepted on is the run that actually ran.
355
+ - **Host residue (P9, run at the gate's verdict and again at the end of prepare):** 588 → 586 dirs,
356
+ 2 roots / 112 K reclaimed, by prefix `entwurf-s2b-overlay` and `check-probe-ordering`. One
357
+ reparented process holds a `/tmp` path and was named rather than killed: pid 489788, the
358
+ operator's own `emacs --init-directory=/tmp/agent-emacs-init --daemon`, which no gate mints.
359
+ - **Open, pre-cut, not resolved by this candidate:** `./run.sh doctor-meta-bridge` is FAIL on this
360
+ host for exactly one managed scalar — `settings showTurnDuration missing/drifted`, where
361
+ `~/.claude/settings.json` says `true` and `scripts/meta-bridge-state.py:90` requires `false`.
362
+ Every other axis of that doctor reads ok (hook launch form, doorbell static contract, statusline,
363
+ user-scope MCP reachability, cached artifact). The install-state carries no per-scalar provenance,
364
+ so whether this is drift from our write or the operator's own UI choice is not decided here, and
365
+ the host was deliberately left untouched.
366
+
367
+ - **Resolved 2026-09-23, after the paragraph above was written.** That doctor red was not drift and
368
+ not undecidable: the key had two owners, and GLG ruled the operator owns it. The scalar is retired
369
+ (see Fixed), and the host migration was measured on both sides — `showTurnDuration=true` before
370
+ and after, the install-state ledger entry consumed (15 managed keys → 14), and
371
+ `./run.sh doctor-meta-bridge` now **exit 0, PASS, 33 ok**. The paragraph above is kept as what was
372
+ true when the frozen candidate was measured.
373
+
374
+ **Post-prepare release-fix candidate (2026-09-23, host `oracle`) — source, focused and host
375
+ receipts only.** This is not a cut receipt. `check-gate-qualification`'s body (`782/782` killed,
376
+ `[gate-qualification] ok`) and `pnpm run check:full` (`total elapsed 603s exit 0`) ran on the earlier
377
+ frozen seal (`git diff --cached --binary | sha256sum` =
378
+ `ee478d4eac77aae6db187eeb5da2d357ab275c00e6d1eb9463ca04f29bf42425`), before this paragraph and two
379
+ documentation lines were amended; they are not re-attributed to the amended bytes. When this
380
+ paragraph was written (2026-09-23, before 09:44 KST) the LIVE release gate (`--cut`) had not been run
381
+ on this candidate, so this paragraph by itself does not speak to releasability; the LIVE receipt is
382
+ the dated addendum below, and it belongs to the `00264f…` seal only. What was measured:
383
+
384
+ - **Focused gates, green on the working candidate:** `check-dep-versions`
385
+ (`ok — pi 0.87.1 is coherent … 9 range + 1 exact + 3 prose declarations`), `check-gate-manifests`
386
+ (**782** committed mutants across 62 lanes, every `find` still matching its subject exactly once,
387
+ lane inventory matching its declared contract), `check-release-gate-outcomes`,
388
+ `smoke-meta-install-state`, `smoke-meta-keyset-guard`, `check-keyset-overlap` against the
389
+ operator's own agent-config fragment (exit 1 → exit 0, `keyset disjoint ok: 1 fragment vs 15
390
+ pi-owned key(s)`), `check-meta-doctor-oracle`, `check-meta-manifest-schema`, `smoke-meta-honesty`,
391
+ `smoke-setup-verdict`, `smoke-user-scope-citizen`, `smoke-meta-prune`, `check-pi-runtime-version`,
392
+ `check-pi-import-surface`, `check-model-lock`, `check-node-floor-coherence`, `check-pi-preflight`,
393
+ `check-pi-launch`, `check-pack-pin-matcher`, `check-pack` (540 files), `check-herdr-activation`,
394
+ `check-herdr-placement`, `check-pi-queue-oracle`, `check-entwurf-v2-surface`,
395
+ `check-control-send-receipt`, `check-compaction-send-guard`, `check-mux-launcher-fence`,
396
+ `check-codex-bridge-identity`, `check-codex-native-push`, `check-env-namespace`,
397
+ `check-shell-quote`, `check-tests-beside-behavior`, `check-install-surface`,
398
+ `smoke-agy-install-state`, `check-acp-sdk-surface`, `check-mux-fresh-call`. Three type fences
399
+ `tsc --noEmit` / `-p scripts` / `-p mcp` rc=0, and `pnpm lint` at 0 errors / 22 warnings — the
400
+ same counts a pristine HEAD produces, measured in its own snapshot for the comparison.
401
+ - **Four new mutants, each killed at its own claimed signature** in a scratch snapshot mirror with
402
+ control-pre and control-post green, because the shared checkout is never mutated in place:
403
+ `META-RETIRE-TURN-DURATION-OPERATOR-OWNED`, `CODEX-OPERATOR-PREREQ-ABSENT-SKIPS`,
404
+ `CODEX-OPERATOR-PREREQ-WRONG-FAILS`, `CODEX-OPERATOR-PREREQ-STATED-NOT-SEARCHED`. The
405
+ release-gate lane inventory moves 19 → 22 and meta-retire 3 → 4.
406
+ - **`check-install-surface` was re-run the only way it can tell the truth about an unstaged
407
+ edit, and that mattered.** It reads the candidate through `git show :<file>`, so a first run over
408
+ a working-tree-only change is a false green — a trap `NEXT.md` already documented from the
409
+ previous release. Two U+2014 em dashes had in fact landed in the ASCII-only release skill at
410
+ `(334,64)` and `(347,9)`. They are gone, and the proof was taken in a scratch snapshot where the
411
+ whole candidate was `git add -A`ed (**exit 0**, S7e and S6's 494 NUL-free sources included), with
412
+ a negative control planting one em dash back to confirm S7e really reddens rather than passing
413
+ vacuously. No staging was performed in the shared checkout.
414
+ - **Host receipts (operator-owned steps, run by the coordinator under GLG's authority and verified
415
+ independently here):** the retired-scalar migration applied with the operator's `true` intact and
416
+ the ledger entry consumed; `doctor-meta-bridge` PASS; the global pi CLI moved 0.87.0 → **0.87.1**
417
+ and this repo's installed `pi-coding-agent` reads **0.87.1**. The running pi coordinator process
418
+ may predate that upgrade, so it is not used as evidence for it — the versions above were read from
419
+ the CLI and the installed package.
420
+
421
+ **LIVE release gate on the amended candidate (2026-09-23 09:44–11:15 KST, host `oracle`) — dated
422
+ addendum.** Candidate seal: HEAD `61cde06755c1eb353c2338c314cdf1eed384b26e`, `git diff HEAD --binary |
423
+ sha256sum` = `git diff --cached --binary | sha256sum` =
424
+ `00264f9177cfc2fde16d468299f12a4d00abc8d54fad1fd3c6c70df06d56478f`, unstaged 0, untracked 0. The
425
+ coordinator's launcher refused to start unless HEAD and both seals matched, and printed them again
426
+ after the run: pre and post identical. That seal is the bytes with the D1/D2/O2 documentation
427
+ amendment above and WITHOUT this addendum; the result is not re-attributed to the documentation-only
428
+ seal that adds these lines. The 69a6f1d `FAIL=1` receipt and the `ee478d…` seal receipts above stay
429
+ as history.
430
+
431
+ - **Command:** `env -u CLAUDE_CONFIG_DIR -u PI_SESSION_ID -u PI_AGENT_ID LIVE=1
432
+ ENTWURF_CODEX_APP_SERVER_PID=3067196 ENTWURF_CODEX_FRESH_MODEL=gpt-5.6-luna
433
+ ENTWURF_CODEX_FRESH_PI_MODEL=openai-codex/gpt-5.6-terra ./run.sh release-gate
434
+ /tmp/entwurf-release-gate-0.25.0-00264f --cut`, exit 0. The app-server pid is the pane pid of the
435
+ one the coordinator started (on GLG's direct instruction) in its own tmux session
436
+ `entwurf025_codex_server`; it was stated, not searched. Its uid and exact `app-server --listen`
437
+ argv were checked through `/proc`, and its session differs from the gate's.
438
+ - **Log (cross-host artifact):** `/tmp/entwurf-release-gate-0.25.0-00264f/release-gate.log`, sha256
439
+ `100c61a5721dee3e693a5f6d4851138d52172ad5301a231431a10478237bf20d`. The decisive lines, verbatim:
440
+
441
+ ```text
442
+ [check:full] total elapsed 621s exit 0
443
+ [gate-qualification] origin checkout: HEAD + work-surface content hash identical before/after
444
+ [gate-qualification] qualified claims: 782/782 killed — …
445
+ [gate-qualification] ok
446
+ [smoke-codex-fresh-live] 64 assertions ok — fixture -> initial visible Pi -> visible Codex/exact callback -> Pi-addressed native-push v2 -> Codex outbound v2 -> visible Pi/exact callback
447
+ MUST: PASS=24 FAIL=0 SKIP=0
448
+ BEHAVIOR: PASS=1 FAIL=0 SKIP=0
449
+ mode: --cut (a MUST SKIP is a BLOCKER — this run is being read as release acceptance)
450
+ cut: OK
451
+ ```
452
+
453
+ - **Claude Code host:** `claude --version` = 2.1.280, which is above the `>=2.1.217` floor. Host
454
+ doctor log `/tmp/entwurf-0251-claude-280-doctor.log`, sha256
455
+ `568f73883a60025e370b5f45cba111620a60c45af351165bab25ddfa0d5be1e7`, exit 0, PASS.
456
+ - **P9, run right after the verdict:** the prefix-blind census found one reparented process holding
457
+ a `/tmp` path (pid 489788, the operator's own emacs). It is not gate residue and was left alone.
458
+ `OWNED` derived 107 prefixes. Idle roots older than 60 minutes: 3 (128K). Two were
459
+ `check-probe-ordering` and one `entwurf-s2b-overlay`; they were inspected, then removed. After P9,
460
+ the operator owned 592 directories under `/tmp`.
461
+
462
+ ## 0.24.0 - 2026-09-20
463
+
464
+ ### Added
465
+
466
+ - **`entwurf_callback` — the eighth garden verb, and it takes no arguments.** The fresh-call
467
+ callback was the one place in the garden where the transcript was the address axis: the caller
468
+ garden id and the launch nonce reached a new sibling only as prose in its first prompt, and the
469
+ sibling had to retype both into `entwurf_v2`. Three releases of symptoms were one cause — 0.23.1
470
+ dropped `$`/`@` sigils, this lane's first release gate filled in `<placeholders>` early, its second lost the
471
+ trailing character of a garden id (`…-26428c` → `…-26428`, refused correctly by the decider) —
472
+ and the protocol had no error-detecting property: a mangled id is indistinguishable from a
473
+ different one, so the decider could only refuse, never repair. GLG's ruling: "luna만 못하는게
474
+ 아니라 구조적 문제". Now both launch rails inject `ENTWURF_CALLBACK_TARGET` and
475
+ `ENTWURF_CALLBACK_NONCE` beside the identity scrub (tmux `-e` in `mux-fresh-call`,
476
+ `tab create --env` in `herdr-fresh-call`), computed by the rail and never caller-chosen;
477
+ `callback-env.ts` is the single leaf that formats and parses the pair, so a drifted regex cannot
478
+ inject what the verb rejects; and `entwurf_callback {}` reads the pair from its own process,
479
+ validates garden-id and nonce grammar (exactly what `mintNonce` emits), and dispatches through
480
+ the existing v2 runner so the target is still re-resolved by record and decider (Hard Rule 2).
481
+ Absent or malformed env is a named refusal with no fallback to a model-supplied target (Hard
482
+ Rule 5). Framing for pi, claude-code, copilot and omp now says only "call the callback with no
483
+ arguments"; a separate `FRESH_CALL_DELIVERY_TOOL` names the v2 dialect for the task result, and
484
+ Claude's `--allowedTools` and ToolSearch hint carry callback, delivery and peers together (the
485
+ review found the first cut loaded only two, which would have left a claude-code sibling unable
486
+ to send its result). OMP's bootstrap payload moves to v2 with the closed shape `{v, task}`; a
487
+ leftover `target`/`nonce` key is refused. Measured before the seam was written: the
488
+ entwurf-bridge MCP child inherits the pane env for claude-code, copilot and omp (canary in
489
+ `/proc/<child>/environ`, private tmux, fixture roots); pi is in-process. **Codex is the named
490
+ exception:** the bridge serving a Codex tool call is the operator app-server's child (ppid
491
+ measured) and its identity is request-scoped `_meta.threadId`, so a window env never reaches
492
+ it; `entwurf_callback` refuses there as `codex-callback-env-unsupported`, the Codex framing
493
+ keeps the argument form and says why, and a per-thread carrier is a follow-up after a
494
+ measurement that does not exist yet. The separate Pi→Codex template relay in
495
+ `codex-fresh-live-protocol.ts` (a semantic `<placeholder>` template pushed through a model and
496
+ audited for byte equality) is recorded, not fixed. Surface gates moved with the verb
497
+ (`check-entwurf-bridge-boot` exact eight-verb set + `QK:BRIDGEBOOT-CALLBACK-ZERO-ARG`,
498
+ `check-probe-bridge-command`, `check-bridge`, install/state smokes, README/VERIFY); manifests
499
+ 735 → 739 mutants, 59 lanes. Commits `cb86d44`, `ff09522`, `ca04c1b`; designs by
500
+ `claude-opus-5` and `openai-codex/gpt-5.6-terra`, Codex verdict and implementation by
501
+ `xai/grok-4.6`, closing review by a fresh `claude-opus-5`.
502
+ - **`plugins/herdr` 0.4.0.** The plugin's install-time build now reaches a pi range check
503
+ (`install-user-scope`, above), its README installs pi at the published peer range, and its
504
+ runtime lock rides the 0.24.0 candidate window. A minor rather than a patch because a host that
505
+ activated 0.3.1 against an out-of-range pi was silently accepted and is now refused by name.
506
+
507
+ ### Changed
508
+
509
+ - **pi floor moves to 0.86.0 (`>=0.86.0 <0.87`), and the ACP provider reads the 0.86 shape
510
+ honestly.** pi 0.86 hands a custom provider a branded `TranscriptContext`: `systemPrompt`
511
+ and `tools` are folded into a leading `role:"system"` message (`normalizeContext`,
512
+ pi-mono `model-runtime.ts:639-642`, `v0.86.0` = `ecac0a9c`). The ACP preflight read
513
+ `context.tools`, which is always `undefined` there, and fell back to the full builtin set —
514
+ so `assertExcludeToolsHonored` could never fire, while `tsc` (the old parameter type is
515
+ assignable) and `check-acp-backend-preflight` (a 0.85-shaped literal fed straight to
516
+ `streamAcpTurn`) both stayed green. Measured on real `pi-ai@0.86.0`: raw Context with `bash`
517
+ excluded → THROW; the same input through `normalizeContext` → PASS with no throw. The adapter
518
+ now replays the transcript (`getCurrentTools(context.messages)`, no fallback — an empty
519
+ replayed list is "the operator excluded everything" and is rejected as such),
520
+ `renderMessage` drops system messages by an explicit `case` so the never-forward-systemPrompt
521
+ invariant is a decision rather than a `default`, and every provider-path gate builds its
522
+ fixture through `normalizeContext()`. New lane `acp-backend-preflight`
523
+ (`[QK:ACP-PREFLIGHT-REPLAYS-TRANSCRIPT-TOOLS]`, exact-once mutant = the old fallback).
524
+ Session-reuse signatures now hash the folded leading system message, so a record persisted
525
+ under 0.85.1 is not a prefix of a 0.86 turn and falls back to `new` (the designed safe path).
526
+ - **Pin move, with every mirror.** devDeps 0.86.0; peers `>=0.86.0 <0.87`; the eight explicit
527
+ `check-pack-install` pins and its leak regex (`@0\.86\.0(_|$)`, boundary lookalike now the
528
+ prefix-extended `0.86.0-beta.1`); lockfile (typebox resolves to a single 1.3.27); baseline
529
+ docs; ROADMAP bump ledger. `check-dep-versions` now excludes dated ledger entries from its
530
+ baseline scan (`stripBumpLedger`, guarded both ways), because a ledger line is a receipt of
531
+ the range that landed THAT day and the first bump after an entry is written is what exposed
532
+ it. Vendor floors were re-measured rather than carried: a real 0.86.0 session file
533
+ (`herdr-placement.ts`), the installed `types.d.ts` (`compaction-send-guard.ts`), the
534
+ unregistered `--entwurf-control` refusal (`check-pi-launch.ts:18`); the registered-host
535
+ duplicate-flag receipt at `check-pi-launch.ts:29` stays dated 0.85.1 with the reason.
536
+ Constellation shrank for the first time (`pi-client`/`pi-protocol` left pi-coding-agent's
537
+ direct deps at 0.86.0); all eight pins are kept on purpose — a retired pin is how a caret
538
+ re-enters unwatched.
539
+ - **The two doors onto one pi registration tell the same truth (#119).** `entwurf setup`
540
+ refused an out-of-range pi by name, but `run.sh install-user-scope` — the verb herdr plugin
541
+ activation actually reaches (`build.mjs` → `herdr-plugin-activate.mjs`) — had no range check
542
+ and wrote the citizen in silence. It now enforces the same closed range through the same
543
+ helpers (`pi_supported_range`/`pi_version_in_range`, one parser), refuses with
544
+ `[install-user-scope] pi: FAIL … outside the supported range` and zero bytes written; absent
545
+ pi is deliberately unchanged (this verb invents no presence verdict). `check-herdr-activation`
546
+ gains `[QK:HAC-PI-FLOOR-BOTH-DOORS]` (24 → 25, the out-of-range/in-range PAIR, fixtures
547
+ derived from the package.json pin) and pins `PI_BIN` in its sandbox `world()` — without that
548
+ the gate's verdict depended on the operator's installed pi. `plugins/herdr/README.md` now
549
+ installs pi at the published peer range and joined `check-dep-versions` BASELINE_DOCS, so
550
+ the number cannot drift. Since 0.86.0 published (2026-09-19T23:15Z) the old unpinned line
551
+ installed a pi the shipped range rejects; the plugin had no known installs, so the repair is
552
+ a correction, not a migration.
553
+ - **Provider-path fixtures are built through `normalizeContext`, and a cast past the brand is
554
+ now a red gate.** The full floor on the bump commit went red in four ACP gates
555
+ (`check-acp-stop-reason`, `-prompt-lifecycle`, `-stream-hooks`, `-usage-accounting`) as
556
+ `'error' !== 'done'` — the new preflight correctly rejecting seven fixture helpers that cast a
557
+ tool-less literal `as Context`, which is exactly the operation that silences the
558
+ `TranscriptContext` brand at typecheck time. Every helper now declares the four pi builtins
559
+ through `normalizeContext`; no production source or assertion text changed.
560
+ `check-acp-backend-preflight` gains a static sweep (`[QK:ACP-FIXTURE-NO-CONTEXT-CAST]`) over
561
+ `scripts/check-acp-*.ts` and the lib tests so that class cannot come back silently; no mutant
562
+ by decision (the claim is the absence of a token and has no production subject — its
563
+ kill-proof is an injected offender named by `file:line`). Full floor on `5d3140c`:
564
+ `check:full` exit 0 in 598s. Manifests: 733 → 735 mutants, 58 → 59 lanes.
565
+ - **The first release gate of this lane (then numbered 0.23.2) was red on three MUST steps, and
566
+ two of them were this lane's own holes** (`/tmp/entwurf-release-gate-0.23.2.3s5A0o`, `MUST: PASS=21 FAIL=3 SKIP=0`).
567
+ `check-gate-qualification` 733/735: both `pack-install` mutants still quoted the old run.sh
568
+ `@0\.85\.1(_|$)` line after the pin mirror moved it (MUTANT-STALE), and nothing in
569
+ `check:full` looks at whether a `find` still matches — `check-gate-manifests` proved the
570
+ subject was tracked but never opened it. The finds now mirror run.sh byte-exact and the head
571
+ gains `[QK:MUTANT-FIND-MATCHES-SUBJECT]` (every `find` matches its subject exactly once, using
572
+ the body's own `countOccurrences`), which would have caught this at commit time instead of
573
+ fifty minutes into the gate. `smoke-acp-session-reuse-live`: `turn1`/`turn2` were annotated
574
+ `: Context` with no tools and the preflight rejected them — the third fixture of this class,
575
+ and the sweep written to stop it had globbed only `check-acp-*` and only the cast spelling.
576
+ Both turns build through `normalizeContext`; the sweep now covers `scripts/smoke-*.ts` and the
577
+ annotation form (indexed-access types such as `Context["messages"]` excluded), and retyped
578
+ `startTurn` in `check-acp-prompt-lifecycle` on the way (`5aeb8d4`). The third red,
579
+ `smoke-codex-fresh-live`, was a host prerequisite: the gate shell did not export
580
+ `ENTWURF_CODEX_APP_SERVER_PID` / `ENTWURF_CODEX_FRESH_MODEL` / `ENTWURF_CODEX_FRESH_PI_MODEL`
581
+ (the app-server was alive, PID 3969303); the rerun carries them. An independent audit
582
+ (`openai-codex/gpt-5.6-terra`, read-only) re-measured every mutant `find` against its subject
583
+ (0 stale), every direct provider-path fixture (0 tool-less), and named four stale declarations
584
+ fixed before the rerun: `docs/acp-backend-rail.md` support table, `pnpm-workspace.yaml`
585
+ release-age exclusions for pi-client/pi-protocol, `check-pack-install` diagnostic wording, and
586
+ the filename-floor sentence in `docs/mux-launch-rail.md`.
587
+ The qualification body on `ca04c1b` then reported 737/739 with two WRONG-REASON verdicts —
588
+ mutants dying at a neighbouring claim: `check-herdr-fresh-call` asserted the umbrella
589
+ whole-argv cell before the callback-pair cell, and the `pack-install` COVERS-CLOSURE `replace`
590
+ still carried the 0.85.1 pin. Both fixed in `3a9ae38`; a lane-scoped replay through the real
591
+ `qualifyMutants` runner (herdr-fresh-call + pack-install, 40/40 killed, 504s) is the fix
592
+ receipt, and the exact-SHA full body is read from `release-gate --cut`. Bridge source changes
593
+ need `pnpm run build-bridge` before any gate: `check-bridge-delivery` compares the gitignored
594
+ `dist` against source and reports staleness as red, which it did once in this lane.
595
+ The standalone LIVE pass that followed (each smoke once, ambient identity stripped) found the
596
+ one surface the verb move had not reached: `smoke-herdr-fresh-call-live` still joined both of
597
+ its cells on `entwurf_v2` as the callback — the pi cell on an argument predicate the
598
+ zero-argument verb can never satisfy, the claude cell on `indexOf("entwurf_v2")`, which landed
599
+ on the result delivery and reported the real callback as forbidden work before the callback.
600
+ The same red run is the receipt that production was right: the claude child's MCP activity
601
+ log reads `entwurf_peers → entwurf_callback (completed) → entwurf_v2 (completed)`, the pi
602
+ child's own tool result says the callback was delivered, and every delivery/fence cell passed
603
+ (2 FAILED / 30 ok, preserved fixture `/tmp/entwurf-opus2/herdr-live-fixture-red`). The oracle
604
+ now joins on the bare `entwurf_callback` the observing surfaces spell and on the ABSENCE of an
605
+ address in the pi call (`149bef7`); no deterministic gate covers this on-demand axis, which is
606
+ why it surfaced only here.
607
+ The same pass found the host behind the tree: `smoke-omp-fresh-live` timed out waiting for the
608
+ callback because the INSTALLED omp birth unit still spoke bootstrap v1 (no
609
+ `entwurf_bridge_entwurf_callback`, `{v:1}`), so the v2 payload the checkout launcher sent was
610
+ refused as `version-unsupported` — the exact condition `doctor-omp-bridge` exists to name, and
611
+ it did (runtime FAIL / ownership PASS). `install-omp-bridge` with no omp session open, doctor
612
+ green on both axes, re-run 21/21. The herdr re-run on `149bef7` is 31/32: the two repaired
613
+ cells pass; the remaining red is a claude-code child that called back and then never attempted
614
+ the delivery verb, recorded in ROADMAP as a follow-up rather than patched on n=2.
615
+ - **The plugin runtime lock names `herdr-checkout` again, for the length of the 0.24.0
616
+ candidate window** — the same two steps 0.23.0 (`dd84ac0`) and 0.23.1 (`3c3a67d`/`194e800`)
617
+ took: an `npm` lock must name a version that is already published and coherent with this
618
+ checkout's `package.json` (`certifyLockCoherence`, `check-herdr-runtime-bootstrap` 8a), and the
619
+ version bump lands before the publish. Lock and `check-herdr-plugin-build` moved together
620
+ (`47f6d36`, the measured inverse of `194e800`); the npm pin returns with its published sha512
621
+ once 0.24.0 is on the registry.
622
+
623
+ - **`plugins/herdr` 0.3.1 leads with the workbench a visitor is looking at.** Its manifest and
624
+ README now say that Entwurf brings sibling AI sessions into Herdr, then name its actual narrow
625
+ integration scope: the install-time build activates only Herdr-integrated pi and Claude Code,
626
+ while the pane is read-only. This is an affordance correction, not a new runtime claim.
627
+ - **The plugin runtime lock is pinned to npm `@junghanacs/entwurf@0.23.1`** and its published
628
+ sha512, closing the candidate window 0.23.1 shipped in. The pair moves together, as that entry
629
+ says it must: the lock and `check-herdr-plugin-build`'s committed-lock expectations, with the
630
+ `IDENTITY` fixture's sha512 travelling with its version rather than being left on the previous
631
+ release's bytes. The integrity was read from the registry in this session
632
+ (`npm view @junghanacs/entwurf@0.23.1 dist.integrity`) rather than copied from another
633
+ session's report, which is the whole point of a pin.
634
+
7
635
  ## 0.23.1 - 2026-09-19
8
636
 
9
637
  ### Changed