@junghanacs/entwurf 0.16.0 → 0.17.1

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 (48) hide show
  1. package/AGENTS.md +5 -3
  2. package/CHANGELOG.md +337 -0
  3. package/README.md +8 -11
  4. package/VERIFY.md +8 -1
  5. package/demo/README.md +1 -1
  6. package/docs/acp-backend-rail.md +25 -14
  7. package/docs/setup-clean-host.md +24 -10
  8. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/acp-client.js +1 -1
  9. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +34 -10
  10. package/package.json +10 -10
  11. package/pi-extensions/lib/acp/acp-client.ts +57 -4
  12. package/pi-extensions/lib/acp/backend-adapter.ts +78 -9
  13. package/pi-extensions/lib/acp/backend.ts +578 -18
  14. package/pi-extensions/lib/acp/claude-acp-launch.js +100 -0
  15. package/pi-extensions/lib/acp/event-mapper.ts +43 -6
  16. package/run.sh +129 -32
  17. package/scripts/check-acp-launch-namespace.ts +127 -0
  18. package/scripts/check-acp-prompt-lifecycle.ts +145 -2
  19. package/scripts/check-acp-stop-reason.ts +8 -2
  20. package/scripts/check-acp-usage-accounting.ts +1074 -0
  21. package/scripts/check-copilot-birth-hook.ts +28 -1
  22. package/scripts/check-gate-qualification.ts +4 -2
  23. package/scripts/check-omp-fresh-preflight.ts +27 -0
  24. package/scripts/check-setup-qualification.sh +40 -2
  25. package/scripts/copilot-bridge-oracle.sh +14 -6
  26. package/scripts/fake-copilot-vendor.sh +4 -2
  27. package/scripts/lib/pi-record-discovery.ts +47 -0
  28. package/scripts/mutants/acp-launch-namespace.json +34 -0
  29. package/scripts/mutants/acp-prompt-lifecycle.json +67 -2
  30. package/scripts/mutants/acp-stream-hooks.json +4 -2
  31. package/scripts/mutants/acp-usage-accounting.json +181 -0
  32. package/scripts/mutants/copilot-birth.json +3 -5
  33. package/scripts/mutants/pack-install.json +2 -2
  34. package/scripts/mutants/setup-verdict.json +35 -0
  35. package/scripts/omp-config-xdev.py +310 -0
  36. package/scripts/omp-config-xdev.sh +76 -0
  37. package/scripts/omp-tool-surface.py +61 -10
  38. package/scripts/raw-acp-child-exit-measure/README.md +285 -0
  39. package/scripts/raw-acp-child-exit-measure/acp-turn-population.py +89 -0
  40. package/scripts/raw-acp-child-exit-measure/reaper-correlation.py +47 -0
  41. package/scripts/smoke-acp-bundled-mcp-live.ts +2 -2
  42. package/scripts/smoke-acp-cortex-live.ts +2 -2
  43. package/scripts/smoke-acp-raw-turn-live.ts +1 -1
  44. package/scripts/smoke-acp-socket-citizen-live.ts +2 -2
  45. package/scripts/smoke-acp-v2-send-live.ts +2 -2
  46. package/scripts/smoke-entwurf-v2-matrix-live.ts +60 -10
  47. package/scripts/smoke-mux-lifecycle-live.ts +46 -2
  48. package/scripts/smoke-setup-verdict.sh +48 -3
package/AGENTS.md CHANGED
@@ -104,7 +104,9 @@ A `--entwurf-control` pi session is a citizen for the same reason a native bridg
104
104
 
105
105
  ## Issue queue — a manual sweeper, not a backlog
106
106
 
107
- - OPEN issues are live contracts only, capped at five. A sixth means something closes first; the cap does not bend.
107
+ - **Two caps, and the inner one is what disciplines the work.** OPEN issues are capped at **ten** in total; of those, **implementation issues — the ones that will go out on a branch — are capped at five**. A sixth implementation issue means one closes first; that inner cap does not bend.
108
+ - **What sits outside the implementation five:** research issues, and issues GLG keeps open to look at. These are not slots to be earned or swept — a research lane may stay open across releases, and a north-star issue other repos read may never close. They still count toward the ten, so the queue cannot grow without limit.
109
+ - Classify by destination, not by size: if closing it produces a diff, it is an implementation issue. If closing it produces a decision, a document, or nothing, it is not.
108
110
  - A slot is earned by a current defect or executable contract, not by importance, age, or possible future value.
109
111
  - Direction, philosophy, frozen invariants and observations without a current action live in `ROADMAP.md`, durable docs, or closed history.
110
112
  - Nothing stays open "in case." If a closed problem recurs, it returns as a new issue carrying the new evidence.
@@ -140,7 +142,7 @@ LIVE=1 AGY_CONVERSATION_ID=<id> ./run.sh smoke-agy-native-push-live
140
142
  - The deterministic floor is tiered (#70). `pnpm check` is the everyday core — toolchain (lint + typecheck), the vitest lanes, and the pure-unit / behavioral-contract / source-topology gates plus the cheap static coherence checks. It prints its own total wall time; acceptance is ≤60s on the reference host `oracle` (an operator measurement, never a hard wall-clock gate on arbitrary hosts). `pnpm run check:full` is the full deterministic floor — the core plus the hermetic-integration and package/install tiers — and is what the candidate protocol, push CI, release-gate, and `prepublishOnly` run. Exact membership is the named `check:*` group scripts in `package.json` (the executable SSOT); a gate changes tier by semantic-class decision, never because it happened to get faster or slower. Neither tier includes `check-gate-qualification`, which is scheduled separately (below).
141
143
  - **Kill-proof discipline (gate qualification).** A gate is a test only if re-planting a closed defect turns it red for the claimed reason. `check-gate-qualification` proves that automatically: committed mutants in `scripts/mutants/` must be KILLED at their `[QK:<claim>]` signature inside an isolated snapshot repo (control→mutant→restore→control; the real checkout is never written). Gates a release touches carry such manifests; assertion counts are never evidence — claim IDs + killed mutant IDs are. `check-agy-permission-matrix` holds the enumerated permission contract space; matrix cells change by axis/rule edits, never by appending cases.
142
144
  - **When changing a contract/gate:** name the production subject and an oracle independent of it; give the failing assertion a stable `[QK:<claim>]` label and add/update the exact-once mutant in `scripts/mutants/*.json`; if the contract is combinatorial, update the literal matrix axes/cells/exclusions together with their declared counts; then verify the focused gate, and let qualification and the full floor follow the scheduling contract below — once on the frozen candidate, not once per amendment. `MUTANT-STALE`/`SURVIVED`/`WRONG-REASON`/`CONTROL-RED`/`HANG`/`IMPURE` are red — never substitute an assertion count for a kill.
143
- - Run LIVE gates with `PWD` in scratch so session artifacts do not land in the repo.
145
+ - Run LIVE gates with `PWD` in scratch so session artifacts do not land in the repo. Strip `CLAUDE_CONFIG_DIR`, `PI_SESSION_ID`, and `PI_AGENT_ID` from the gate process — a live pi/ACP session exports them into children, where they strip Claude hooks or capture a fresh-call callback.
144
146
  - Release acceptance and evidence levels are defined in [VERIFY.md](./VERIFY.md); recorded host evidence is in [BASELINE.md](./BASELINE.md).
145
147
  - A failed gate or evidence downgrade blocks commit/release. Pipes can be connected and the water can still taste wrong.
146
148
 
@@ -182,7 +184,7 @@ implement → affected focused gates → independent review → one amendment bu
182
184
  - Every `.ts` file belongs to one typecheck fence: root emit-capable config, MCP strip-types config, or scripts strip-types config. Do not hide files with `exclude`.
183
185
  - Root pi extensions import TypeBox through `@earendil-works/pi-ai`; do not mix direct `@sinclair/typebox` types.
184
186
  - MCP/scripts use explicit `.ts` imports where Node strip-types requires them. Installed operator surfaces route to compiled JS.
185
- - pi runtime range is `>=0.84.3 <0.85` with devDep exact `0.84.3`; re-evaluate loader aliases and `/compat` at every minor ceiling. The ceiling moves on measurement, never on assumption, and a previous bump's argument is never reused: at 0.83.0→0.84.0 `compat.ts` was still byte-identical but `loader.ts` was NOT, so the diff itself had to be read and judged reachable-or-not. Per-bump hashes, diff judgments, and reachability findings live in the ROADMAP **Dep bump(별도 트랙)** ledger — keep them there, not here.
187
+ - pi runtime range is `>=0.84.4 <0.85` with devDep exact `0.84.4`; re-evaluate loader aliases and `/compat` at every minor ceiling. The ceiling moves on measurement, never on assumption, and a previous bump's argument is never reused: at 0.83.0→0.84.0 `compat.ts` was still byte-identical but `loader.ts` was NOT, so the diff itself had to be read and judged reachable-or-not. Per-bump hashes, diff judgments, and reachability findings live in the ROADMAP **Dep bump(별도 트랙)** ledger — keep them there, not here.
186
188
  - ACP pins are recorded in `package.json` and checked by `check-dep-versions`/`check-acp-sdk-surface`; do not describe a dependency bump as a behavioral fix without evidence.
187
189
 
188
190
  ## Working Style
package/CHANGELOG.md CHANGED
@@ -4,6 +4,343 @@ All notable changes to this project will be documented here. Format follows [Kee
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.17.1 - 2026-09-03
8
+
9
+ ### Fixed
10
+
11
+ - **The C1b red that blocked two of the three 0.17.0 `--cut` runs: a LIVE smoke was waiting exactly
12
+ as long as pi's lock-stale window, and losing by 148ms.** The cause is measured end to end, and
13
+ none of it is a product regression:
14
+ 1. pi guards `auth.json` AND `models-store.json` with `proper-lockfile` and reads through that
15
+ lock on every boot (`dist/core/auth-storage.js`), retrying a held lock for `staleMs = 30_000`
16
+ before taking it over.
17
+ 2. `terminateChild`'s SIGTERM ends a resident before `proper-lockfile`'s release ever runs, so a
18
+ kill that lands inside the lock window orphans the lock directory. Sweeping 24 kill offsets
19
+ across a boot reproduced it once, at +375ms.
20
+ 3. With `~/.pi/agent/models-store.json.lock` orphaned, the next boot → V3 record measured
21
+ **30_148ms** — and **1_114ms** immediately afterwards, once the stale takeover had cleared it.
22
+ 4. `smoke-entwurf-v2-matrix-live`'s `BOOT_TIMEOUT_MS` was `30_000`: it stopped looking 148ms
23
+ before C1b's record landed. Hence the exact signature the blocked cuts left — empty stderr, no
24
+ record in any store, and (confirmed in both runs from the host's own process-audit trail) a
25
+ child that was alive for the entire 30s and was killed by the smoke at +30s.
26
+ The control was in the same three runs: `smoke-entwurf-chain-live` does the same two-resident
27
+ dance with `BOOT_TIMEOUT_MS = 45_000` and passed 3/3 while matrix-live failed 2/3.
28
+ - **The bound is now shared and carries its receipt.** `PI_BOOT_TIMEOUT_MS = 45_000` lives in
29
+ `scripts/lib/pi-record-discovery.ts` next to the measurements above, and the five smokes that
30
+ sat on the 30s cliff (`smoke-entwurf-v2-matrix-live`, `smoke-acp-socket-citizen-live`,
31
+ `smoke-acp-bundled-mcp-live`, `smoke-acp-v2-send-live`, `smoke-acp-cortex-live`) now derive
32
+ from it. 30s was the single worst value available: it expires *inside* the takeover.
33
+ `smoke-resident-garden-guard` already used 90s and needed no change.
34
+ - **A boot overrun now names the lock.** `describePiLockResidue()` reports which pi locks are held
35
+ at failure time — read-only, because a live holder and an orphan look identical from outside and
36
+ only pi's own stale protocol may arbitrate them.
37
+ - **Both blocked smokes now say WHICH failure they hit**, which is what made the cause findable.
38
+ `smoke-entwurf-v2-matrix-live` watches each resident (pid, exit code/signal and how many ms in, a
39
+ signal-0 liveness probe taken in the catch before the reaper runs, and a **per-child** stderr tail
40
+ — the old single shared buffer could not say whether C1 or C1b spoke; an empty tail now prints as
41
+ `(empty)` rather than being skipped). `smoke-mux-lifecycle-live` attaches window forensics when a
42
+ nonce callback never comes — pane-pid liveness, `list-panes`, and the last 40 lines of
43
+ `capture-pane`, for both cells. Its launch receipt already tells a human "the window is visible
44
+ and can be read directly"; on a headless gate nobody is there and the window is torn down seconds
45
+ later. Every diagnostic step is best-effort so it can never become the failure.
46
+
47
+ The mux pi-native nonce timeout (the second blocked cut) is NOT this bug and stays open: 300s is
48
+ ten times the stale window, and the codex rail was healthy in that same run (`chain-live` and
49
+ `smoke-omp-fresh-live` both passed on it). The forensics above are what the next occurrence will
50
+ answer with.
51
+
52
+ ### Verification
53
+
54
+ - `pnpm run check:toolchain` (biome + `tsc` ×3) — green.
55
+ - **The repair measured against the exact failing condition.** With `models-store.json.lock`
56
+ planted as an orphan, `LIVE=1 ./run.sh smoke-entwurf-v2-matrix-live` is **17/17 PASS in 34s** —
57
+ the stale wait absorbed. The same condition measured 30_148ms to birth, i.e. red under the old
58
+ 30_000 bound.
59
+ - **Boot cost, undisturbed**: 1008–1212ms across 80 consecutive boots in the smokes' spawn shape
60
+ (5.1–5.4s under 4× CPU oversubscription). The bound is not sized for boot cost; it is sized to
61
+ clear pi's stale window.
62
+ - **The new matrix-live diagnostic exercised on a real failure path**, at 0 model tokens, by
63
+ pointing the smoke at a bogus provider: `resident C1: pid=… EXITED code=1 signal=null at +1057ms
64
+ — it was gone before the wait ended`, with that child's own stderr beneath it.
65
+ - `LIVE=1 ./run.sh smoke-entwurf-v2-matrix-live` — **17/17 PASS** on the real
66
+ `openai-codex/gpt-5.6-luna` target (undisturbed run).
67
+ - `LIVE=1 ./run.sh smoke-mux-lifecycle-live` — **81 checks passed, exit 0** (real model turns on
68
+ both pi cells and the claude-code cell).
69
+ - 15 iterations of the gate's own C1b neighbourhood (`smoke-resident-garden-guard` → `check-bridge`
70
+ → `doctor-pi-provider` → `smoke-entwurf-v2-matrix-live`) reproduced nothing — recorded because it
71
+ is what ruled out host load and prior-smoke residue and sent the search to pi's lock.
72
+ - **`LIVE=1 ./run.sh release-gate /tmp/entwurf-release-gate-0.17.1.hb5Q5j --cut`** — **MUST
73
+ PASS=23 FAIL=0 SKIP=0**, **BEHAVIOR PASS=1 FAIL=0 SKIP=0**, exit 0, `cut: OK`. Run on `665191d`,
74
+ 2026-09-03 09:46:37 → 10:35:42 KST, with `env -u CLAUDE_CONFIG_DIR -u PI_SESSION_ID -u
75
+ PI_AGENT_ID`. Log: `/tmp/entwurf-release-gate-0.17.1.hb5Q5j/release-gate.log`. This carried
76
+ `pnpm run check:full` (451s) and `./run.sh check-gate-qualification` (**347/347 KILLED**) as MUST
77
+ steps. **This is the green `--cut` 0.17.0 never got**: both cells that blocked it — C1b in
78
+ `smoke-entwurf-v2-matrix-live` and the pi-native nonce in `smoke-mux-lifecycle-live` — passed
79
+ here, on the same host, in one run.
80
+ - **Exact-SHA CI on `665191d`** — `check` (34m20s, carrying `check:full` and
81
+ `check-gate-qualification`), `install-surface`, `artifact-consumer`, all `success`.
82
+ Run: https://github.com/junghan0611/entwurf/actions/runs/33697821117
83
+
84
+ ## 0.17.0 - 2026-09-02
85
+
86
+ ### Verification
87
+
88
+ Each receipt carries its own scope; none of them is transferable to another commit or host.
89
+
90
+ - **`LIVE=1 ./run.sh release-gate <scratch> --cut`** — **MUST PASS=23 FAIL=0 SKIP=0**,
91
+ BEHAVIOR PASS=1 FAIL=0 SKIP=0, exit 0, `cut: OK`. Run on commit `0379764`, tracked tree
92
+ `cb1a3dc69b521909f0fe6f956e5c0fa36e885d5955f91271dd8e63d30cd918fb`, 2026-09-02
93
+ 20:14:51 → 21:04:41 KST. Frozen log: `/tmp/entwurf-0.17.0-evidence/ACCEPTED-cut-release-gate.log`.
94
+ This run carried `pnpm run check:full` and `./run.sh check-gate-qualification`
95
+ (**346/346 KILLED**) as MUST steps. **It is not the 0.17.0 acceptance SHA:** `9479750`
96
+ changed source after it.
97
+ `CHANGELOG.md` is in `package.json` `files` and `check:full → check:package → check-pack`
98
+ enumerates it (`npm pack --dry-run`, required/forbidden names — not a cardinality lock).
99
+ `NEXT.md` is not in `files`. Working-tree cleanliness at cut start was an operator
100
+ observation, not a line in the gate log.
101
+ - **Four reds along the way, all real, none smoothed.** A formatter rejection of the new mutant
102
+ manifest; a doc-floor gate catching the ROADMAP ledger entry quoting the ADAPTER's
103
+ `engines.node ">=22"` as though it were entwurf's own; a qualification run that aborted RED on its
104
+ own self-fence because the worktree was edited mid-run — kept in the record because a
105
+ qualification predating the final bytes is not a receipt, and the fence is what says so; and
106
+ `check-bridge-delivery` failing "artifact is not stale" after the review amendment edited three
107
+ `.ts` files without rebuilding the MCP bridge. That last one is why the tree hash above is quoted
108
+ twice: `mcp/entwurf-bridge/dist/` is gitignored, so the rebuild moved no candidate byte, and the
109
+ two receipts really do cover the same tree. None of the four would have been caught by typecheck
110
+ plus the focused gate.
111
+ - **The first `--cut` attempt was RED — MUST PASS=18 FAIL=5 SKIP=0 — and every one of the five was
112
+ the operator's own environment, not this release.** Recorded because the failure wore a
113
+ convincing disguise: it read as a broken host. `static` was the stale bridge artifact above,
114
+ re-created when a `git checkout`/`merge` bumped every tracked `.ts` mtime past the built `dist`.
115
+ The other four came from running the gate **inside a live entwurf ACP Claude session**, which
116
+ exports two carriers into every child it spawns. `CLAUDE_CONFIG_DIR` points at the ACP overlay,
117
+ whose `settings.json` is `hooks: {}` by design — so the Claude Code children the live smokes
118
+ launch are born with no entwurf SessionStart hook and mint no meta-record, failing
119
+ `smoke-claude-native-resume-live`, `smoke-entwurf-chain-live` and `smoke-mux-lifecycle-live` on
120
+ citizen birth. The same variable made `doctor-meta-bridge` report `installed: absent` and
121
+ `claude mcp list` deny `entwurf-bridge`; with it unset the doctor reads
122
+ `installed: v3 (229fef123589)` matching source and assembled exactly. `PI_SESSION_ID` did the
123
+ analogous damage on the omp axis: `smoke-omp-fresh-live` reported a 240s nonce-callback timeout,
124
+ but the sibling was alive — it was born at 19:58:21 KST and called back at 19:58:28 to the
125
+ AMBIENT caller id rather than the caller the smoke had minted, so the smoke watched the right
126
+ mailbox and saw nothing. That smoke's own source anticipates this exact disguise
127
+ (`scripts/smoke-omp-fresh-live.ts:186`, "the failure would masquerade as a silent sibling").
128
+ Re-run with `env -u CLAUDE_CONFIG_DIR -u PI_SESSION_ID -u PI_AGENT_ID`, all five turned green and
129
+ nothing else changed. That gap — VERIFY.md named `PWD` in scratch but not the carriers — is what
130
+ cost the first cut its 52 minutes; VERIFY.md and AGENTS.md now name the strip.
131
+ - **Qualification lane size.** On the `0379764` cut the total was **335 → 346**, eleven new claims
132
+ in `scripts/mutants/acp-usage-accounting.json` (absent on the 0.16.1 base). `9479750` adds a
133
+ twelfth, `ACP-REBILL-MAIN-LOOP-SCOPE`. Standalone `./run.sh check-gate-qualification` on
134
+ `fee89d3` is **347/347 KILLED** (36m19s, log `/home/junghan/.pi/background/1788355846036-bg03.log`).
135
+ That is a qualification receipt, not a `--cut` receipt.
136
+ - **Three recuts on the D1 tree, all `cut: BLOCKED`.** Each ran
137
+ `env -u CLAUDE_CONFIG_DIR -u PI_SESSION_ID -u PI_AGENT_ID LIVE=1 ./run.sh release-gate <scratch> --cut`.
138
+ All three: MUST PASS=22 FAIL=1 SKIP=0, BEHAVIOR PASS=1. None of the FAILs is D1.
139
+ 1. `/tmp/entwurf-cut-0.17.0.WdTkH7` — `smoke-entwurf-v2-matrix-live` C1b: second
140
+ `pi --entwurf-control` wrote no hidden-store record in 30s, stderr empty. Isolated re-run:
141
+ 17/17 PASS.
142
+ 2. `/tmp/entwurf-cut2-0.17.0.XQcOuR` — `smoke-mux-lifecycle-live` pi-native nonce
143
+ `mux-fresh-call-4bd6391e9783fb24fb1ee74a` never arrived in 300s (claude-code cell passed;
144
+ v2-matrix passed this run). Same shape as the 0.16.1 mux-lifecycle retry.
145
+ 3. `/tmp/entwurf-cut3-0.17.0.sfZ8WC` — C1b 30s again. Isolated re-run: 17/17 PASS.
146
+ mux-lifecycle PASS this run.
147
+ GLG 2026-09-03: no fourth cut. Make proceeds with this gap named here, not rounded up to
148
+ `cut: OK`.
149
+ - **`smoke-acp-raw-turn-live`** — PASS on the moved pin, quoted from the accepted `--cut` run's own
150
+ output rather than from a session message: launch source
151
+ `package:@agentclientprotocol/claude-agent-acp` (not PATH fallback), `protocolVersion=1`, model
152
+ `claude-sonnet-5`, `stopReason=end_turn`, reply `"OK"`, 62,998 bytes NDJSON. This is the dep-bump
153
+ track's own named lock, not a substitute for it. An earlier standalone run of the same smoke
154
+ reported 58,189 bytes and another 58,178; the byte count is per-run and is not a fixed
155
+ fingerprint, which is precisely why the number cited here is the one the gate printed.
156
+ - **Recurrence corpus measurement** — cited from the in-source comment at
157
+ `pi-extensions/lib/acp/backend.ts` (`priorTurnInputOutputSum`): 2,398 of 2,410 adjacent pairs
158
+ (99.50%), 2026-05 → 2026-09. The raw pair inventory is not in the frozen receipts; treat it as
159
+ an operator observation recorded in source, not as a cut receipt.
160
+
161
+ ### Changed
162
+
163
+ - **ACTION REQUIRED — ACP accounting consumers must read `usage.acp`.** Claude ACP
164
+ `PromptResponse.usage` is the sum across a turn's API round trips, not one request's
165
+ prompt shape. The turn's accounting totals now travel at
166
+ `usage.acp.{input,output,cacheRead,cacheWrite}`; pi's four request-shaped fields remain
167
+ zero, while `usage.totalTokens` remains the vendor's context-occupancy reading. Consumers
168
+ that display ACP token/cache accounting must read the new key — `agent-config`'s
169
+ `pi-extensions/glg-footer.ts` does so at `47b9b95`. The numerator is taken from the
170
+ vendor's ACCOUNTING-GRADE `_meta.quota.model_usage` rows (summed) in preference to the
171
+ main-loop-only `PromptResponse.usage`, because the cost denominator is an adjacent diff of
172
+ the backend's running total and already has that wider scope. Deterministic receipts:
173
+ `scripts/check-acp-usage-accounting.ts` CELLs 1/1b drive pi's real overflow and context
174
+ readers with the incident-scale 4,185,084 cache-read aggregate on a 223,516-token context,
175
+ and CELL 1e drives both token carriers at once so a silent fallback to the narrow one
176
+ cannot pass for a preference.
177
+ - **ACTION REQUIRED — the certified Claude ACP dependency coordinates move together, and
178
+ this release REQUIRES them.** The bundled Claude adapter is
179
+ `@agentclientprotocol/claude-agent-acp` **0.70.0 → 0.73.0**, its wire SDK is
180
+ `@agentclientprotocol/sdk` **1.3.0 → 1.4.0**, and the resolved transitive
181
+ `@anthropic-ai/claude-agent-sdk` is **0.3.232 → 0.3.257**. This is not a refresh riding
182
+ along with the fix: `_meta.quota.model_usage` does not exist before adapter 0.71.0 (added
183
+ by upstream `fad4d10`, "report per-model token usage on prompt responses"), so the
184
+ accounting above has no accounting-grade numerator without this bump. Three adapter minors
185
+ are folded into one certification; the per-bump measurement — declared-dependency deltas
186
+ per tag, the zod floor narrowing at 0.71.0, lock peer-resolution, the reachable/unreachable
187
+ split of the new surface, and the re-measured MCP readiness boundary — is recorded in the
188
+ ROADMAP "Dep bump(별도 트랙)" ledger. Re-run the ACP support gates on any locally
189
+ overridden adapter command before treating that command as covered by these coordinates.
190
+
191
+ ### Fixed
192
+
193
+ - **Claude ACP no longer presents a turn aggregate as one request to pi.** That projection
194
+ caused pi's raw overflow reader to compact a live 223,516-token session under a
195
+ 1,000,000-token window, invented two phantom cache misses, and silenced the one real miss
196
+ after a 401-minute idle gap. The aggregate is retained as accounting evidence, context
197
+ occupancy remains separate and is carried forward across a turn that reports none, and a
198
+ material re-billed prefix is reported as a PROVEN LOWER BOUND rather than silently hidden.
199
+ Turn cost remains the adjacent difference of the SDK's cumulative estimate, never a local
200
+ reprice. A backend with no measured semantics (cortex) is still sealed not at all.
201
+ - **The re-billed-prefix bound no longer mixes main-context occupancy with wide accounting IO.**
202
+ `readTurnAccounting` prefers `_meta.quota.model_usage`, whose rows also count Task subagents,
203
+ sidechains, and internal compaction. Feeding that `cacheWrite` into a bound whose occupancy is
204
+ main-context inflates the lower bound through both remaining terms, so a warm main prefix can
205
+ announce a miss and attach this turn's dollar figure to it. The bound and its stored prior now
206
+ read `PromptResponse.usage` (main loop) only; `usage.acp` stays the wide totals. Deterministic
207
+ oracle: `scripts/check-acp-usage-accounting.ts` CELL 1f.
208
+ - **A backwards session total no longer asserts a cause it has not measured.** The operator
209
+ diagnostic for a decreasing cumulative cost previously named a conversation reset as "the
210
+ known cause". The adapter's `conversation_reset` handler only switches to a fresh
211
+ conversation and touches no cost (0.73.0 `dist/acp-agent.js:3675-3682`); the documented
212
+ mechanism lives in claude-agent-sdk instead, which states that a mid-session `/clear`
213
+ resets the running total (`sdk.d.ts:4884`). The notice now reports the observation, names
214
+ `/clear` in the vendor's own word rather than paraphrasing it into a different noun, states
215
+ explicitly that the adapter's `conversation_reset` event is NOT that mechanism, and says plainly
216
+ that the cause is not measured here. Naming the disproven event as the documented cause was the
217
+ release's own thesis being violated inside its own diagnostic; it was caught in review.
218
+
219
+ ## 0.16.1 - 2026-09-01
220
+
221
+ ### Verification
222
+
223
+ Each receipt carries its own scope; none of them is transferable to another commit or host.
224
+
225
+ - **`pnpm run check:full`** — PASS, exit 0, **454s** inside the gate, on the 0.16.1
226
+ versioned tree (HEAD `2e7ceb4` + the uncommitted changelog/version edits).
227
+ - **`./run.sh check-gate-qualification`** — **335/335 KILLED, NOT KILLED 0**, as the
228
+ release-gate MUST step. The first run of this cut reported **334/335**: the single
229
+ miss was `PACK-INSTALL-PIN-MATCHER-BOUNDED` reading `MUTANT-STALE` because `5c1bda5`
230
+ moved `run.sh`'s pin matcher to the 0.84.4 floor and left that mutant's anchor on
231
+ 0.84.3, so it matched 0× and the gate had silently stopped asking its question.
232
+ Repaired in `2e7ceb4` and re-run green. A rotted anchor is recorded here rather than
233
+ quietly fixed, because "N killed" only means something when N is the whole set.
234
+ - **`LIVE=1 ./run.sh release-gate /tmp/entwurf-release-gate-0.16.1.m0LUjr --cut`** —
235
+ **MUST: PASS=23 FAIL=0 SKIP=0**, **BEHAVIOR: PASS=1 FAIL=0 SKIP=0**. Log at
236
+ `/tmp/entwurf-release-gate-0.16.1.m0LUjr/release-gate.log`. `smoke-mux-lifecycle-live`
237
+ failed the first run on a pi-native nonce callback that never arrived inside its 300s
238
+ window (the codex rail was measured healthy at the time: 5h 23%, weekly 77%), and
239
+ passed on re-run — recorded because a LIVE step that needed a retry is not the same
240
+ evidence as one that passed first time.
241
+ - **#72 launch/observation receipts** — the launcher's two field claims are not
242
+ unit-provable, so they were measured on the host that produced the failure:
243
+ `/proc/<pid>/cmdline` carries no vendor name, the janitor's own Phase 1 selector run
244
+ against the real `ps` row with its age threshold forced to 0 selects nothing, and a
245
+ real SIGTERM still ends at exit 0 with the frame on stderr. Commands and output in
246
+ `scripts/raw-acp-child-exit-measure/README.md`.
247
+
248
+ ### Added
249
+
250
+ - **`setup` composes OMP — the fifth backend is now in the one command.** v0.16.0 admitted OMP
251
+ as a D6 citizen with installers, doctors and inverses for every unit, but `setup_all` had no
252
+ `omp` branch: on an operator host the released package printed a **green** setup summary while
253
+ OMP had nothing installed — no extension, no `mcp.json` entry, no garden id on the status line.
254
+ Presence-driven now, same shape as Copilot: `omp` on PATH composes four independent named
255
+ components (birth → MCP hand → the `tools.xdev` operator setting → receiver), absent is one
256
+ zero-state SKIP, and a detected-but-incomplete unit is a named FAIL owning a nonzero exit.
257
+ Gate: `smoke-setup-verdict` S-8 drives the real composition against a stub vendor and asserts
258
+ every install-state, both extension units in the sandbox agent dir, the effective `xdev-off`
259
+ reading, and idempotence on a second run; S-1 pins `OMP_BIN` absent and requires the SKIP row.
260
+ - **`install-omp-config` / `uninstall-omp-config` — the operator setting is a unit, not a
261
+ documentation step.** `tools: xdev: false` was a hand-edit in `docs/setup-clean-host.md`, so a
262
+ host that skipped it registered every entwurf tool behind the vendor's `xd://` wrapper and the
263
+ doorbell announced a tool the model could not call. The writer owns exactly the lines it adds
264
+ (recorded in its install-state), refuses a symlinked config, refuses a config it cannot parse,
265
+ and refuses an EXPLICIT operator `tools: xdev: true` **by name** rather than overwriting a
266
+ decision — setup surfaces that as a component FAIL for the operator to resolve. The inverse
267
+ takes back only the recorded lines and refuses when the file changed since install.
268
+ - **`docs/adding-a-harness.md` step 10 — an onboarding is not finished until `setup` composes
269
+ it.** The rule the OMP gap forced, with the five conditions a new harness owes (presence-only
270
+ trigger, one independent component row per unit, compose only what an inverse can undo,
271
+ operator settings are units with a refusing writer, pin the probe seam and add an aggregate
272
+ cell) and the shape to watch for: two closed parity loops with no gate owning the edge between
273
+ them.
274
+
275
+ ### Changed
276
+
277
+ - **pi floor moved to 0.84.4.** devDependency exact pin, the three peer ranges (`>=0.84.4 <0.85`),
278
+ the `check-pack-install` runtime pins, the pin-leak matcher's version boundary with its synthetic
279
+ fixture, and the pnpm lockfile all move together — the floor is one fact with several spellings,
280
+ and a partial bump is how they drift apart.
281
+
282
+ ### Fixed
283
+
284
+ - **#72 — the ACP Claude child is no longer selected by the host janitor that was killing it, and
285
+ a caught signal survives the vendor erasing it.** The child was not crashing: it was being **SIGTERMed from outside**. A
286
+ janitor installed on the operator's host for a *different* harness (openclaw's acpx, upstream
287
+ PR #245) selects the vendor process name `claude-agent-acp` by **argv substring** and kills
288
+ anything older than 900s. entwurf **retains** its child across turns, so that child's age is the
289
+ age of the SESSION, not of a turn — every session past 15 minutes was shot at every 5 minutes.
290
+ The vendor's own handler then turned the signal into `dispose(); process.exit(0)`, so the death
291
+ reached entwurf as exit code 0 with no signal, indistinguishable from a clean shutdown. That is
292
+ why three diagnosis passes retired four candidate causes and still missed it. Measured: **12 of
293
+ 12** anomalous terminations across two boots correlate with a reap, pid- and timestamp-locked —
294
+ including the sample that opened the issue (2026-07-30) and the original field report
295
+ (2026-08-16); receipts in `scripts/raw-acp-child-exit-measure/`. Two changes, both inside the
296
+ issue's repair fence (no timeout, no replay, no watcher/supervisor/hidden retry): (a) the default
297
+ launch is now an **entwurf-owned launcher** (`pi-extensions/lib/acp/claude-acp-launch.js`) that
298
+ imports the vendor **in-process** — same process, so there is no child to restart and it cannot
299
+ become a supervisor — carrying a name no vendor-name scanner matches, consuming no argv (the
300
+ vendor's own `argv.slice(1)` self-reinvocation still works), standing down if it would otherwise
301
+ be the only signal listener (an observer that suppressed default termination would make the
302
+ process signal-immune), and dying nonzero without retry if the import fails; (b) a caught
303
+ SIGTERM/SIGINT is recorded as a **typed lifecycle fact** on its own line
304
+ (`launch observed SIGTERM before child exit (sender not attributed)`) via an exact full-line
305
+ control frame that is consumed out of the vendor stderr tail — the tail stays vendor evidence,
306
+ the observation is entwurf's, and sender attribution is explicitly NOT claimed (that needs the
307
+ host journal). `CLAUDE_AGENT_ACP_COMMAND` stays verbatim: an operator who names their own command
308
+ owns the result. **Known trade:** after the name split a host janitor can no longer collect
309
+ entwurf's children even when they genuinely leak, so entwurf now owns that cleanup story.
310
+ **Read the order right: the cause was the host's janitor, and its owner has retired it**
311
+ (`nixos-config e283d92`, 2026-09-01 — `acp-zombie-reaper.timer` disabled). Measured there:
312
+ PR #245 is CLOSED, not merged; that deployment runs `acp.enabled=false` with no ACP agent;
313
+ acpx is not even installed in the running image (`/app/node_modules/acpx` absent, its `.bin`
314
+ entries dangling symlinks); and every reap since the 2026-06-10 ACP removal was a single-process
315
+ misfire matching our pid table. So the change here is **not what stops the symptom** — the
316
+ retirement is. It is defense-in-depth against the class, and that class is wider than this
317
+ issue: the selector reads the WHOLE `ps` line, so on a harness that puts the prompt in argv, a
318
+ session merely *discussing* `claude-agent-acp` matched. Measured 2026-09-01 with zero ACP
319
+ processes alive, the script's own counter read `alive_acp=3` — three ordinary agent sessions,
320
+ one of them the session investigating it. A launcher whose name we own survives the next such
321
+ janitor too. Gates:
322
+ new `check-acp-launch-namespace` (2 mutants) plus `check-acp-prompt-lifecycle` CELLs 12–13,
323
+ where 13 is the negative sibling holding that vendor prose mentioning `SIGTERM` can never forge
324
+ the observation (3 mutants). The primary repair still belongs to the janitor's owner — a cleaner
325
+ should scope by a positive marker it owns, not by a name anyone may share.
326
+
327
+ - **A vendor-written `config.yml` no longer reads as `unreadable`.** omp's own settings writer
328
+ emits `modelRoles:` followed by an indented `{}`, and `scripts/omp-tool-surface.py`'s
329
+ block-only reader returned None for the WHOLE file on that shape — so an untouched operator
330
+ config classified as unreadable and `doctor-omp-mcp` went RED for a reason unrelated to
331
+ `tools.xdev` (measured on a real host, omp 18.0.0). The reader now parses flow collections in
332
+ value position and as a whole child block. `check-omp-fresh-preflight` gains the vendor shapes
333
+ plus `[QK:OMP-XDEV-VENDOR-SHAPE-READABLE]` direct assertions — agreement between the two
334
+ readers could never catch this, since both collapse `unreadable` and `true` into "not false".
335
+ - **The Copilot plugin-row grammar admits the vendor's state token.** Copilot CLI 1.0.81 prints
336
+ `<qualified> (v0.1.0) (enabled)` plus an indented `from <path>` continuation line;
337
+ `copilot_exact_row_version` read the version as `0.1.0) (enabled`, called the row malformed,
338
+ and refused the birth install on a host whose plugin was installed and enabled — `entwurf
339
+ setup` reported `copilot-birth: FAIL`. The grammar now admits exactly one optional
340
+ `(enabled)`/`(disabled)` token; any other trailing token is still malformed. The fake vendors
341
+ in `scripts/fake-copilot-vendor.sh` and `check-copilot-birth-hook.ts` emit the 1.0.81 shape,
342
+ with `[QK:COPILOT-ROW-STATE-TOKEN-ADMITTED]` holding both directions.
343
+
7
344
  ## 0.16.0 - 2026-08-31
8
345
 
9
346
  This release admits **OMP (`omp`) as the fifth garden backend** and closes the admission
package/README.md CHANGED
@@ -60,8 +60,8 @@ native Antigravity / agy
60
60
  ```
61
61
 
62
62
  Claude's `install-meta-bridge`, Copilot's four `install-copilot-*` surfaces, agy's
63
- `install-agy-{bridge,statusline,hooks}` and OMP's three `install-omp-{bridge,mcp,receive}` units
64
- (each with its own `doctor-omp-*`) are distinct because their lifecycle and delivery transports are
63
+ `install-agy-{bridge,statusline,hooks}` and OMP's four `install-omp-{bridge,mcp,config,receive}`
64
+ units are distinct because their lifecycle and delivery transports are
65
65
  genuinely different. Codex remains verified probe evidence, not a shipped managed native-citizen
66
66
  lane; see [DELIVERY.md](./DELIVERY.md).
67
67
 
@@ -157,7 +157,7 @@ cd ~/repos/gh/entwurf
157
157
 
158
158
  The full source setup requires Node 24, pnpm, and Python 3 on PATH. Harnesses are
159
159
  optional-by-presence: `setup` runs the frozen dependency install, then composes what the operator
160
- already installed — a compatible `pi` (`>=0.84.3 <0.85`), Claude Code, agy, and the Copilot CLI
160
+ already installed — a compatible `pi` (`>=0.84.4 <0.85`), Claude Code, agy, and the Copilot CLI
161
161
  each get their wiring completed when detected, an absent harness is an explicit zero-state SKIP,
162
162
  and a detected harness that cannot be completed (including a below-floor `pi`) is a named FAIL
163
163
  that makes setup exit nonzero. `setup` never installs a harness binary or touches a credential
@@ -188,7 +188,7 @@ gitignored and may be absent or stale immediately after a checkout or pull.
188
188
  ### Pi adapter / ACP plugin lane
189
189
 
190
190
  To use the `entwurf` provider inside pi, install a compatible pi binary
191
- separately (`@earendil-works/pi-coding-agent >=0.84.3 <0.85`). Then point pi at
191
+ separately (`@earendil-works/pi-coding-agent >=0.84.4 <0.85`). Then point pi at
192
192
  the npm-installed package or development clone:
193
193
 
194
194
  ```bash
@@ -213,17 +213,14 @@ SKIP while the detected harnesses are composed.
213
213
 
214
214
  A plain MCP registration exposes the bridge tools; a **garden-native** session also
215
215
  needs entwurf's lifecycle hook and identity marker. `setup` already composes all of that
216
- for every harness it detects — you do not paste this list to install. OMP is the one
217
- exception and it is deliberate: its units are operator-selectable installs that `setup`
218
- does not compose, so the three `install-omp-*` commands below are how an omp host gets
219
- wired. Otherwise this is the repair surface: each unit has its own installer, its own
220
- doctor with a named refusal, and its own inverse, so a single broken unit can be redone
221
- without touching the rest.
216
+ for every harness it detects — you do not paste this list to install. This is the repair
217
+ surface: each unit has its own installer, its own doctor with a named refusal, and its own
218
+ inverse, so a single broken unit can be redone without touching the rest.
222
219
 
223
220
  - **Claude Code** (Linux-certified axis) — `install-meta-bridge`, `doctor-meta-bridge`.
224
221
  - **Antigravity / agy** — `install-agy-bridge`, `install-agy-statusline`, `install-agy-hooks`, each with a matching `doctor-agy-*`.
225
222
  - **GitHub Copilot CLI** — 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-*`.
226
- - **OMP (`omp`)** — three units, all 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-receive` (the receiver extension: mailbox watch + announce-only doorbell) — each with a matching `doctor-omp-*` and `uninstall-omp-*`. One OPERATOR setting sits beside them and is not ours to write: `tools: xdev: false` in the omp agent config, without which the vendor mounts MCP tools as `xd://` devices the model cannot call.
223
+ - **OMP (`omp`)** — 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.
227
224
 
228
225
  Run them as `entwurf <command>`. Which unit a doctor's refusal names, and the clean-host
229
226
  walk-through for each harness, live in [docs/setup-clean-host.md](./docs/setup-clean-host.md).
package/VERIFY.md CHANGED
@@ -165,8 +165,15 @@ cd "$REPO_DIR" && ./run.sh setup "$PROJECT_DIR"
165
165
 
166
166
  ### 1.2 Live acceptance (optional)
167
167
 
168
+ Put `PWD` in scratch so session artifacts do not land in the repo. LIVE gates inherit
169
+ the caller's environment: a pi or ACP session exports `PI_SESSION_ID` / `PI_AGENT_ID` /
170
+ `CLAUDE_CONFIG_DIR` into every child, which masquerades as the smoke's identity or
171
+ strips Claude hooks (`hooks: {}` overlay). Strip those carriers:
172
+
168
173
  ```bash
169
- LIVE=1 ./run.sh release-gate /path/to/consumer-project --cut
174
+ cd /path/to/scratch
175
+ env -u CLAUDE_CONFIG_DIR -u PI_SESSION_ID -u PI_AGENT_ID \
176
+ LIVE=1 /path/to/entwurf/run.sh release-gate /path/to/scratch --cut
170
177
  pi --provider entwurf --model claude-sonnet-5 -p "reply with ok only" # one-turn smoke
171
178
  ```
172
179
 
package/demo/README.md CHANGED
@@ -139,7 +139,7 @@ SCENE_DELAY=30 FINAL_PAUSE=10 bash demo.sh
139
139
 
140
140
  ## Prerequisites
141
141
 
142
- - `pi` on PATH (current floor 0.84.3)
142
+ - `pi` on PATH (current floor 0.84.4)
143
143
  - `entwurf` provider configured + auth ready for the selected sender/peer models
144
144
  - `asciinema` installed
145
145
  - `agg` installed (optional — only for GIF conversion)
@@ -92,11 +92,11 @@ undifferentiated "supported" column is what let a Claude PASS read as if it also
92
92
 
93
93
  | Surface | Declaration | Class | What a green actually says |
94
94
  |---|---|---|---|
95
- | Entwurf package | `0.15.1` | shipped baseline | the package contract these rows belong to |
96
- | pi runtime | devDep exact `0.84.3`, peer `>=0.84.3 <0.85` | **exact** oracle + **closed range** | built and certified against 0.84.3; hosts inside the range are accepted, and the ceiling moves only on measurement |
97
- | ACP wire SDK | `@agentclientprotocol/sdk 1.3.0` | **exact** | the shared wire oracle both adapters speak |
98
- | Claude ACP adapter | `@agentclientprotocol/claude-agent-acp 0.70.0` | **exact**, bundled | the adapter we ship and certify; resolved before any PATH fallback |
99
- | Claude Agent SDK | `0.3.232` (transitive) | **exact** oracle | the runtime risk surface behind the adapter |
95
+ | Entwurf package | `0.17.0` | shipped baseline | the package contract these rows belong to |
96
+ | pi runtime | devDep exact `0.84.4`, peer `>=0.84.4 <0.85` | **exact** oracle + **closed range** | built and certified against 0.84.4; hosts inside the range are accepted, and the ceiling moves only on measurement |
97
+ | ACP wire SDK | `@agentclientprotocol/sdk 1.4.0` | **exact** | the shared wire oracle both adapters speak |
98
+ | Claude ACP adapter | `@agentclientprotocol/claude-agent-acp 0.73.0` | **exact**, bundled | the adapter we ship and certify; resolved before any PATH fallback |
99
+ | Claude Agent SDK | `0.3.257` (transitive) | **exact** oracle | the runtime risk surface behind the adapter |
100
100
  | Anthropic SDK | `0.100.1` | **exact**, peer-resolution only | satisfies the Agent SDK peer floor (0.93.0+); never an API client here (gate L4) |
101
101
  | Claude Code runtime | `>=2.1.217` (`entwurf.claudeCodeFloor`) | **floor** | below it, hook args are silently dropped; entwurf enforces this itself |
102
102
  | Node | `>=24` (`engines.node`) | **floor** | single axis, derived everywhere else |
@@ -141,8 +141,10 @@ different reasons, and collapsing them would hide a real risk**:
141
141
  - **Capability-gated.** AIR typed session failures, the 0.69.0 AIR file-change report, terminal
142
142
  output widgets and nested subagent transcripts each test a client capability entwurf does not
143
143
  send, so the adapter itself keeps the legacy path.
144
- - **Advertised but never called.** Some surfaces carry no capability prerequisite at all — 0.70.0's
145
- `providers/list` / `providers/set` / `providers/disable` are advertised unconditionally. They are
144
+ - **Advertised but never called.** Some surfaces carry no capability prerequisite at all — the
145
+ `providers/list` / `providers/set` / `providers/disable` trio added in 0.70.0 is advertised
146
+ unconditionally, and 0.71.0–0.73.0 add native subagents, async tasks, message-specific session
147
+ forks, AI-generated session titles and permission-mode kinds on the same footing. They are
146
148
  unreachable only because the common loop never invokes them (nor `logout`). Nothing upstream
147
149
  enforces that; it is our own call-site discipline, and it stops holding the moment we use one.
148
150
 
@@ -255,13 +257,22 @@ caller-session `_meta`, and cross-machine certification.
255
257
 
256
258
  A backend can return `newSession` before its declared MCP server is callable. This was
257
259
  observed intermittently on the Claude rail and directly on Cortex's private `mcp.json`
258
- path. Neither `claude-agent-acp` 0.70.0 nor the Cortex landing adds a client-side
259
- readiness fence, and `mcpServerStatus()` is not called by the common loop.
260
- (Re-measured at the 0.68.0 → 0.70.0 bump: AIR typed failures and the 0.69.0 AIR
261
- file-change report are both capability-gated and unadvertised by entwurf; 0.70.0's
262
- `providers/set` / `providers/disable` are advertised unconditionally by the adapter but
263
- entwurf calls neither, so no session's provider state is transitioned; goal extension is
264
- still not a fence. This bump changes no readiness behavior and closes no part of #72.)
260
+ path. Neither `claude-agent-acp` 0.73.0 nor the Cortex landing adds a client-side
261
+ readiness fence over a session's declared MCP servers, and entwurf's common loop
262
+ calls `mcpServerStatus()` nowhere.
263
+ (Re-measured at the 0.70.0 0.73.0 bump, not inherited from the previous one.
264
+ `mcpServerStatus` call sites in `src/acp-agent.ts` went 0 2, new in 0.71.0 via
265
+ `0cbbaf3` (MCP OAuth, LLM-25012) — so the ADAPTER now calls it where it previously
266
+ did not. Both sites were read at `v0.73.0 src/acp-agent.ts:1618` and `:1711`: each is
267
+ gated behind `supportsMcpOAuth(query)`, and the polling one waits only on a server
268
+ that already reported `needs-auth`, never on every declared server before
269
+ `newSession` returns. That is an auth handshake, not a readiness fence, so the
270
+ boundary below is unchanged. The other reachable-surface findings also re-measured:
271
+ AIR typed failures and the AIR file-change report stay capability-gated and
272
+ unadvertised by entwurf; `providers/set` / `providers/disable` stay advertised
273
+ unconditionally and uncalled; native subagents, async tasks, session forks, session
274
+ titles, permission-mode kinds and clear-context planning are all new-but-uncalled.
275
+ This bump changes no readiness behavior and closes no part of #72.)
265
276
 
266
277
  ### 11-7-a/b. Instrument and first measurement
267
278
 
@@ -12,10 +12,10 @@ only on Linux because its strict live-owner join uses `/proc`.
12
12
  | npm/pnpm | npm is bundled with Node; pnpm is required for source setup | package or source installation |
13
13
  | Python 3 | required by `setup`/`install` (project-path normalization + settings writers); `--help`/`check-bridge` stay Python-free | pi/Claude/agy/Copilot wiring writers |
14
14
  | entwurf | global/project-local `@junghanacs/entwurf`, or a source checkout | operator command and garden capability |
15
- | pi | optional-by-presence, `>=0.84.3 <0.85` — absent is an explicit setup SKIP, below-floor is a named FAIL | ACP provider, control sockets |
15
+ | pi | optional-by-presence, `>=0.84.4 <0.85` — absent is an explicit setup SKIP, below-floor is a named FAIL | ACP provider, control sockets |
16
16
  | Claude Code | optional, **`>=2.1.217`** — the exec-form hook floor | Claude ACP auth/runtime and mailbox-backed native citizen |
17
17
  | GitHub Copilot CLI | optional-by-presence, operator-installed and authenticated — absent is an explicit setup SKIP; detected composes all four units (birth/MCP/receiver/footer) | self-fetch citizen and visible fresh |
18
- | OMP (`omp`) | optional, operator-installed; three units (birth/MCP/receiver) plus `tools: xdev: false` in the agent config | self-fetch citizen and visible fresh (accepted on one host — see §4b) |
18
+ | OMP (`omp`) | optional-by-presence, operator-installed — absent is an explicit setup SKIP; detected composes all four units (birth/MCP/`tools.xdev` setting/receiver) | self-fetch citizen and visible fresh (accepted on one host — see §4b) |
19
19
  | Antigravity `agy` | optional, operator-installed and authenticated | native-push citizen |
20
20
  | Cortex Code | optional, operator-installed and authenticated | Cortex ACP backend |
21
21
 
@@ -72,11 +72,12 @@ cd ~/repos/gh/entwurf
72
72
 
73
73
  This owns `~/.local/bin/entwurf` as a symlink to that checkout's `run.sh` and fails if the
74
74
  link is foreign, outside PATH, or shadowed by another command. It detects and wires
75
- pi/Claude/agy/Copilot by presence and prints a computed per-component PASS/SKIP/FAIL summary — a
75
+ pi/Claude/agy/Copilot/OMP by presence and prints a computed per-component PASS/SKIP/FAIL summary — a
76
76
  detected harness that cannot be completed makes setup exit nonzero. A detected `copilot`
77
77
  composes all four native units (birth → MCP → receiver → visible footer) with independent
78
- per-unit verdicts (#86 C3b); §4 keeps the explicit per-unit install/doctor/inverse surfaces for
79
- repair.
78
+ per-unit verdicts (#86 C3b), and a detected `omp` composes its own four (birth → MCP →
79
+ `tools.xdev` setting → receiver) the same way; §4 and §4b keep the explicit per-unit
80
+ install/doctor/inverse surfaces for repair.
80
81
 
81
82
  ### 1.1 User-scope ownership (one shared registration, one recorded owner)
82
83
 
@@ -109,7 +110,7 @@ packageRoot↔installerRoot coupling mismatch and a package/provider managed-pat
109
110
  Install the exact release floor, then wire the project:
110
111
 
111
112
  ```bash
112
- npm install -g @earendil-works/pi-coding-agent@0.84.3
113
+ npm install -g @earendil-works/pi-coding-agent@0.84.4
113
114
  pi --version
114
115
 
115
116
  cd ~/entwurf-smoke
@@ -117,7 +118,7 @@ entwurf install .
117
118
  pi -e "$(npm root -g)/@junghanacs/entwurf" --list-models entwurf
118
119
  ```
119
120
 
120
- The supported range is `>=0.84.3 <0.85`. It is a hard minimum: installing this
121
+ The supported range is `>=0.84.4 <0.85`. It is a hard minimum: installing this
121
122
  release onto a 0.83.x pi host upgrades the runtime rather than keeping the older
122
123
  minor. A host using only the external MCP bridge can skip pi until it needs a
123
124
  control socket; no delivery rail launches a pi process.
@@ -213,16 +214,26 @@ sibling launched onto a default-config host would start, look healthy, and be un
213
214
  the callback tool at all. A refusal there names `omp-callback-tool-uncallable` and opens
214
215
  nothing.
215
216
 
217
+ `setup` composes all four omp units when `omp` is on PATH, and the setting is one of them —
218
+ `entwurf setup` writes `tools: xdev: false` itself. The verbs below are the REPAIR path, not
219
+ the install:
220
+
216
221
  ```bash
217
222
  entwurf install-omp-bridge # the birth extension, into <omp agent dir>/extensions/
218
223
  entwurf install-omp-mcp # the omp-native entwurf-bridge server
224
+ entwurf install-omp-config # the operator setting: tools.xdev: false
219
225
  entwurf install-omp-receive # the receiver extension: mailbox watch + doorbell
220
226
 
221
227
  entwurf doctor-omp-bridge
222
- entwurf doctor-omp-mcp
228
+ entwurf doctor-omp-mcp # also owns the tools.xdev runtime axis
223
229
  entwurf doctor-omp-receive
224
230
  ```
225
231
 
232
+ The setting writer owns exactly the lines it adds and records them, so `uninstall-omp-config`
233
+ takes back its own bytes and nothing else. It refuses a symlinked config, a config it cannot
234
+ parse, and — deliberately — an EXPLICIT `tools: xdev: true`: that is your decision, not drift,
235
+ so setup names it as a component FAIL for you to resolve instead of overwriting it.
236
+
226
237
  Order matters only in one direction: the receiver JOINS the citizen birth mints, and
227
238
  announces a tool the MCP hand provides. Install it without them and it will log
228
239
  `arm-deferred`, give up after ~20s, and `doctor-omp-receive` will name the missing sibling
@@ -234,8 +245,11 @@ retires it. A task subagent arms nothing. While nothing is armed, dispatch to th
234
245
  id is the honest `mailbox-undeliverable` refusal — an unarmed receiver is a legible state,
235
246
  not a broken one.
236
247
 
237
- The admission work has landed and `setup` still does not compose these: the three omp
238
- units stay operator-selectable installs, so run them yourself on an omp host.
248
+ `[측정]` This section used to end by saying `setup` did not compose these and the verbs had to
249
+ be run by hand. That is what v0.16.0 actually shipped, and on an operator host it printed a
250
+ green `setup` summary with OMP entirely absent — no extension, no MCP entry, no visible garden
251
+ id. The composition landed afterwards; `docs/adding-a-harness.md` step 10 is the rule that
252
+ keeps the next harness from repeating it.
239
253
 
240
254
  Both installers resolve the omp agent directory the way omp itself does, and REFUSE rather
241
255
  than guess when an inherited `PI_CODING_AGENT_DIR`, `PI_CONFIG_DIR` or `PI_PROFILE` makes it