@junghanacs/entwurf 0.24.0 → 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 (64) hide show
  1. package/CHANGELOG.md +455 -0
  2. package/DELIVERY.md +24 -1
  3. package/README.md +2 -2
  4. package/VERIFY.md +19 -1
  5. package/demo/README.md +5 -2
  6. package/docs/acp-backend-rail.md +49 -2
  7. package/docs/setup-clean-host.md +4 -4
  8. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +5 -4
  9. package/mcp/entwurf-bridge/dist/pi-extensions/lib/control-send-receipt.js +101 -0
  10. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +6 -2
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +17 -1
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-runner.js +4 -0
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-send.js +53 -6
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +38 -3
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +13 -3
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-placement.js +11 -0
  17. package/mcp/entwurf-bridge/dist/protocol.js +0 -7
  18. package/mcp/entwurf-bridge/dist/scripts/doctor-pi-provider.js +40 -3
  19. package/mcp/entwurf-bridge/dist/scripts/herdr-rails.mjs +72 -0
  20. package/mcp/entwurf-bridge/src/index.ts +5 -4
  21. package/package.json +10 -9
  22. package/pi-extensions/entwurf-control.ts +27 -154
  23. package/pi-extensions/lib/compaction-send-guard.ts +5 -3
  24. package/pi-extensions/lib/control-send-receipt.ts +108 -0
  25. package/pi-extensions/lib/entwurf-v2-contract-schema.ts +1 -1
  26. package/pi-extensions/lib/entwurf-v2-contract.ts +6 -2
  27. package/pi-extensions/lib/entwurf-v2-production.ts +19 -1
  28. package/pi-extensions/lib/entwurf-v2-runner.ts +23 -1
  29. package/pi-extensions/lib/entwurf-v2-send.ts +82 -6
  30. package/pi-extensions/lib/entwurf-v2-surface.ts +42 -3
  31. package/pi-extensions/lib/herdr-fresh-call.ts +13 -3
  32. package/pi-extensions/lib/herdr-placement.ts +11 -0
  33. package/protocol.js +0 -8
  34. package/run.sh +197 -29
  35. package/scripts/check-entwurf-v2-surface.ts +289 -10
  36. package/scripts/check-gate-qualification.ts +7 -4
  37. package/scripts/check-herdr-fresh-call.ts +10 -2
  38. package/scripts/check-herdr-placement.ts +51 -0
  39. package/scripts/check-herdr-plugin-build.ts +11 -4
  40. package/scripts/check-herdr-plugin.ts +745 -38
  41. package/scripts/check-mux-launcher-fence.ts +37 -0
  42. package/scripts/check-pi-launch.ts +7 -4
  43. package/scripts/check-release-gate-outcomes.ts +165 -0
  44. package/scripts/doctor-pi-provider.ts +44 -4
  45. package/scripts/herdr-rails.mjs +75 -0
  46. package/scripts/meta-bridge-state.py +18 -1
  47. package/scripts/mutants/bridge-command-boot.json +38 -0
  48. package/scripts/mutants/control-send-receipt.json +235 -0
  49. package/scripts/mutants/herdr-fresh-call.json +4 -4
  50. package/scripts/mutants/herdr-plugin-build.json +7 -11
  51. package/scripts/mutants/herdr-plugin.json +177 -9
  52. package/scripts/mutants/meta-retire.json +22 -0
  53. package/scripts/mutants/pack-install.json +4 -4
  54. package/scripts/mutants/pi-floor.json +17 -0
  55. package/scripts/mutants/release-gate.json +48 -0
  56. package/scripts/mutants/retired-sent-surface.json +32 -0
  57. package/scripts/mutants/v2-surface.json +7 -7
  58. package/scripts/smoke-codex-fresh-live.ts +94 -13
  59. package/scripts/smoke-herdr-fresh-call-live.ts +15 -8
  60. package/scripts/smoke-herdr-raw-install-live.sh +59 -2
  61. package/scripts/smoke-meta-install-state.sh +107 -1
  62. package/scripts/smoke-mux-lifecycle-live.ts +9 -2
  63. package/scripts/smoke-pi-provider-state.sh +106 -0
  64. package/scripts/smoke-setup-verdict.sh +18 -2
package/CHANGELOG.md CHANGED
@@ -4,6 +4,461 @@ 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
+
7
462
  ## 0.24.0 - 2026-09-20
8
463
 
9
464
  ### Added
package/DELIVERY.md CHANGED
@@ -27,7 +27,7 @@ API. The levels compare capabilities, not implementation shapes.
27
27
 
28
28
  | State | Meaning |
29
29
  |---|---|
30
- | `queued` | A message is durable; the backend may not have seen it. |
30
+ | `queued` | A receiver queue accepted the message; the backend may not have seen it. Durability is a property of the RAIL, not of this word — see the acceptance table below. |
31
31
  | `triggered` | A supported event/API accepted the signal. |
32
32
  | `woke` | An idle interactive session began a turn without user typing. |
33
33
  | `injected` | The message reached model-visible context. |
@@ -36,6 +36,29 @@ API. The levels compare capabilities, not implementation shapes.
36
36
 
37
37
  Avoid bare `delivered`; name the observed boundary.
38
38
 
39
+ ### Control-socket acceptance boundaries (#120)
40
+
41
+ A control-socket send answers with ONE of these, and the sender renders that word rather than the
42
+ internal route it took. They are not four spellings of success: they differ on every axis an
43
+ operator has to reason about.
44
+
45
+ | Boundary | Order | Durability | Visibility | Rejection |
46
+ |---|---|---|---|---|
47
+ | `sent` | n/a — the receiver was idle and the turn was TRIGGERED directly | none needed; the turn started | the turn is running, but the model has NOT necessarily read the text yet, and nothing here says the turn completed | an in-band refusal is `rejected`, never this |
48
+ | `queued-steer` | drained after each turn; overtakes every `queued-follow-up` already waiting | **none** — volatile receiver process memory | nothing shows it: it is not in the TUI's pending-message count | an abort clears the queue and the message is gone, silently |
49
+ | `queued-follow-up` | drained only when the receiver's inner loop ends; a steer arriving later still goes first | **none** — volatile receiver process memory | same: invisible in the receiver's own queue display | same: an abort drops it |
50
+ | `accepted-unknown-boundary` | unknown | unknown | unknown | the receiver ACCEPTED; this names our inability to classify its answer (version skew), and it must never be retried or rendered as `sent` |
51
+
52
+ `[측정 2026-09-22, pi-agent-core 0.87.0; 재측정 2026-09-23, 0.87.1 — 좌표 동일]` `dist/agent.js:60-71,96-97,137-138`
53
+ (two independent `one-at-a-time` queues, no FIFO between them)
54
+ and `dist/agent-loop.js:85,186,191-197`
55
+ (steering drained every turn, follow-ups only once the inner loop ends).
56
+
57
+ The **meta-mailbox** rail is the contrast, not a fifth boundary: it writes a durable `*.msg` before
58
+ ringing any doorbell, its filenames are ISO stamps so a drain is a global FIFO, an undrained inbox
59
+ is an honest `mailbox-undeliverable` refusal, and its receipt is that FILE. A mailbox enqueue is a
60
+ durable file receipt; a control-socket queue is process memory.
61
+
39
62
  ## Levels (D0–D8)
40
63
 
41
64
  These are independent of VERIFY's `L0–L5` evidence levels and BASELINE's question
package/README.md CHANGED
@@ -254,7 +254,7 @@ cd ~/repos/gh/entwurf
254
254
 
255
255
  The full source setup requires Node 24, pnpm, and Python 3 on PATH. Harnesses are
256
256
  optional-by-presence: `setup` runs the frozen dependency install, then composes what the operator
257
- already installed — a compatible `pi` (`>=0.86.0 <0.87`), Claude Code, agy, and the Copilot CLI
257
+ already installed — a compatible `pi` (`>=0.87.1 <0.88`), Claude Code, agy, and the Copilot CLI
258
258
  each get their wiring completed when detected, an absent harness is an explicit zero-state SKIP,
259
259
  and a detected harness that cannot be completed (including a below-floor `pi`) is a named FAIL
260
260
  that makes setup exit nonzero. `setup` never installs a harness binary or touches a credential
@@ -288,7 +288,7 @@ This rail is CERTIFIED on Linux; on macOS it is NOT CERTIFIED — pending
288
288
  physical host (control sockets, mux, ACP plugin hosting).
289
289
 
290
290
  To use the `entwurf` provider inside pi, install a compatible pi binary
291
- separately (`@earendil-works/pi-coding-agent >=0.86.0 <0.87`). Then point pi at
291
+ separately (`@earendil-works/pi-coding-agent >=0.87.1 <0.88`). Then point pi at
292
292
  the npm-installed package or development clone:
293
293
 
294
294
  ```bash
package/VERIFY.md CHANGED
@@ -132,6 +132,24 @@ that moved out of `scripts/` and into a file beside its subject would otherwise
132
132
  > `LIVE=1 ENTWURF_CODEX_APP_SERVER_PID=<existing-app-server-pid>
133
133
  > ENTWURF_CODEX_FRESH_MODEL=<codex-model>
134
134
  > ENTWURF_CODEX_FRESH_PI_MODEL=<pi-model> ./run.sh smoke-codex-fresh-live`.
135
+ >
136
+ > Those three values are the step's operator-supplied prerequisite, and
137
+ > **`release-gate --cut` inherits that triple** — it is exported onto the aggregate invocation (the
138
+ > release skill's P5 carries it), never inferred by the gate, so the no-inference rule above holds
139
+ > unchanged inside the aggregate. **An ABSENT value is a protocol SKIP naming what was missing; a PRESENT but wrong one
140
+ > stays a FAIL.** That split is the honest reading of the step-outcome protocol — a step that never
141
+ > ran and a step that ran and broke are different facts — and it is what makes this MUST reproducible
142
+ > at every cut instead of inviting a standing exception: before 2026-09-23 all three were assertions, so their
143
+ > absence threw, and because the aggregate supplied none of them this step went red inside every
144
+ > cut on every host (0.25.0's candidate receipt, `MUST PASS=23 FAIL=1`, is that shape). A SKIP is
145
+ > **not** a softening: `--cut` refuses it as `cut: BLOCKED (MUST SKIP)`, so a cut without this
146
+ > receipt is still impossible — the operator supplies the triple and re-runs. A host with no Codex
147
+ > app-server therefore cannot take a cut, which is the intended cost of #87's release stop
148
+ > (「A visible-fresh receipt outside the MUST tier is no longer the general rule」above) rather than
149
+ > an accident. The installed-config preflights are deliberately NOT in that split and remain FAIL
150
+ > on absence — a missing Codex birth, user MCP/status/terminal-title config, runtime, bridge, tmux
151
+ > seat, or an unanswered launch directory is host state `setup` and the Codex doctors own, not a
152
+ > per-invocation input.
135
153
  > **Both of THIS cell's live-spend defaults are `gpt-5.6-luna` from 2026-09-17**
136
154
  > (`openai-codex/gpt-5.6-luna` on the pi side) — the Codex leg came down off `sol`. It is a tier
137
155
  > choice and not a gate change; the receipt this cell is accepted on must be the one that actually
@@ -269,7 +287,7 @@ The goal is not merely "invoke Claude Code." We want:
269
287
  **One install command to remember: `./run.sh setup <project>`.** It is idempotent — re-run the exact same command whenever anything looks wrong. There is no second install surface to juggle: from a clone `setup` runs the whole floor in order.
270
288
 
271
289
  1. `pnpm install` — installs the pinned development dependencies and builds the bridge (source-checkout bootstrap only; an installed package never runs npm/pnpm inside `node_modules`)
272
- 2. pi wiring → `<project>/.pi/settings.json` + user-scope registration — only when a `pi` inside the supported range (`>=0.86.0 <0.87`) is on PATH; absent pi is an explicit zero-state SKIP, a below-floor pi is a detected FAIL. The user-scope entry is owner-recorded (#86 C2): another root's live-or-missing ownership makes this step a zero-write refusal (setup: pi FAIL) that names `takeover-user-scope`; the install-states bind the exact managed settings path (a drifted/symlinked/corrupt target is a zero-write refusal) and the inverse removes only the recorded owner's exact entry; `doctor-pi-package` reports the verdict
290
+ 2. pi wiring → `<project>/.pi/settings.json` + user-scope registration — only when a `pi` inside the supported range (`>=0.87.1 <0.88`) is on PATH; absent pi is an explicit zero-state SKIP, a below-floor pi is a detected FAIL. The user-scope entry is owner-recorded (#86 C2): another root's live-or-missing ownership makes this step a zero-write refusal (setup: pi FAIL) that names `takeover-user-scope`; the install-states bind the exact managed settings path (a drifted/symlinked/corrupt target is a zero-write refusal) and the inverse removes only the recorded owner's exact entry; `doctor-pi-package` reports the verdict
273
291
  3. Claude meta-bridge global plugin — only when `claude` is on PATH; otherwise skipped cleanly
274
292
  4. source stable-bin exposure — including certified `entwurf` → this checkout's `run.sh`, the managed runtime Copilot fresh resolves; helper units are attempted independently and a foreign helper is a named FAIL
275
293
  5. agy bridge + exact permission + statusline + `PreInvocation` hook — only when `agy` is on PATH; each adapter is idempotent and independently doctorable
package/demo/README.md CHANGED
@@ -46,7 +46,10 @@ a background driver types the scene prompts into the sender pane.
46
46
  1. **Spawn** a sonnet sibling (memory write — "tempered indigo").
47
47
  2. **Resume** that same sibling (memory recall — identity preservation,
48
48
  the #9 regression guard).
49
- 3. **Cross-session greeting** via `entwurf_send` (sent box ↔ received box).
49
+ 3. **Cross-session greeting** via `entwurf_send`. *(Historical: `entwurf_send` was retired in the
50
+ 0.12 cutover, and the "sent box" half of that pairing was never actually reachable — the
51
+ sender-side renderer had zero producers and was removed in #120 P3. What the recording shows on
52
+ the receive side is real; the send side is a box the operator could not have seen.)*
50
53
 
51
54
  Run:
52
55
 
@@ -139,7 +142,7 @@ SCENE_DELAY=30 FINAL_PAUSE=10 bash demo.sh
139
142
 
140
143
  ## Prerequisites
141
144
 
142
- - `pi` on PATH (current floor 0.86.0)
145
+ - `pi` on PATH (current floor 0.87.1)
143
146
  - `entwurf` provider configured + auth ready for the selected sender/peer models
144
147
  - `asciinema` installed
145
148
  - `agg` installed (optional — only for GIF conversion)