@junghanacs/entwurf 0.22.0 → 0.23.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 (98) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +380 -0
  3. package/DELIVERY.md +10 -2
  4. package/README.md +43 -4
  5. package/VERIFY.md +6 -0
  6. package/docs/setup-clean-host.md +11 -1
  7. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +39 -40
  8. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-declaration.js +609 -0
  9. package/mcp/entwurf-bridge/dist/pi-extensions/lib/codex-fresh-preflight.js +95 -56
  10. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-control-rpc.js +40 -0
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +6 -2
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-facts.js +7 -1
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peer-observe.js +67 -2
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +9 -1
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +24 -0
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +423 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +98 -0
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +1018 -0
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-placement.js +213 -0
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +46 -273
  21. package/mcp/entwurf-bridge/dist/scripts/peer-facts.js +109 -0
  22. package/mcp/entwurf-bridge/src/index.ts +41 -41
  23. package/mcp/entwurf-bridge/tsconfig.build.json +1 -0
  24. package/package.json +4 -4
  25. package/pi-extensions/entwurf-control.ts +91 -52
  26. package/pi-extensions/lib/codex-declaration.js +612 -0
  27. package/pi-extensions/lib/codex-fresh-preflight.ts +107 -51
  28. package/pi-extensions/lib/entwurf-control-rpc.ts +46 -0
  29. package/pi-extensions/lib/entwurf-fact-provider.ts +13 -2
  30. package/pi-extensions/lib/entwurf-facts.ts +14 -1
  31. package/pi-extensions/lib/entwurf-peer-observe.ts +72 -2
  32. package/pi-extensions/lib/entwurf-peers-render.ts +9 -1
  33. package/pi-extensions/lib/entwurf-self-address.ts +33 -0
  34. package/pi-extensions/lib/fresh-call-composition.ts +493 -0
  35. package/pi-extensions/lib/fresh-call-dispatch.ts +142 -0
  36. package/pi-extensions/lib/herdr-fresh-call.ts +1378 -0
  37. package/pi-extensions/lib/herdr-placement.ts +276 -0
  38. package/pi-extensions/lib/mux-fresh-call.ts +95 -301
  39. package/run.sh +396 -8
  40. package/scripts/check-codex-birth-hook.ts +187 -0
  41. package/scripts/check-entwurf-control-rpc.ts +138 -0
  42. package/scripts/check-entwurf-fact-provider.ts +4 -2
  43. package/scripts/check-entwurf-facts.ts +12 -6
  44. package/scripts/check-entwurf-peers-surface.ts +3 -1
  45. package/scripts/check-entwurf-self-address.ts +68 -0
  46. package/scripts/check-fresh-call-dispatch.ts +301 -0
  47. package/scripts/check-gate-qualification.ts +16 -4
  48. package/scripts/check-herdr-activation.ts +1293 -0
  49. package/scripts/check-herdr-fresh-call.ts +1250 -0
  50. package/scripts/check-herdr-placement.ts +383 -0
  51. package/scripts/check-herdr-plugin-build.ts +842 -0
  52. package/scripts/check-herdr-plugin-profile.ts +371 -0
  53. package/scripts/check-herdr-plugin.ts +523 -0
  54. package/scripts/check-herdr-runtime-bootstrap.ts +1264 -0
  55. package/scripts/check-herdr-sandbox.ts +456 -0
  56. package/scripts/check-herdr-supply.ts +184 -0
  57. package/scripts/check-mux-launcher-fence.ts +138 -0
  58. package/scripts/check-peer-facts.ts +331 -0
  59. package/scripts/check-release-gate-outcomes.ts +12 -0
  60. package/scripts/check-typing-call-fence.ts +296 -0
  61. package/scripts/codex-birth-doctor.sh +159 -16
  62. package/scripts/codex-birth-install.sh +178 -72
  63. package/scripts/codex-birth-uninstall.sh +162 -7
  64. package/scripts/fixtures/herdr-supply.json +29 -0
  65. package/scripts/herdr-activation.mjs +536 -0
  66. package/scripts/herdr-plugin-activate.mjs +270 -0
  67. package/scripts/herdr-plugin-deactivate.mjs +193 -0
  68. package/scripts/herdr-runtime.mjs +1203 -0
  69. package/scripts/install-herdr-ci.sh +96 -0
  70. package/scripts/lib/codex-fresh-live-protocol.ts +2 -2
  71. package/scripts/lib/tree-digest.ts +97 -0
  72. package/scripts/meta-bridge-install.sh +19 -2
  73. package/scripts/meta-bridge-state.py +167 -11
  74. package/scripts/mutants/codex-caller-seat.json +6 -8
  75. package/scripts/mutants/codex-native.json +195 -29
  76. package/scripts/mutants/control-socket-disconnect.json +57 -0
  77. package/scripts/mutants/fresh-call-dispatch.json +154 -0
  78. package/scripts/mutants/herdr-activation.json +305 -0
  79. package/scripts/mutants/herdr-fresh-call.json +448 -0
  80. package/scripts/mutants/herdr-placement.json +161 -0
  81. package/scripts/mutants/herdr-plugin-build.json +160 -0
  82. package/scripts/mutants/herdr-plugin-profile.json +202 -0
  83. package/scripts/mutants/herdr-plugin.json +133 -0
  84. package/scripts/mutants/herdr-runtime-bootstrap.json +411 -0
  85. package/scripts/mutants/herdr-supply.json +133 -0
  86. package/scripts/mutants/meta-hook-session-switch.json +2 -2
  87. package/scripts/mutants/mux-fresh-call.json +104 -17
  88. package/scripts/mutants/mux-launcher-fence.json +13 -0
  89. package/scripts/mutants/omp-fresh.json +4 -4
  90. package/scripts/mutants/peer-facts.json +98 -0
  91. package/scripts/mutants/self-address.json +33 -0
  92. package/scripts/mutants/typing-call-fence.json +17 -0
  93. package/scripts/peer-facts.ts +120 -0
  94. package/scripts/register-pi-package.py +8 -0
  95. package/scripts/register-pi-provider.py +88 -12
  96. package/scripts/smoke-codex-birth.sh +281 -12
  97. package/scripts/smoke-herdr-fresh-call-live.ts +1027 -0
  98. package/scripts/smoke-herdr-plugin-build-live.ts +543 -0
package/AGENTS.md CHANGED
@@ -115,6 +115,7 @@ implement → affected focused gates → independent review → one amendment bu
115
115
  - [ROADMAP.md](./ROADMAP.md) — direction and deferred work.
116
116
  - [docs/adding-a-harness.md](./docs/adding-a-harness.md) — entry route for a new harness.
117
117
  - [docs/mux-launch-rail.md](./docs/mux-launch-rail.md) — mux ownership and launch contracts.
118
+ - [docs/herdr-launch-rail.md](./docs/herdr-launch-rail.md) — herdr launch rail (#116): two-step placement, the one-line birth argv, and within-generation reclaim. `entwurf_fresh_call` selects it from process context (`HERDR_ENV=1`, pilot `pi | claude-code`), never from a caller parameter and never as a fallback either direction.
118
119
  - [docs/acp-backend-rail.md](./docs/acp-backend-rail.md) — ACP adapter contract.
119
120
  - [DELIVERY.md](./DELIVERY.md), [VERIFY.md](./VERIFY.md), [BASELINE.md](./BASELINE.md) — delivery coordinates, verification protocol, and evidence.
120
121
  - [README.md](./README.md) — operator-facing package contract.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,386 @@ All notable changes to this project will be documented here. Format follows [Kee
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.23.0 - 2026-09-18
8
+
9
+ ### Added
10
+
11
+ - **`entwurf_fresh_call` opens a sibling on the herdr rail, beside the tmux one (#116).** A session
12
+ running inside [herdr](https://github.com/steipete/herdr) has no tmux server to place a window
13
+ on, so the rail is chosen from PROCESS CONTEXT — `HERDR_ENV=1`, herdr's own marker, read for its
14
+ exact value — and never from a caller parameter, which would let someone request tmux placement
15
+ from inside herdr and fail after a mutation instead of before one. There is **no fallback either
16
+ direction**: an incomplete herdr context is refused by its own herdr reason rather than quietly
17
+ opening a window the operator cannot see from where they are standing. The pilot backends are
18
+ `pi` and `claude-code`; `placement` is refused BY NAME there, because placement inside herdr
19
+ belongs to herdr and a tmux session name would silently relocate the sibling.
20
+ Coordinates, refusals, reclaim and evidence grades: [docs/herdr-launch-rail.md](./docs/herdr-launch-rail.md).
21
+ - One composition root (`fresh-call-dispatch.ts`) now owns rail choice, the Codex preflights and
22
+ the render call for BOTH public surfaces, so a defect planted in one surface can no longer
23
+ survive on the other. The rail-neutral half of the composition (argv dialects, callback tool
24
+ names, first-turn framing, nonce, input contract) moved to `fresh-call-composition.ts`, which
25
+ knows nothing about tmux; each rail supplies only what it alone can state.
26
+ - A sibling is placed in a NEW TAB of the caller's own herdr workspace, without taking focus,
27
+ with the workspace read from herdr's answer about the caller's own pane — never the focused
28
+ workspace and never the `w<N>:` prefix of an id this rail treats as opaque. Orphan reclaim is
29
+ proven within a generation and closes the PANE rather than the tab (`[측정 2026-09-15]`).
30
+ - `entwurf_peers` carries a read-only `placement` column with four words
31
+ (`unobserved` / `none` / `ambiguous` / `herdr <pane>`), joined only on an official herdr report
32
+ for its own backend. A pane coordinate is a VIEW, never an address: the garden id still arrives
33
+ in the callback envelope (Hard Rule 16).
34
+ - **A herdr plugin that installs entwurf onto a host where nothing entwurf is on `PATH`.**
35
+ `plugins/herdr/` bootstraps a stable, scoped plugin runtime from an exact checkout candidate,
36
+ classifies which integrations the host already has, activates only what it owns, and ships a
37
+ read-only citizen status pane built on the core's own `peer-facts` projection. The install
38
+ reports NAMED steps on the operator's terminal and ends with one usage example per activated
39
+ backend, because `[GLG 직접, 2026-09-17]` "정확한 모델명을 모른다" was a real blocker on a clean
40
+ install. Entwurf still installs only Entwurf bytes — never a harness, subscription or login
41
+ (Hard Rule 17) — and a clean NixOS host additionally needs `programs.nix-ld.enable = true` for
42
+ the ACP rail's dynamically linked vendor binary.
43
+
44
+ - **A gate keeps the typing-call boundary by NAME, not by prose alone.** `[외부 관측 2026-09-17
45
+ 19:35]` an outside reader measured this tree and found the law perfectly kept and nothing keeping
46
+ it: zero typing calls across `pi-extensions/`, and zero checks and zero mutants that would notice
47
+ one arriving. Gates here grow on top of incidents, and this boundary was named and REFUSED at
48
+ design time (`docs/herdr-launch-rail.md` §3 threw the keystroke verb away before it shipped), so
49
+ it never produced the failure that would have produced a test — and a rule kept since day one is
50
+ the most defenceless kind, because its green means only that nobody has broken it yet.
51
+ `check-typing-call-fence` scans every TRACKED AND UNTRACKED production source under
52
+ `pi-extensions/`, `mcp/`, `plugins/herdr/` and `scripts/herdr-*.mjs` for the names of typing calls
53
+ — tmux `send-keys` / `paste-buffer` / `load-buffer` and herdr's PTY verbs `agent.prompt` /
54
+ `agent.send_keys` / `pane.send_text` / `pane.input.set` — with comments blanked and string
55
+ literals KEPT, because a typing call spelled as a string is the call.
56
+ - The comment exemption is load-bearing rather than a convenience: the sentence that states the
57
+ rule contains the very names the rule forbids, so a scanner that read prose would be red on the
58
+ law itself and the cheapest way to green would be deleting the law.
59
+ - Three assertions stand in front of the claim for the same reason — the detector is proven to SEE
60
+ each name planted in code, proven BLIND to the same name in a comment, and the scanned inventory
61
+ is measured, because without those "zero hits" and "looked at nothing" print identically.
62
+ - **What it does not catch is named in its own header rather than implied:** it reads SPELLINGS,
63
+ so a verb assembled at runtime passes it. That limit is accepted rather than closed. This is a
64
+ tripwire for the slip — a module reaching for the vendor's typing verb because the argv route
65
+ looked harder that afternoon — not a defence against somebody hiding one, and a gate claiming to
66
+ be the latter would be the more dangerous object. The claim therefore says CARRIES a typing call
67
+ spelled out, which is what it measures.
68
+ - `scripts/` stays out as a whole: this gate lives there, and a gate that scanned itself could
69
+ only be made green by deleting the names it exists to forbid. Wired into `check:hermetic`;
70
+ 8 checks over 85 production sources, one exact-once mutant.
71
+ - **The framing OFFERS its corroboration in a spelling the sibling can actually use.** The callback
72
+ tool name goes through a per-backend dialect map; the peer listing the first turn offers did not,
73
+ and four of the five backends reach that tool under a composed MCP name. The offer was unusable by
74
+ exactly the siblings it was written for — and it was written to replace a prohibition, removed
75
+ because a sibling should be able to check who called it. `FRESH_CALL_PEERS_TOOL` is the same
76
+ dialects applied to the other tool, so the two maps drift together or not at all.
77
+
78
+ ### Fixed
79
+
80
+ - **`smoke-codex-fresh-live` now directs Codex to the callback sender garden id by the envelope's `session:` line.** In the first 0.23.0 retry Luna copied the model label from `from:` instead; the red receipt is `/tmp/entwurf-release-gate-0.23.0-retry.17pfMp`.
81
+
82
+ - **A Claude child that could not see its callback tool now knows how to load it.** The framing said
83
+ "FIRST ACTION … call `entwurf_v2`" to a child for which that tool was not callable yet: `[측정
84
+ 2026-09-18, n=5]` claude-code 2.1.267 surfaces a still-connecting MCP server as DEFERRED — the tool
85
+ NAME is listed, the SCHEMA is not, and a call without the schema fails. Across five isolated probes
86
+ the bridge's tools were directly callable **zero** times (4 DEFERRED, 1 still connecting), so the
87
+ child answered in text and went idle. That is the "silence" the herdr rail recorded in 5 of its 14
88
+ launches. The claude-code dialect now carries one factual sentence naming `ToolSearch` with BOTH
89
+ tools in a single select, and `[측정, n=3]` callbacks went 3/3 — including a child 782 ms in, which
90
+ is slower than every silent run. **The latency story it replaces is retired:** re-read at n=13
91
+ instead of n=4, success and silence overlap completely (52 ms called back, 94 ms did not), so
92
+ `docs/herdr-launch-rail.md` §14 now carries the distribution and the mechanism instead of the
93
+ correlation. Every other backend's framing is byte-identical — pi has no deferred-tool surface and
94
+ the other three were never probed (`FRESHCOMP-TOOL-LOAD-HINT-CLAUDE-ONLY`).
95
+
96
+ - **The first turn no longer looks like the injection it is not (#116).** `[GLG 직접, 날것 PC,
97
+ 2026-09-17]` a Claude Sonnet 5 sibling REFUSED its whole first turn and named three reasons, all
98
+ of them ours: a "decode the following JSON string literal and follow the decoded instructions"
99
+ wrapper, the line "Do not inspect environment variables, do not call entwurf_self", and no way to
100
+ verify the caller. The framing is now plain prose folded onto one physical line, with ONLY the
101
+ operator's task riding as a literal — `[source herdr 7505c08]` `src/app/agents.rs:157-161`
102
+ refuses any agent argument carrying a Unicode Cc, so a newline never reaches the sibling, and
103
+ `src/platform/linux.rs:127-141` single-quotes every argument, which is why the encoding was never
104
+ buying shell safety. The prohibition is replaced by the same steering stated as a FACT (your own
105
+ record does not carry the caller's address; the tool call is what reaches it) plus an OFFERED
106
+ read-only way to corroborate the caller. `[측정 oracle 2026-09-18, LIVE run sCzzE0]` a Sonnet 5
107
+ child called `entwurf_peers` and then `entwurf_v2`, both successful, and its callback landed.
108
+ - **The rail tells herdr how long to wait, and judges the child on its own clock (#116).** Without
109
+ an explicit `--timeout`, herdr gave up at its own 30s default while our process bound sat at
110
+ 300s, so a cold-starting Claude Code sibling that was alive and booting came back as
111
+ `herdr-agent-start-failed [herdr: timeout]` over an unreclaimed pane. The bound is now an
112
+ explicit 240s, deliberately BELOW our own kill, so herdr has to lose that race or its named
113
+ answer is replaced by "no exit status". The LIVE cell also stopped measuring the child with the
114
+ CALLER's clock — two runs of identical code had differed only in how long the caller's launch
115
+ took — and now records what the child was doing every run: herdr's agent status and entwurf's own
116
+ hook journal are the WHOLE evidence surface, because `[측정 2026-09-18]` no claude session in this
117
+ smoke, caller or child, acting or silent, exists under `~/.claude/projects/` or any fenced XDG
118
+ root — including a child that had just made two successful tool calls.
119
+ - **A peer that hangs up on an accepted control socket no longer takes the resident with it.** The
120
+ connection error is handled where the socket is accepted, so a half-open peer is a bounded
121
+ per-connection failure instead of an uncaught exception.
122
+ - **The uncitizened `entwurf_self` notice is proven, not regexed.** The gate asserted the shape of
123
+ a message instead of the fact it reports, which a reworded notice would have broken while a
124
+ wrong notice passed.
125
+
126
+
127
+ - **The Codex birth unit owns its own declaration, not the whole `hooks.json` (#117).** Installing
128
+ Herdr's official Codex integration appends a second `SessionStart` group to
129
+ `$CODEX_HOME/hooks.json`. The vendor kept running BOTH declarations — `[source]`
130
+ `codex-rs/hooks/src/engine/discovery.rs:664-665` hashes a normalized event/matcher/group/handler
131
+ and keys trust by `<path>:<event>:<group_idx>:<handler_idx>`, so trust was declaration-scoped all
132
+ along — but entwurf's certification was file-scoped: `install-state` recorded a whole-file
133
+ sha256 and the fresh preflight additionally required `SessionStart.length === 1`. On a host with
134
+ both installed, `doctor-codex-birth` was RED and every Codex fresh launch was refused
135
+ `codex-birth-unit-missing`. **v0.22.0's release LIVE was measured with Herdr's hooks moved
136
+ aside; this is the debt that names paid.**
137
+ - Ownership is now exactly one `SessionStart` group holding one handler whose `command` is our
138
+ quoted launcher path. It is certified by SHAPE (which keys may exist) and by a NORMALIZED
139
+ digest (recursively sorted keys, no whitespace) — blind to indentation and key order, because
140
+ `[측정 2026-09-17 oracle]` Herdr re-serializes the entire document through serde, which changed
141
+ our own handler's key order from `type,command,timeout` to `command,timeout,type`.
142
+ - The vendor trust receipt is read at the index our declaration was MEASURED at, never at the
143
+ constant `:0:0`. With a neighbour at index 0 that constant read THEIR approval and reported it
144
+ as ours — a green for a hook the vendor had never been asked to run.
145
+ - Neighbours are `present-but-foreign`: reported in the doctor's own FOREIGN section, certified
146
+ by nothing, never overwritten or absorbed, and no input to any verdict of ours. Editing one
147
+ moves nothing.
148
+ - Install into a file somebody else already declares in is an APPEND by text splice (never an
149
+ adoption and never a refusal), and appending rather than inserting leaves a neighbour's index —
150
+ and therefore their existing trust receipt — alone. Uninstall splices out only entwurf's group
151
+ (plus entwurf's own top-level `description`), removing the file only when entwurf was its sole
152
+ declaration. Every splice is re-parsed and deep-compared to the value it intended before it
153
+ reaches disk; a splice that lands anywhere else is a zero-write refusal.
154
+ - Our own declaration missing, edited or DUPLICATED is still a named red, in install, doctor,
155
+ inverse and preflight alike.
156
+ - Entwurf still never creates, computes, pre-seeds or bypasses a vendor trust receipt, and
157
+ deliberately does NOT recompute what that hash should be: the vendor normalizes with its own
158
+ code, and reimplementing it here would make entwurf a second opinion about somebody else's
159
+ security decision that drifts with every vendor release.
160
+ - `install-state` moves to `codex-birth-install-state/v2` (declaration receipt instead of
161
+ `hooksSha256`). The uninstall, doctor and preflight read v2 only and refuse v1 by name; the
162
+ installer reads a v1 receipt once, to supersede it forward — it removes nothing and rewrites no
163
+ foreign byte.
164
+ - New shared leaf `pi-extensions/lib/codex-declaration.js` is the single definition the
165
+ installer, inverse, doctor, fresh preflight and gates all decide with.
166
+ - Gates: `smoke-codex-birth` 102 checks (both orderings green, foreign bytes proven across
167
+ install and inverse, foreign edits neutral, own-declaration drift/duplication/v1 named red,
168
+ trust read at the measured index, the shared file's mode carried over, and an unscanned
169
+ FOREIGN axis reporting NOT READ rather than "none"), `check-codex-birth-hook` 101 checks (the
170
+ leaf's digest, selection, named refusals, span reader and splice post-condition), six new
171
+ preflight cells, and 8 exact-once mutants (inventory 520 → 527). Six existing
172
+ `FRESHCALL-CODEX-*` mutants were re-pointed at the predicates this narrowing moved;
173
+ `FRESHCALL-CODEX-HOOK-KEYS` moved gate as well, because under the preflight's oracle an extra
174
+ handler key is caught by the digest anyway — what its predicate still buys is the NAME.
175
+ Verification receipts at `ac3f1f8`: `pnpm run check:full` exit 0 (403s) and
176
+ `check-gate-qualification` **527/527 killed**, both in the exact-SHA CI run
177
+ [35239086438](https://github.com/junghan0611/entwurf/actions/runs/35239086438).
178
+ - **Not in scope, by decision:** adding Codex to `HERDR_FRESH_CALL_BACKENDS`, Herdr env carriers
179
+ on the Codex MCP child, plugin supervision of the app-server, and the identical ownership
180
+ defect in the statusline/terminal-title atoms (recorded as an Observation, its own atom).
181
+
182
+ - **A sibling herdr has not been introduced to yet is no longer killed for it.** `[측정 oracle
183
+ 2026-09-18, LIVE run XimC19]` a Claude Code child booted, wrote its own birth record at
184
+ 01:53:38.297Z, and 0.585s later this rail closed the pane it was sitting in. The same bytes passed
185
+ five minutes later (run TCatjF) because that child took 1m27s to become ready instead of 4.9s — so
186
+ the FAST path was the dangerous one, and every green run before it was luck rather than proof.
187
+ - The premise was that `agent start` waits for detection before returning. It does not:
188
+ `[file:line @ c77af189]` `src/cli/agent.rs:592-615` returns on `agent_status ∈ {idle,done}` plus
189
+ `interactive_ready`, and `agent_session` is not in that condition at all. On the Claude rail that
190
+ id arrives afterwards from the CHILD's own one-shot hook
191
+ (`src/integration/assets/claude/herdr-agent-state.sh:60-99`, 0.5s socket deadline, failures
192
+ swallowed), so "herdr has not been told the id" is a reachable resting state for a healthy
193
+ sibling.
194
+ - `settleAgentWitness` re-READS instead of judging on the spot: `agent get <the exact name we
195
+ started>`, never a listing, bounded and rebound to the create receipt every time. An unreadable
196
+ or failed read costs one poll, because herdr answers a missing agent and a socket that blinked
197
+ with the same exit status.
198
+ - **Expiry buys nothing.** The window closing is a SUCCESSFUL launch carrying a
199
+ `witness: unavailable` diagnosis that says what it costs (the peers `placement` column has
200
+ nothing to join) and what it does not (the address was never a pane). The old
201
+ `witness-missing → reclaim` path is gone; the one failure this stage can still name is a
202
+ readable reply about a DIFFERENT agent, `herdr-agent-start-vanished`.
203
+ - The reclaim predicate now reads OCCUPANCY, not the session id: a pane herdr says an agent is in
204
+ is never closed, whether or not anybody has reported that agent's session. Collapsing those two
205
+ facts is what made a booted child read as an empty pane, and it would have survived in every
206
+ other post-create failure branch if only the witness path had been patched.
207
+ - The settle bound is a PROPOSAL and says so in its own comment: the red run still had nothing
208
+ 585ms after the child's birth record, the reporter's own deadline is 500ms, and the slow run's
209
+ 85s was a race being hidden rather than a ceiling.
210
+ - **The Codex birth unit decides who owns the shared `hooks.json` ONCE, for all four surfaces.** The
211
+ fresh-call preflight required a plausible owner and refused a group/world-writable mode; the
212
+ installer and the inverse asked only symlink-and-regular; the doctor read the file's CONTENT
213
+ without ever asking who owned it. A host could install clean, read GREEN, and have every Codex
214
+ launch refuse it as `codex-birth-unit-missing` — three surfaces saying yes about bytes the fourth
215
+ said no about. `classifyOwnedPath` now lives in the shared declaration leaf all four already load,
216
+ and it is PURE: it judges a stat record, not a path. A neighbour's MODE stays theirs (the install
217
+ still carries it over untouched); WRITING into a file anyone else can rewrite is the different
218
+ question, and the answer there is a zero-write refusal, because a receipt cannot bind bytes
219
+ somebody else controls. Symlink keeps its own verdict — the repair is not a mode change, it is a
220
+ different file.
221
+ - **The inverse's FILE verdict earns its delete.** It could remove a `hooks.json` this unit never
222
+ created: the arm asked one question — is our group the last declaration — and that is true of a
223
+ file we merely APPENDED to. `{"description":"foreign","hooks":{"SessionStart":[]}}` has no foreign
224
+ GROUP, so after our install it classified FILE and the whole document went, a neighbour's
225
+ top-level bytes included. The receipt had recorded `hooksExistedBefore` since the installer was
226
+ written and nothing read it; it does now, with two more proofs beside it (the top-level keys are
227
+ exactly the two this unit writes, and the description is ours). Anything else SPLICES. An absent
228
+ field reads as "it was already there", so an older receipt licenses a splice and never a delete.
229
+ - **The herdr LIVE oracle joins the callback it claims to have proved.** Its pi cell compared
230
+ CHARACTER OFFSETS across the whole transcript, and both strings are already in the birth prompt in
231
+ that order — so "called back, then worked" and "never called back" read identically. It now wants
232
+ ONE record carrying the delivered nonce and the `sent` outcome together, with the task token in a
233
+ later one. Its claude cell asserted only that the first `entwurf_v2` completed, which a call
234
+ delivering somebody else's nonce also satisfies; the join it can actually make is across two
235
+ independent artifacts — the delivered message carrying the exact nonce and the child as sender,
236
+ enqueued inside that call's own window.
237
+ - **Three Codex branches and one peers read stop being claims nobody tests.** An independent
238
+ coverage audit found the whole production path behind the peers `placement` column — env gate,
239
+ binary execution, timeout, parse failure — with zero assertions and zero mutants, so a typo in
240
+ `["pane","list"]` or a deleted timeout would have left the deterministic floor green. It has two
241
+ claims now, driven through a stub PROCESS (not a fake herdr: every answer-shaped fact still comes
242
+ from the verbatim 0.9.0 recordings, and what the stub supplies is an exit code, a hang, an
243
+ absence). The doctor's ORPHAN verdict, the inverse's SPLICE arm and its FILE arm gained the
244
+ mutants their behaviour cells never had, and the herdr rail's documented cwd asymmetry — `#` is
245
+ DATA here, because herdr has no format expansion for the tmux refusal to protect against — gained
246
+ a cell that drives a real directory through to the argv.
247
+
248
+ - **The herdr rail's two-step `agent prompt` proposal is closed at the VENDOR SOURCE, not with LIVE
249
+ runs — and not implemented.** §14 proposed starting the agent with no prompt and sending the first
250
+ turn with `herdr agent prompt`, which would remove the tool-list race. The question standing in
251
+ front of "does the child call back" is what that verb SENDS, and the vendor answers it:
252
+ `[source herdr 7505c08]` `api/agents.rs:195` hands the text to `encode_api_submission_parts`,
253
+ `api_helpers.rs:25-32` wraps it in bracketed paste and writes it to the child pane's PTY, and
254
+ `:13` / `:208-212` write an encoded Enter 300 ms later. It runs NO control-character check — only
255
+ a non-empty one, unlike the argv path — so whether a newline pastes or submits is decided by the
256
+ child terminal's paste mode at that instant rather than by a contract, and what is acknowledged is
257
+ that input was written (the vendor's own help: "before any input is sent", "It does not track
258
+ turns"). So the two-step is the keystroke channel §3 already measured and refused, and adopting it
259
+ would put screen input on the FIRST turn — exactly what Hard Rule 16 keeps out of delivery
260
+ evidence. The three LIVE runs budgeted for it were NOT spent characterising a path that cannot
261
+ ship. The remaining legitimate direction for the race is the framing that stands before the tool
262
+ list, the same axis as omp's `--entwurf-bootstrap`.
263
+ - **Two mutant claims the #116 merge moved now aim at what they were about.** Replaying every lane
264
+ the merge touched answered 484/486, and both refusals were the merge's own bookkeeping.
265
+ `FRESHCALL-CWD-SURFACE-PARITY` SURVIVED because the regenerated find was extracted from the FIRST
266
+ `cwd: z` block in the bridge — which belongs to `entwurf_register_native`, not
267
+ `entwurf_fresh_call` — so the mutation deleted a required cwd from a DIFFERENT tool and nothing
268
+ failed; both parity finds are now extracted from inside the `fresh_call` registration span.
269
+ `FRESHCALL-CWD-CALLER-ONLY` came back WRONG-REASON because the mutation now lands in the
270
+ composition leaf while the cell carrying its signature read only the tmux module, so three other
271
+ cells failed instead and the kill could not be attributed.
272
+
273
+ - **The first turn says where the result goes.** `[GLG 직접, 2026-09-18]` a fresh sibling finished
274
+ its task, printed the answer in its own window, and the caller never saw it. The investigation
275
+ found no broken injection: NEITHER framing, the retired one or the current one, had ever said
276
+ this. It used to work because a hand-written briefing named the target and the tool, and when the
277
+ briefing stopped doing that the sentence turned out to live nowhere. The framing now ends with two
278
+ lines — where to send the result (the callback tool, in the same backend dialect) and why sending
279
+ is needed at all, because a sibling with a visible window has no way to know its window is not the
280
+ delivery. It is TOPOLOGY, not a command: nothing watches for completion or sends on the sibling's
281
+ behalf, and a supervisor is what this rail refuses to be.
282
+ - **A LIVE smoke stops taking the operator's `claude` launcher** — #67's failure on a rail born
283
+ after its fence. The vendor resolves the two halves of its install from different roots: the
284
+ version store from `XDG_DATA_HOME`, the launcher from `HOME`. This smoke fenced XDG while keeping
285
+ the real HOME, so each Claude child found an empty store beside a real launcher, installed itself
286
+ into the fixture, and repointed `~/.local/bin/claude` at `<fixture>/claude/versions/2.1.267`.
287
+ **That is not a hazard, it happened** — seven preserved fixture roots each hold that install, and
288
+ the operator's launcher pointed into the newest of them, one `/tmp` sweep away from a `claude`
289
+ that does not start. The fixture's `xdg-data/claude` is now a symlink to the operator's vendor
290
+ data dir, which puts store and launcher back in one install while the rest of the tree stays
291
+ fenced, and the smoke consumes the shared fence: fail-closed preflight, integrity oracle in
292
+ teardown, cleanup verdict. The gate side stopped naming files — `LAUNCHFENCE-EXPOSED-SMOKE-WIRED`
293
+ asks the POPULATION (every LIVE smoke that assigns a fixture `XDG_DATA_HOME` either consumes the
294
+ fence or carries an exemption verified against its own source: four today, three wired, one
295
+ exempt), because the two claims that listed smokes by name are exactly what the newest rail walked
296
+ past. `[LIVE 2026-09-18]` the first run in eight to leave zero version copies behind.
297
+ - **The herdr LIVE ordering oracle reads the join pi actually writes.** The claim asked for ONE
298
+ transcript record carrying both the delivered nonce and the `sent` outcome; pi cannot write that
299
+ record, because the nonce rides the toolCall and the outcome rides a separate toolResult, joined
300
+ by `toolCallId`. So the oracle failed a run whose child did exactly what the claim describes
301
+ (callback 04:15:01.381, `sent` .415, task token .03.728) — the most expensive kind of red: correct
302
+ product, red gate, and a cut that stops for nothing. The join was what the claim was always about:
303
+ the `sent` outcome must belong, by that id, to THE call that carried THIS nonce, and the task
304
+ token must appear after it. Proven on the failing transcript — the real run holds, a forged pair
305
+ whose `sent` belongs to a different call does not, and a run whose token appears only before the
306
+ callback does not.
307
+
308
+ ### Changed
309
+
310
+ - **The two tool descriptions say what the code does.** `entwurf_v2` no longer offers `codex` as an
311
+ example of a backend with no adapter: Codex is a native-push backend, and that sentence pointed a
312
+ caller at the wrong dispatch outcome. `entwurf_fresh_call` says it opens a NEW UNFOCUSED TAB in
313
+ the caller's own herdr workspace rather than "a herdr pane" — the contract has been tab-first
314
+ since it shipped. Both surfaces lost the top-level cwd sentence the `cwd` parameter description
315
+ already owns, which is also what kept them inside the 2048-char host cap.
316
+ - **The herdr rail document describes the first turn that actually ships.** §3 still recorded the
317
+ retired shape — the whole framing wrapped in one JSON literal behind "decode the following" —
318
+ which a Sonnet 5 sibling refused outright. The current shape is plain prose with only the
319
+ operator's task as a literal, and the section now says why: the encoding never bought shell
320
+ safety (herdr single-quotes every argument), and what is left is the vendor's control-character
321
+ refusal, so only the thing that must carry newlines is encoded.
322
+ - **`ROADMAP.md` and `docs/setup-clean-host.md` stop calling accepted work pending.** The Codex
323
+ caller-seat and caller-DIRECTORY re-runs were accepted on 2026-09-16 with 56 and 65 assertions;
324
+ both documents said "pending", and the roadmap's current-package line still read 0.20.1.
325
+ - **`plugins/herdr/README.md` separates an admission floor from a measured supply.** Herdr
326
+ `>=0.9.0` is the manifest floor; Entwurf's reproducible and CI rail is pinned to exactly 0.9.1,
327
+ and a different Herdr inside the admission window carries no receipt of ours. Its install-output
328
+ example uses `<version>` / `<full-commit>` placeholders instead of a frozen `0.21.0` and a frozen
329
+ commit that no longer reproduce. Both READMEs now name the two installation routes and link to
330
+ each other.
331
+
332
+ ### Observations — named rather than carried
333
+
334
+ These were raised by the pre-cut review round and deliberately NOT acted on in this release. Each
335
+ one is recorded here so the next lane inherits the finding instead of rediscovering it.
336
+
337
+ - **The vendor trust receipt is bound by KEY, not by VALUE.** `doctor-codex-birth` and the fresh
338
+ preflight read the receipt at the index our declaration was measured at and check that its value
339
+ has the shape `sha256:<64 hex>` — they do not check that the hash is the one the vendor would
340
+ compute for the CURRENT declaration, so a stale or arbitrary well-shaped value reads as ready.
341
+ Closing it would mean reimplementing the vendor's normalization here, which is exactly the second
342
+ opinion about somebody else's security decision that Hard Rule 9 and the doctor's own prose
343
+ refuse. The live failure mode is bounded and visible: a stale receipt makes the vendor show its
344
+ consent screen again to the human sitting there, and an unattended launch reads as a callback
345
+ timeout.
346
+ - **The typing fence does not detect an assembled typing call** — see its header, and the entry
347
+ above.
348
+ - **The declaration span fixtures do not pin CRLF, BOM or deeply nested arrays by name.** The parser
349
+ and its splice post-condition handled all three correctly in a direct memory-only sample; what is
350
+ missing is a labelled cell, and BOM in particular would read better as an explicit refusal than as
351
+ an incidental one.
352
+ - **Four more coverage gaps were enumerated and left for their own lane:** the uninstall's v1-state
353
+ refusal has a behaviour cell but no mutant of its own, the `trustReceiptKey` composition is
354
+ asserted separately by the doctor and the preflight rather than jointly, and two spawn-seam
355
+ sub-claims were deliberately not split into their own QKs.
356
+ - **The Claude child's `--allowedTools` names only the callback tool**, so the corroboration the
357
+ framing offers is not on the auto-approved list. Measured behaviour is unchanged by this release
358
+ (the bare spelling was not on it either), and widening auto-approval is a decision rather than a
359
+ repair.
360
+ - **`doctor-codex-birth` FOREIGN 축은 이웃 선언의 trust 부재를 판단하지 않지만 vendor 동의 화면은
361
+ 파일 단위로 첫 턴을 막는다.** `smoke-codex-fresh-live` 전제조건은 폴더 trust만 이름짓고 hook
362
+ trust는 timeout으로 읽힌다 — 컷 후 atom.
363
+
364
+ ### Verification
365
+
366
+ - **Land exact-SHA CI.** Push run
367
+ [`35312087142`](https://github.com/junghan0611/entwurf/actions/runs/35312087142) at
368
+ `09aea576a80983709a7e2bb7593d6fb546234eee` concluded green for `check`,
369
+ `install-surface`, `artifact-consumer`, and `macos-install-surface`; the `check` job's
370
+ `check-gate-qualification` body also concluded green.
371
+ - **LIVE Codex acceptance is a focused rerun, not a final `release-gate --cut` exit 0.** First cut
372
+ attempt `/tmp/entwurf-release-gate-0.23.0.Ire9Nr` was red because the vendor's Herdr hook trust
373
+ receipt was absent; the operator answered the vendor consent screen once. Retry
374
+ `/tmp/entwurf-release-gate-0.23.0-retry.17pfMp` recorded `MUST: PASS=23 FAIL=1 SKIP=0`; its only
375
+ red cell was `smoke-codex-fresh-live`, where Luna copied the model label from the callback
376
+ envelope's `from:` line. The frozen-candidate delta afterwards is exactly the two instruction
377
+ sentences in `scripts/lib/codex-fresh-live-protocol.ts` plus these CHANGELOG records. After
378
+ `pnpm run build-bridge` and `./run.sh check-bridge-delivery` (19 PASS), the repaired standalone
379
+ `smoke-codex-fresh-live` passed 66 assertions:
380
+ `/tmp/entwurf-codex-smoke-0.23.0-amend.BHrPTU/smoke-codex-fresh-live.log`.
381
+ - **Deterministic floor and explicit variance.** `pnpm run check:full` passed before the amendment
382
+ at `/tmp/entwurf-check-full-0.23.0.Q6GGeI`; it also passed on the amended working bytes in 579 s
383
+ at `/tmp/entwurf-check-full-0.23.0-amend.zBu9ic/check-full.log`. VERIFY §389 문자 조건 미충족 —
384
+ GLG 명시 승인 하의 편차, 사유: 통과 스텝 재실행은 자원낭비. The prepared-HEAD exact-SHA CI owns
385
+ the final deterministic proof.
386
+
7
387
  ## 0.22.0 - 2026-09-17
8
388
 
9
389
  ### Added
package/DELIVERY.md CHANGED
@@ -202,8 +202,16 @@ for the inherited case.
202
202
 
203
203
  Four ownership atoms remain separate:
204
204
 
205
- 1. `entwurf install-codex-birth` owns `$CODEX_HOME/hooks.json` — one whole file, ours or
206
- absent — plus its launcher and import closure under `$XDG_DATA_HOME/entwurf/codex-birth`.
205
+ 1. `entwurf install-codex-birth` owns **one `SessionStart` declaration inside**
206
+ `$CODEX_HOME/hooks.json`not the file — plus its launcher and import closure under
207
+ `$XDG_DATA_HOME/entwurf/codex-birth`. The vendor keys trust per
208
+ `<path>:<event>:<group>:<handler>`, so a neighbouring integration (Herdr's official Codex
209
+ integration appends its own group) coexists: entwurf certifies the NORMALIZED digest and
210
+ shape of its own group, requires it exactly once, reads the vendor receipt at the index that
211
+ group was measured at, and reports every other group as present-but-foreign — certified by
212
+ nothing, rewritten never. Install appends and uninstall splices by text, so a neighbour's
213
+ bytes survive both unchanged. The ownership receipt is `codex-birth-install-state/v2`; a v1
214
+ receipt (whole-file digest) is refused by name and superseded forward by one reinstall.
207
215
  No root: every path belongs to the operator. The vendor gates a user-layer declaration on
208
216
  ONE interactive "Trust all", which entwurf never answers, pre-seeds or computes; the
209
217
  receipt is read as its own doctor axis, and until it exists setup is honestly non-green.
package/README.md CHANGED
@@ -78,6 +78,19 @@ A few words that look unusual for a coding tool.
78
78
 
79
79
  ## Install
80
80
 
81
+ **Two installation routes, and the one you want depends on where you work.**
82
+
83
+ - **Direct** — `npm install -g @junghanacs/entwurf`, then `entwurf setup <project>`. This is the
84
+ general route: it composes every harness it FINDS on the host — pi, Claude Code, Codex, Copilot,
85
+ Antigravity, the ACP rail — and reports each one PASS / SKIP / FAIL. Use it in an ordinary shell,
86
+ in tmux, anywhere.
87
+ - **Herdr workbench** — `herdr plugin install` from inside a Herdr session. It activates Entwurf for
88
+ the pi and Claude Code sessions Herdr has integrated, and only those. Contract, evidence grades
89
+ and its refusals: [plugins/herdr/README.md](plugins/herdr/README.md).
90
+
91
+ Neither route installs a harness, a subscription, or a login (Hard Rule 17). Everything below this
92
+ paragraph describes the direct route.
93
+
81
94
  **Platform evidence, in one breath.** The Entwurf-only install surface on
82
95
  macOS is CERTIFIED (CI) (`macos-install-surface`). On macOS every
83
96
  garden-native harness rail, marker/request join, ACP turn, and mux is NOT CERTIFIED —
@@ -308,7 +321,7 @@ repair surface: each unit has its own installer, doctor, and inverse.
308
321
  - **Antigravity / agy** (Linux CERTIFIED; macOS NOT CERTIFIED — pending physical host) — `install-agy-bridge`, `install-agy-statusline`, `install-agy-hooks`, each with a matching `doctor-agy-*`.
309
322
  - **GitHub Copilot CLI** (Linux CERTIFIED; macOS NOT CERTIFIED — pending physical host) — four independent units, four independent failure modes: `install-copilot-bridge` (birth: garden id + who-sent, on the first prompt), `install-copilot-mcp` (the entwurf tool hand, where `entwurf_inbox_read` lives), `install-copilot-receive` (the receiver extension: doorbell + receiver marker), `install-copilot-statusline` (optional for a manual citizen, required for supported fresh) — each with a matching `doctor-copilot-*` and `uninstall-copilot-*`.
310
323
  - **OMP (`omp`)** (Linux CERTIFIED; macOS NOT CERTIFIED — pending physical host) — four units, in-process extensions rather than launchers: `install-omp-bridge` (birth: the `mode === "tui"` visible host, its garden id on the status line, and who-sent), `install-omp-mcp` (the omp-native `entwurf-bridge` entry), `install-omp-config` (the one operator setting `tools: xdev: false`, without which the vendor mounts MCP tools as `xd://` devices the model cannot call), `install-omp-receive` (the receiver extension: mailbox watch + announce-only doorbell) — each with a matching `uninstall-omp-*`, and a `doctor-omp-*` for all but the setting, whose runtime axis `doctor-omp-mcp` owns. The setting writer owns exactly the lines it adds and refuses an explicit operator `tools: xdev: true` by name rather than overwriting it.
311
- - **OpenAI Codex CLI** (supported in 0.21.0 on Linux; macOS NOT CERTIFIED — pending physical host) — `install-codex-birth` publishes the `SessionStart` declaration into `$CODEX_HOME/hooks.json` with its launcher closure under `$XDG_DATA_HOME/entwurf/codex-birth`, all operator-owned; the vendor trust receipt for that declaration is the operator's single answer, and `doctor-codex-birth` reports it as its own axis (present, or red with the exact instruction — never computed, never written); `install-codex-mcp` owns `[mcp_servers.entwurf-bridge]`, including the `env_vars` boundary for `CODEX_HOME`, Entwurf garden/control roots, and the app-server's `TMUX`/`TMUX_PANE`; `install-codex-statusline` owns `thread-title` in `tui.status_line`; `install-codex-terminal-title` owns `thread-id` in `tui.terminal_title`. Each has a matching doctor and inverse. **One Codex precondition is the vendor's own and no installer supplies it: the directory a sibling starts in must already be answered in this Codex.** A direct consent decision is recorded per exact directory on this rail — not the parent, not the git root — and a directory the vendor has no answer for at all opens a consent screen instead of running a first turn, so `entwurf_fresh_call` prints `codex-launch-cwd-undecided` and opens the window anyway; that screen is self-repairing, and answering it once teaches the vendor the directory for good. **Nothing here refuses a launch** — the note is a diagnostic, and the one caller that treats it as a precondition is `smoke-codex-fresh-live`, where nobody is at the keyboard. Two neighbouring cases read differently: a directory answered `untrusted` is not noted at all (the vendor skips its screen on this rail, so that turn starts), and a directory INSIDE an explicitly untrusted project is noted as `codex-launch-cwd-untrusted-ancestor` because the vendor answers that one with an error naming the repository root rather than a prompt. The check is a narrow local read of the operator's own config, never the vendor's verdict: the vendor also consents through project layers it reads from its app-server, and everything the check cannot see stays silent. The operator owns the app-server and seats it wherever they like; Entwurf never creates or supervises it. Omitted fresh placement follows the CALLER, never the backend being opened: an explicit `placement` always wins, then a Codex CALLER opens beside its own TUI pane — located by the `thread-id` that pane's terminal title carries, with 0 or 2+ matching panes refused and no fallback — then the caller's own session. (#95 first shipped a fixed `codex` home for omitted-placement Codex TARGETS; #95 D1 retired it on 2026-09-16 once the anchor made the room unnecessary.) A pane title is a placement input only, never an address, liveness or delivery fact. Exact 0.153.4 source's missing per-client carrier still bounds placing a sibling beside a TUI whose thread nobody named as unsupported.
324
+ - **OpenAI Codex CLI** (supported in 0.21.0 on Linux; macOS NOT CERTIFIED — pending physical host) — `install-codex-birth` publishes the `SessionStart` declaration into `$CODEX_HOME/hooks.json` with its launcher closure under `$XDG_DATA_HOME/entwurf/codex-birth`, all operator-owned; it owns **that declaration, not the file** — another integration's `SessionStart` group coexists (the vendor keys trust per `<path>:<event>:<group>:<handler>`), so install appends, uninstall splices only ours out, foreign bytes survive both, and `doctor-codex-birth` reports neighbours in a FOREIGN section it certifies nothing in; the vendor trust receipt is read at the index our declaration was measured at, is the operator's single answer, and `doctor-codex-birth` reports it as its own axis (present, or red with the exact instruction — never computed, never written); `install-codex-mcp` owns `[mcp_servers.entwurf-bridge]`, including the `env_vars` boundary for `CODEX_HOME`, Entwurf garden/control roots, and the app-server's `TMUX`/`TMUX_PANE`; `install-codex-statusline` owns `thread-title` in `tui.status_line`; `install-codex-terminal-title` owns `thread-id` in `tui.terminal_title`. Each has a matching doctor and inverse. **One Codex precondition is the vendor's own and no installer supplies it: the directory a sibling starts in must already be answered in this Codex.** A direct consent decision is recorded per exact directory on this rail — not the parent, not the git root — and a directory the vendor has no answer for at all opens a consent screen instead of running a first turn, so `entwurf_fresh_call` prints `codex-launch-cwd-undecided` and opens the window anyway; that screen is self-repairing, and answering it once teaches the vendor the directory for good. **Nothing here refuses a launch** — the note is a diagnostic, and the one caller that treats it as a precondition is `smoke-codex-fresh-live`, where nobody is at the keyboard. Two neighbouring cases read differently: a directory answered `untrusted` is not noted at all (the vendor skips its screen on this rail, so that turn starts), and a directory INSIDE an explicitly untrusted project is noted as `codex-launch-cwd-untrusted-ancestor` because the vendor answers that one with an error naming the repository root rather than a prompt. The check is a narrow local read of the operator's own config, never the vendor's verdict: the vendor also consents through project layers it reads from its app-server, and everything the check cannot see stays silent. The operator owns the app-server and seats it wherever they like; Entwurf never creates or supervises it. Omitted fresh placement follows the CALLER, never the backend being opened: an explicit `placement` always wins, then a Codex CALLER opens beside its own TUI pane — located by the `thread-id` that pane's terminal title carries, with 0 or 2+ matching panes refused and no fallback — then the caller's own session. (#95 first shipped a fixed `codex` home for omitted-placement Codex TARGETS; #95 D1 retired it on 2026-09-16 once the anchor made the room unnecessary.) A pane title is a placement input only, never an address, liveness or delivery fact. Exact 0.153.4 source's missing per-client carrier still bounds placing a sibling beside a TUI whose thread nobody named as unsupported.
312
325
 
313
326
  Run them as `entwurf <command>`. Which unit a doctor's refusal names, and the clean-host
314
327
  walk-through for each harness, live in [docs/setup-clean-host.md](./docs/setup-clean-host.md).
@@ -522,7 +535,7 @@ pnpm run check:full # full deterministic floor (adds the her
522
535
  ./run.sh check-agy-sender-identity # record-backed pid/start-key sender identity
523
536
  ./run.sh check-codex-native-push # deterministic Codex UDS probe + no-replay queue contract
524
537
  ./run.sh smoke-codex-config-state # Codex MCP/status-line install, doctor, and inverse
525
- ./run.sh check-codex-birth-hook # sandboxed payload + declaration: one V3 per top-level thread, the envelope's own axes, no markers
538
+ ./run.sh check-codex-birth-hook # sandboxed payload + declaration: one V3 per top-level thread, the envelope's own axes, no markers, and the declaration leaf (normalized digest, selection by launcher command, byte-preserving splice)
526
539
 
527
540
  # source-maintainer only — qualification snapshots the git work surface, and both
528
541
  # commands are source-contract gates rather than installed operator checks:
@@ -623,7 +636,7 @@ A two-pane recording covers the pre-0.12 v1 surface end-to-end — sibling resum
623
636
 
624
637
  Live peer messaging carries a sender envelope `{ sessionId, agentId, cwd, timestamp, origin?, replyable? }`; `entwurf_self` returns that authoritative envelope for the current pi session or trusted meta-session. Plain external MCP hosts are non-replyable. A garden-native meta-session carries a trusted `meta-session` envelope, but **`replyable` is a fact its own rail decides, not a consequence of being trusted** — a self-fetch citizen needs a live armed receiver, a native-push citizen needs an alive adapter probe, and a pi session needs its control socket. `entwurf_self` also reports which rail a meta-session reply would ride, because a native-push citizen has no mailbox to name. `wants_reply` is an etiquette marker rendered as a `(wants reply)` badge — not a transport contract, no wait, no polling. **v2 never gates on it:** a `wants_reply` from an external/non-replyable caller is passed through and surfaced honestly beside that sender's `replyable: false`, not rejected — the decider routes on target + intent, never on sender replyability. (The retired v1 `entwurf_send` did reject it; that behaviour went with the verb.)
625
638
 
626
- In ACP-backed and external native-harness sessions, `entwurf-bridge` exposes seven tools: `entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_self`, `entwurf_inbox_read`, and the explicit/manual `entwurf_register_native` fallback. Native pi exposes the shared capability directly through the extension surface (`entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call` tools; the socket-scan `/entwurf-sessions` command is gone — #50 C4). **For garden-id delivery/reply use `entwurf_v2`** — the canonical surface that classifies the target and routes to live-pi / self-fetch meta-mailbox (Claude Code, Copilot, OMP) / native-push (Antigravity, Codex), and refuses a dormant target instead of waking it. **To open a sibling that does not exist yet use `entwurf_fresh_call`** — it launches one of five fixed backends (`pi`, `claude-code`, `copilot`, `omp`, `codex`) as a visible window on the operator's own tmux server, requires an explicit model, accepts one optional literal absolute `cwd` (omit it or pass `""` to use the caller's cwd) and one optional `placement.tmuxSession` naming an EXISTING session on that server to open the sibling in instead of the caller's own (the operator's project seat — nothing is ever created: an absent session is `tmux-session-missing` and a name outside the addressed grammar `[A-Za-z0-9][A-Za-z0-9_-]*` is `tmux-session-name-invalid`, with no window and no session left behind. The two inputs never infer each other, and the positive form of that matters more than the prohibition: **with `cwd` omitted, a seated sibling starts in THIS agent's own working directory**, measured — it does not inherit the target session's path or its active pane, so naming the `org` seat does not put the sibling in the `org` project. Pass `cwd` when you want a different directory. entwurf never resolves that path: it hands the literal string to tmux, so with a SYMLINKED directory the receipt echoes what you asked for while the record, the transcript path, the callback envelope and `pane_current_path` all show the path the OS resolved it to — two spellings of one place, and the resolved one is what `entwurf_resume_call` later reopens), passes the model and selected directory through the runtime's visible launch path, and hands it a first task; a `copilot` launch goes through entwurf's own managed invocation and is refused before any window opens if this host lacks the Copilot birth, MCP, receiver or visible-footer units, and an `omp` launch carries its task in the `--entwurf-bootstrap` payload instead of an argv prompt because that vendor connects its MCP tools after the session has already started. Omitted placement follows the CALLER rather than the backend being opened: a Codex CALLER opens its sibling beside its own TUI pane, located by the `thread-id` that pane's terminal title carries (0 or 2+ matching panes reject with no fallback); every other caller opens in its own seat. (0.21.0 shipped a backend-selected default instead — an omitted-placement `codex` TARGET resolved a fixed operator-owned session named `codex` — and #95 D1 retired it on 2026-09-16.) Any of the five can still be sent to an operator-owned existing seat with an explicit `placement.tmuxSession`, and named `claude`/`pi` seats remain available that way, but they are not omitted-placement defaults. Read tmux here as the VISIBLE LAUNCH SEAT, never as an address: the garden id is the address, and it arrives in the callback envelope. Sibling symmetry means each backend keeps its own transcript, auth, native tools and visibility — not that the transports are made identical. The sibling's first action is a nonce callback whose sender envelope carries its garden id. The launch receipt records the requested model/cwd/seat plus tmux coordinates — including the RESOLVED target session id, which is where the window actually is — and never claims that the runtime accepted them or completed delivery. (The v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.) Garden-native operator commands require `--entwurf-control`. There is no spawn target allowlist — the target registry is gone (#50 C3): `entwurf_v2` and `entwurf_resume_call` address an existing record-backed citizen, while `entwurf_fresh_call` takes its explicit backend/model/task and optional cwd directly rather than resolving a model tuple from a registry file. **To reopen a DORMANT pi citizen use `entwurf_resume_call {target}`** — the record supplies transcript, model, provider and cwd, so it takes no prompt, no task and no model override; it runs no turn, returns a LAUNCH receipt and a separate OBSERVATION receipt (only the second says the citizen is back), refuses a non-pi target as `target-not-pi`, and on an unobserved socket leaves the visible window open and releases its lock rather than retrying.
639
+ In ACP-backed and external native-harness sessions, `entwurf-bridge` exposes seven tools: `entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call`, `entwurf_self`, `entwurf_inbox_read`, and the explicit/manual `entwurf_register_native` fallback. Native pi exposes the shared capability directly through the extension surface (`entwurf_v2`, `entwurf_peers`, `entwurf_fresh_call`, `entwurf_resume_call` tools; the socket-scan `/entwurf-sessions` command is gone — #50 C4). **For garden-id delivery/reply use `entwurf_v2`** — the canonical surface that classifies the target and routes to live-pi / self-fetch meta-mailbox (Claude Code, Copilot, OMP) / native-push (Antigravity, Codex), and refuses a dormant target instead of waking it. **To open a sibling that does not exist yet use `entwurf_fresh_call`** — WHERE it opens is a fact about the calling process, not a parameter: inside herdr (`HERDR_ENV=1`) it opens a NEW TAB in the caller's own workspace, without taking focus, and admits only `pi` and `claude-code`, refusing `placement` by name; everywhere else it launches one of five fixed backends (`pi`, `claude-code`, `copilot`, `omp`, `codex`) as a visible window on the operator's own tmux server. There is no fallback between the two — an incomplete herdr context is refused by its herdr reason rather than quietly opening a tmux window nobody inside herdr can see. Everything that follows in this paragraph describes the tmux rail; the herdr rail's coordinates, refusals and reclaim are owned by [docs/herdr-launch-rail.md](./docs/herdr-launch-rail.md). On tmux it requires an explicit model, accepts one optional literal absolute `cwd` (omit it or pass `""` to use the caller's cwd) and one optional `placement.tmuxSession` naming an EXISTING session on that server to open the sibling in instead of the caller's own (the operator's project seat — nothing is ever created: an absent session is `tmux-session-missing` and a name outside the addressed grammar `[A-Za-z0-9][A-Za-z0-9_-]*` is `tmux-session-name-invalid`, with no window and no session left behind. The two inputs never infer each other, and the positive form of that matters more than the prohibition: **with `cwd` omitted, a seated sibling starts in THIS agent's own working directory**, measured — it does not inherit the target session's path or its active pane, so naming the `org` seat does not put the sibling in the `org` project. Pass `cwd` when you want a different directory. entwurf never resolves that path: it hands the literal string to tmux, so with a SYMLINKED directory the receipt echoes what you asked for while the record, the transcript path, the callback envelope and `pane_current_path` all show the path the OS resolved it to — two spellings of one place, and the resolved one is what `entwurf_resume_call` later reopens), passes the model and selected directory through the runtime's visible launch path, and hands it a first task; a `copilot` launch goes through entwurf's own managed invocation and is refused before any window opens if this host lacks the Copilot birth, MCP, receiver or visible-footer units, and an `omp` launch carries its task in the `--entwurf-bootstrap` payload instead of an argv prompt because that vendor connects its MCP tools after the session has already started. Omitted placement follows the CALLER rather than the backend being opened: a Codex CALLER opens its sibling beside its own TUI pane, located by the `thread-id` that pane's terminal title carries (0 or 2+ matching panes reject with no fallback); every other caller opens in its own seat. (0.21.0 shipped a backend-selected default instead — an omitted-placement `codex` TARGET resolved a fixed operator-owned session named `codex` — and #95 D1 retired it on 2026-09-16.) Any of the five can still be sent to an operator-owned existing seat with an explicit `placement.tmuxSession`, and named `claude`/`pi` seats remain available that way, but they are not omitted-placement defaults. Read tmux here as the VISIBLE LAUNCH SEAT, never as an address: the garden id is the address, and it arrives in the callback envelope. Sibling symmetry means each backend keeps its own transcript, auth, native tools and visibility — not that the transports are made identical. The sibling's first action is a nonce callback whose sender envelope carries its garden id. The launch receipt records the requested model/cwd/seat plus tmux coordinates — including the RESOLVED target session id, which is where the window actually is — and never claims that the runtime accepted them or completed delivery. (The v1 verbs `entwurf` / `entwurf_resume` / `entwurf_send` are gone.) Garden-native operator commands require `--entwurf-control`. There is no spawn target allowlist — the target registry is gone (#50 C3): `entwurf_v2` and `entwurf_resume_call` address an existing record-backed citizen, while `entwurf_fresh_call` takes its explicit backend/model/task and optional cwd directly rather than resolving a model tuple from a registry file. **To reopen a DORMANT pi citizen use `entwurf_resume_call {target}`** — the record supplies transcript, model, provider and cwd, so it takes no prompt, no task and no model override; it runs no turn, returns a LAUNCH receipt and a separate OBSERVATION receipt (only the second says the citizen is back), refuses a non-pi target as `target-not-pi`, and on an unobserved socket leaves the visible window open and releases its lock rather than retrying.
627
640
 
628
641
  ### `entwurf_v2` — canonical dispatch verb
629
642
 
@@ -642,7 +655,7 @@ In ACP-backed and external native-harness sessions, `entwurf-bridge` exposes sev
642
655
 
643
656
  **`entwurf_v2` is the canonical surface for garden-id delivery.** When you have a garden id and want to reach whoever it names — message, reply, or hand-off — `entwurf_v2` is the one surface that reads whether the target is live pi, dormant pi, mailbox-backed Claude Code, Copilot or OMP, or native-push Antigravity or Codex, and routes correctly; *when unsure which transport, use `entwurf_v2`*. This prevents callers from guessing a rail from the shape of an id.
644
657
 
645
- What v2 provides is a **deterministic dispatch substrate** that moves the "which transport?" decision out of the fallible caller/model and into the decider, with transport-appropriate locking and an honest reject (no `✓ delivered`, no `.msg` garbage) when a target cannot receive. It still does **not** mint siblings, and it does not relaunch one either: every row above either reaches a citizen that is already running or refuses. Reopening a dormant pi citizen is `entwurf_resume_call`, a separate lifecycle verb that never routes through this decider. Fresh creation is the separate `entwurf_fresh_call` verb. It opens one of five fixed runtimes — Pi, Claude Code, Copilot, OMP or Codex — visibly on the caller's tmux server. Omitting placement follows the CALLER rather than the backend being opened: an explicit seat always wins; then a Codex CALLER opens beside its own TUI pane, found by the `thread-id` in that pane's terminal title (0 or 2+ matching panes reject, with no fallback); then the caller's own session. An explicit `placement.tmuxSession` sends ANY of the five to an existing named session instead (see the `entwurf_fresh_call` paragraph above for the owning detail). Each launch carries a required explicit model passed in that runtime's measured CLI dialect and one optional literal absolute `cwd`; omitted or `""` means the caller's cwd. Copilot opens through `entwurf copilot`, never the bare vendor; OMP is the opposite — the bare `omp` runtime with no positional prompt at all, because that vendor connects its MCP tools after the session has started, so the task rides a two-stage `--entwurf-bootstrap` payload the installed birth extension releases once the callback tool has actually answered. It returns only a synchronous launch receipt and lets the sibling report its new address asynchronously through the sender envelope of a nonce callback. Use this cwd input for a new cross-repository sibling; do not resume a dormant citizen as a cwd substitute. The meta-mailbox row requires an **active** self-fetch receiver; native-push requires a record-backed, probe-alive native conversation and never borrows mailbox state. The [mux launch lane](./docs/mux-launch-rail.md) owns placement, fixed-runtime launch, and the two narrow compositions above it (fresh-call and resume-call placement); delivery does not import launch, and mux is not a delivery transport.
658
+ What v2 provides is a **deterministic dispatch substrate** that moves the "which transport?" decision out of the fallible caller/model and into the decider, with transport-appropriate locking and an honest reject (no `✓ delivered`, no `.msg` garbage) when a target cannot receive. It still does **not** mint siblings, and it does not relaunch one either: every row above either reaches a citizen that is already running or refuses. Reopening a dormant pi citizen is `entwurf_resume_call`, a separate lifecycle verb that never routes through this decider. Fresh creation is the separate `entwurf_fresh_call` verb. Inside herdr it opens a Pi or Claude Code sibling in a new tab in the caller's own workspace; everywhere else it opens one of five fixed runtimes — Pi, Claude Code, Copilot, OMP or Codex — visibly on the caller's tmux server. The rest of this paragraph is the tmux rail. Omitting placement follows the CALLER rather than the backend being opened: an explicit seat always wins; then a Codex CALLER opens beside its own TUI pane, found by the `thread-id` in that pane's terminal title (0 or 2+ matching panes reject, with no fallback); then the caller's own session. An explicit `placement.tmuxSession` sends ANY of the five to an existing named session instead (see the `entwurf_fresh_call` paragraph above for the owning detail). Each launch carries a required explicit model passed in that runtime's measured CLI dialect and one optional literal absolute `cwd`; omitted or `""` means the caller's cwd. Copilot opens through `entwurf copilot`, never the bare vendor; OMP is the opposite — the bare `omp` runtime with no positional prompt at all, because that vendor connects its MCP tools after the session has started, so the task rides a two-stage `--entwurf-bootstrap` payload the installed birth extension releases once the callback tool has actually answered. It returns only a synchronous launch receipt and lets the sibling report its new address asynchronously through the sender envelope of a nonce callback. Use this cwd input for a new cross-repository sibling; do not resume a dormant citizen as a cwd substitute. The meta-mailbox row requires an **active** self-fetch receiver; native-push requires a record-backed, probe-alive native conversation and never borrows mailbox state. The [mux launch lane](./docs/mux-launch-rail.md) owns placement, fixed-runtime launch, and the two narrow compositions above it (fresh-call and resume-call placement); delivery does not import launch, and mux is not a delivery transport.
646
659
 
647
660
  A live pi target is *reached* over its control socket, but the socket is dispatch-internal transport, never identity (#50 C4). A control socket that no meta-record claims — a pre-record-era resident, an unreadable store, or a stale/planted file — is refused for **every** intent as `record-less-socket`, and the reject names the fix (restart the resident so `session_start` births its record, or quiesce and run the fresh-cut). `entwurf_peers` reports the same state as an aggregated `record-less-socket` diagnostic rather than a peer row.
648
661
 
@@ -656,6 +669,32 @@ A `--entwurf-control` session needs **no special launcher** (#50 C2): pi mints i
656
669
  pi --entwurf-control
657
670
  ```
658
671
 
672
+ **Opening pi inside herdr.** herdr starts a supported agent with its canonical executable and no
673
+ extra argv — measured on herdr 0.9.0, `herdr agent start <name> --kind pi --pane <id>` runs plain
674
+ `pi`. Citizenship is argv-gated (`pi-extensions/entwurf-control.ts`), so a pi opened that way is a
675
+ running pi and *not* a garden citizen: no record, no control socket, no garden id. Pass the flag
676
+ through herdr's own argv passthrough:
677
+
678
+ ```bash
679
+ herdr agent start <name> --kind pi --pane <pane-id> -- --entwurf-control
680
+ ```
681
+
682
+ A sibling that entwurf itself opens already carries the flag; this is only for a pi you start from
683
+ herdr by hand. The claude axis needs nothing extra — its record is minted by the meta-bridge plugin's
684
+ own `SessionStart` hook, which entwurf installs and herdr does not touch. Where such a citizen is
685
+ visible is reported as evidence in `entwurf_peers` (`placement=herdr <pane>`), never as an address:
686
+ [`docs/mux-launch-rail.md`](./docs/mux-launch-rail.md) §7-a.
687
+
688
+ The wiring those two axes need does not have to be installed by hand on a Herdr host: the Entwurf
689
+ Herdr plugin's install-time build reads Herdr's own `integration status`, and for every harness in
690
+ `{pi, claude-code}` that Herdr reports as integrated it acquires an Entwurf runtime at a stable
691
+ Entwurf-owned address and registers the user-scope wiring against it — no Entwurf clone, no manual
692
+ `npm install`. `herdr plugin install junghan0611/entwurf/plugins/herdr` is the whole command, every
693
+ reinstall is the refresh trigger, and `entwurf herdr-plugin-deactivate` is the explicit inverse
694
+ (Herdr's own uninstall removes only its checkout and calls no cleanup hook).
695
+ [`plugins/herdr/README.md`](./plugins/herdr/README.md) owns that lane, including which acquisition
696
+ source the committed lock names.
697
+
659
698
  **Resuming an existing garden session.** Use `entwurf_resume_call {target}`. It reopens a DORMANT pi citizen under the SAME garden id in a visible window in the caller's own tmux session, resolving the transcript, model, provider and cwd from the record — so it takes only the target id, and it runs no turn: the window comes back with the conversation and waits, and talking to it is still `entwurf_v2`. Two receipts arrive and mean different things: a LAUNCH receipt (tmux made a window and was asked to start pi) and an OBSERVATION receipt (the control socket answered under the same id, or `resume-unobserved`). Unobserved is a real outcome, not an error to retry — the window is visible, so read it. A citizen that is already live is refused; so is a non-pi target (`target-not-pi`), because only pi stands a control socket up. The predecessor, `entwurf_v2 intent=owned-outcome`, resumed by launching a hidden window-less background child and was withdrawn under the visible-first rule; delivery still starts no process. Identity preconditions live in `resume-launch-identity.ts`, gated by `check-resume-launch-identity`.
660
699
 
661
700
  **Starting a new session in-process — pi's own `/new`.** Since the #50 C2 cut there is nothing to replace it with: `/new`, `/fork`, `/clone` and RPC session replacement are pi's again. The replacement session fires `session_start`, which upserts its own meta-record and rebinds the control socket to that record's garden id; the old socket is dropped. pi's session id (a uuidv7) is recorded as the citizen's `nativeSessionId` and is never an address. Gate: `run.sh smoke-resident-garden-guard` REPLACEMENT section (0-token RPC E2E).
package/VERIFY.md CHANGED
@@ -54,6 +54,12 @@ Verification here is not a benchmark. In production we exchange short turns and
54
54
  - **Every MUST step is invoked and reports its own outcome.** Prerequisite-bearing LIVE MUST steps ride the P1 STEP OUTCOME protocol (`scripts/lib/step-outcome.sh`): exit 0 = PASS, exit 97 = SKIP (a prerequisite the step does not have, printed as an `[entwurf:skip]` line), anything else = FAIL. The two non-skippable static steps (`pnpm run check:full`, `check-gate-qualification`) are inline: 0 = PASS, nonzero = FAIL, no SKIP arm — they have no prerequisite to decline. A skip is never counted as a pass — that hole is what let a cortex-less host read as cortex acceptance. Without `--cut` this is the unattended diagnostic: SKIPs are reported and the run still exits 0. **`--cut` makes it acceptance and any MUST SKIP is red**, which is how "a real cut needs `LIVE=1` with `SKIP=0`" stopped being prose. A green MUST gate is **necessary, not sufficient** — GLG authorizes the cut.
55
55
  - **When cost-bearing MUST gates run (fixed 2026-07-23, the F6/F7 lesson):** a commit that touches a rail a MUST-tier live gate covers runs that gate **before cross-review is requested** — never parked behind "run it at approval time". Deferring a wired gate to a human decision is what let F6/F7 ship reviewed-and-approved; the wiring exists so the verdict never depends on who pressed enter. "배선이 없어 못 한 것은 OK, 배선이 있는데 안 돌린 것은 우리가 남긴 구멍이다." Model-in-loop cost is spent via the subscription-backed `entwurf` provider where the gate allows it, a free-tier native model otherwise; cost is a reason to pick the cheap target, not to skip the gate.
56
56
 
57
+ > **The herdr rail is an on-demand axis until it has run.** `smoke-herdr-fresh-call-live` opens a real caller inside a real herdr pane, has it invoke the PUBLIC `entwurf_fresh_call`, and reads the child's nonce callback — the one axis no deterministic gate reaches, since the rail is selected from the caller's own process environment. **The release aggregate does not yet require the herdr rail**: herdr is optional (a cut on a host without it must not block), and first evidence precedes promotion — a rail whose first acceptance run is days old has not earned a place in the gate that blocks every cut. Run it directly with `LIVE=1 ./run.sh smoke-herdr-fresh-call-live` whenever a cut changes herdr rail code, and promote it to a MUST only after the axis has a run history to point at. Without `LIVE=1`, or without herdr/pi/claude on PATH, it reports protocol SKIP — never a pass.
58
+
59
+ > **The Herdr plugin's install-time build has two axes, and one of them needs the network (#116 M3-b3).** `check-herdr-plugin-build` is deterministic and rides `check:hermetic`: the listing reaches the runner through its own spawn seam (there is no fake `herdr` binary in this repo), and the one cell that drives the REAL binary reports a named SKIP on a host without it — `ENTWURF_REQUIRE_HERDR=1` makes that absence red. What it cannot have is the real acquisition: `npm pack git+https://github.com/junghan0611/entwurf.git#<full sha>` builds the bridge through `prepare`, which installs devDependencies from the registry. So `LIVE=1 ./run.sh smoke-herdr-plugin-build-live` owns that axis, and its cells are **two kinds of evidence, labelled as such**: three are the real Herdr journey — a real `herdr plugin install` driving the real `[[build]]`, with the UNCHANGED product remote redirected to a local bare clone by git `insteadOf` (the one substitution, which is what makes an unpushed candidate testable) — covering remote-commit available, the `{pi}` → `{pi, claude-code}` reinstall with OpenCode as a zero-write negative control and exactly ONE counted vendor MCP owner entry, and the post-build Herdr-commit gap (where the ledger has already rebound to the gap commit, so the honest assertion is that transition and not unchanged bytes). The fourth cell — remote-commit **unavailable** — calls the shipped runtime leaf directly with the fixed product argv and is therefore **acquisition-leaf evidence, not a journey**; routing it through Herdr would add a second explanation for one red. Its oracle is the honest one too: active runtime, ledger and harness bytes preserved, while the journal is left as the certified `installing` retry authority the transaction deliberately writes before acquiring, and a normal install afterwards is green. Its Claude cell is where the vendor question the deterministic gates refuse to claim gets answered — whether Claude Code accepts an ABSOLUTE executable as an MCP command — and it reports a named skip on a host without `claude`, never a pass. Like the herdr fresh-call rail, this smoke is **on-demand and not an aggregate MUST**. The release aggregate does not require the plugin build smoke: herdr is optional, a cut on a host without it must not block, and first evidence precedes promotion.
60
+
61
+ > **Switching the plugin's production acquisition source is a re-proof, not a configuration change (#116 M3-b3).** `runtime-lock.json` carries a closed `source` discriminant: `npm` (exact `name@version` plus the published sha512, compared against the tarball's own bytes) is the production authority, and `herdr-checkout` (the exact commit Herdr checked out, from the fixed remote) is a VERIFICATION-ONLY carrier that exists so a candidate needs no npm release. Evidence does not transfer between them. Before a release that moves the production source — to npm, or to a GitHub Release tarball plus its sha512 — the exact acquisition and integrity comparison, the installed runtime (name@version, compiled entry, three executable bins, a real `check-bridge`), the swap and torn-swap recovery, activation and deactivation, and the package-consumer proof must all be re-run against THAT source. A candidate proof green on the checkout carrier says nothing about a registry artifact, and the reverse is equally true. **Records from before this contract are refused, not migrated:** a v1 runtime journal or activation ledger fails certification by name, and the way forward on such a host is an explicit `entwurf herdr-plugin-deactivate` (or a hand-cleared runtime/ledger state) before the next install — there is no reader that accepts both schemas, because the older shape cannot say WHICH artifact it was.
62
+
57
63
  > **Cortex is an on-demand axis, not an aggregate one.** Its rail needs an external Snowflake connection and login that the host owns, not the repo — so wiring it into the aggregate would block every cut taken on a host without that account. **The release aggregate does not re-certify Cortex**; `LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> ./run.sh smoke-acp-cortex-live` stays a required direct call whenever a cut changes Cortex rail code or an operator elects to certify that host. Its honest-skip behaviour is unchanged: run it without the connection and it reports protocol SKIP, never a pass. **Operator-host limit (2026-08-20, measured):** the source-owning host has no Snowflake subscription, and Cortex Code offers no free tier that a live turn can reach — a direct attempt confirmed it. So on this host `smoke-acp-cortex-live` can only ever report protocol SKIP, and no LIVE Cortex evidence can be produced here until Snowflake supplies a free-usable route or the account is subscribed. Record that SKIP as an absent axis; never soften it into a pass, and never substitute a different backend as Cortex evidence.
58
64
  >
59
65
  > A cut that touches the prompt-lifecycle contract (no wall clock on a running turn) owes one long-turn acceptance the aggregate floor is too short to carry: `LIVE=1 ./run.sh smoke-acp-long-turn-live` drives a real turn whose tool work outlasts the retired 600s cutoff and requires exactly one cold ACP bootstrap in the transcript. It takes >12 minutes by construction and is on-demand, not part of `release-gate`.
@@ -449,6 +449,13 @@ The birth atom publishes a `SessionStart` declaration into `$CODEX_HOME/hooks.js
449
449
  launcher closure under `$XDG_DATA_HOME/entwurf/codex-birth`; it mints a V3 record on the
450
450
  thread's first turn and sets the thread title to the garden id.
451
451
 
452
+ It owns that ONE declaration, not the file. Another integration may declare its own
453
+ `SessionStart` group in the same hooks.json — Herdr's official Codex integration does — and the
454
+ vendor runs both, because trust is keyed per declaration. So installing into a file somebody else
455
+ already wrote is an append, not a refusal; uninstalling takes out only entwurf's group and leaves
456
+ every neighbouring byte where it was; and `doctor-codex-birth` lists the neighbours in their own
457
+ FOREIGN section, certifying none of them and letting none of them move its verdict.
458
+
452
459
  **One step is the operator's, and no command can do it for them.** The vendor will not run a
453
460
  user-layer hook until it has been trusted once: open a visible plain Codex, answer
454
461
  `Trust all and continue`, then send one first turn. `doctor-codex-birth` reads the receipt the
@@ -492,7 +499,10 @@ The 2026-09-11 loaded-thread run remains pre-amendment native-push/identity evid
492
499
  first-admission acceptance passed on 2026-09-12 under the fixed-home topology #95 D1 later retired:
493
500
  real initial Pi outside `codex`, then omitted-placement Codex and outbound Pi inside it, with exact
494
501
  callbacks and addressed delivery both ways. `DELIVERY.md` owns that 57-assertion receipt, its four
495
- coordinates, and its digest as history; the caller-seat re-run is pending.
502
+ coordinates, and its digest as history. The caller-seat re-run is **accepted** — 2026-09-16, 56
503
+ assertions for the seat topology and 65 for the caller-DIRECTORY axis, both exit 0 — and it, not the
504
+ fixed home, is the current contract (`DELIVERY.md`, `BASELINE.md:37-38`, `VERIFY.md` release
505
+ acceptance).
496
506
 
497
507
  `entwurf_fresh_call` accepts backend `codex` — supported in 0.21.0 — and requires an explicit model. Its
498
508
  preflight must prove the safe birth closure, exact MCP/env atom, `thread-title`, and app-server