@junghanacs/entwurf 0.17.2 → 0.18.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 (65) hide show
  1. package/AGENTS.md +6 -5
  2. package/CHANGELOG.md +299 -0
  3. package/CONTRIBUTING.md +1 -1
  4. package/README.md +9 -5
  5. package/VERIFY.md +3 -3
  6. package/demo/README.md +1 -1
  7. package/docs/acp-backend-rail.md +46 -12
  8. package/docs/setup-clean-host.md +35 -3
  9. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +14 -9
  10. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +2 -2
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-deliverability.js +76 -0
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-fact-provider.js +9 -6
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-facts.js +11 -3
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peer-observe.js +66 -0
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +10 -1
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +8 -2
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +25 -13
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +6 -0
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +38 -0
  20. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-hook.js +64 -4
  21. package/mcp/entwurf-bridge/src/index.ts +16 -8
  22. package/package.json +10 -9
  23. package/pi-extensions/entwurf-control.ts +12 -2
  24. package/pi-extensions/lib/acp/acp-client.ts +3 -3
  25. package/pi-extensions/lib/acp/backend-adapter.ts +3 -3
  26. package/pi-extensions/lib/acp/backend.ts +3 -3
  27. package/pi-extensions/lib/acp/event-mapper.ts +5 -5
  28. package/pi-extensions/lib/entwurf-deliverability.ts +104 -0
  29. package/pi-extensions/lib/entwurf-fact-provider.ts +20 -6
  30. package/pi-extensions/lib/entwurf-facts.ts +52 -3
  31. package/pi-extensions/lib/entwurf-peer-observe.ts +77 -0
  32. package/pi-extensions/lib/entwurf-peers-render.ts +12 -1
  33. package/pi-extensions/lib/entwurf-v2-decider.ts +26 -8
  34. package/pi-extensions/lib/entwurf-v2-production.ts +32 -12
  35. package/pi-extensions/lib/entwurf-v2-surface.ts +6 -0
  36. package/pi-extensions/lib/meta-session.ts +44 -0
  37. package/pi-extensions/meta-bridge-hook.ts +72 -3
  38. package/run.sh +146 -56
  39. package/scripts/check-acp-usage-accounting.ts +9 -9
  40. package/scripts/check-bridge-delivery.ts +42 -1
  41. package/scripts/check-entwurf-fact-provider.ts +22 -1
  42. package/scripts/check-entwurf-facts.ts +17 -2
  43. package/scripts/check-entwurf-peers-surface.ts +20 -2
  44. package/scripts/check-entwurf-self-address.ts +20 -2
  45. package/scripts/check-entwurf-v2-decider.ts +6 -3
  46. package/scripts/check-entwurf-v2-matrix.ts +10 -5
  47. package/scripts/check-entwurf-v2-production.ts +72 -1
  48. package/scripts/check-gate-qualification.ts +63 -12
  49. package/scripts/check-meta-hook-session-switch.ts +560 -0
  50. package/scripts/check-release-gate-outcomes.ts +160 -3
  51. package/scripts/meta-bridge-doctor.sh +29 -4
  52. package/scripts/mutants/bridge-command-boot.json +3 -3
  53. package/scripts/mutants/gate-qualification.json +28 -0
  54. package/scripts/mutants/meta-hook-session-switch.json +223 -0
  55. package/scripts/mutants/pack-install.json +14 -3
  56. package/scripts/mutants/release-gate.json +22 -0
  57. package/scripts/raw-acp-compaction-measure/README.md +77 -0
  58. package/scripts/raw-acp-compaction-measure/probe.ts +196 -0
  59. package/scripts/raw-claude-session-switch/README.md +170 -0
  60. package/scripts/smoke-acp-cortex-live.ts +22 -2
  61. package/scripts/smoke-acp-raw-turn-live.ts +1 -1
  62. package/scripts/smoke-acp-v2-send-live.ts +23 -2
  63. package/scripts/smoke-entwurf-chain-live.ts +85 -13
  64. package/scripts/smoke-entwurf-v2-matrix-live.ts +23 -1
  65. package/scripts/smoke-mux-fresh-call-live.ts +13 -3
package/AGENTS.md CHANGED
@@ -90,7 +90,7 @@ A `--entwurf-control` pi session is a citizen for the same reason a native bridg
90
90
 
91
91
  - **control-socket domain (currently `pi`)**: socket liveness, per-target lock, live send, and a dormant cell that rejects.
92
92
  - **No relaunch transport exists inside delivery.** `spawn-bg` — a detached, window-less resume child — was removed under the visible-first rule, not deprecated behind a reject, and `entwurf_v2` still starts no process: a dormant socket-domain citizen is refused as `dormant-fire-forget-unsupported`. Reopening one is the separate lifecycle verb `entwurf_resume_call`, which is pi-only (`target-not-pi` otherwise), returns a LAUNCH receipt and an OBSERVATION receipt that are never merged, releases its per-gid lock on every path, and on an unobserved socket leaves the visible window open rather than retrying. No watcher, no retry, no supervisor. Do not re-route it through `entwurf_v2` and do not describe a spawn domain that does not exist.
93
- - **self-fetch domain (Claude Code, Copilot CLI)**: active receiver + mailbox deliverability; no resume authority. The two arm that receiver through different vendor surfaces — a Claude hook that emits watchPaths, a Copilot first-party extension that holds the watch in a forked child — and the marker records which, because the pid a reader verifies differs.
93
+ - **self-fetch domain (Claude Code, Copilot CLI)**: active receiver + mailbox deliverability; no resume authority. The two arm that receiver through different vendor surfaces — a Claude hook that emits watchPaths, a Copilot first-party extension that holds the watch in a forked child — and the marker records which, because the pid a reader verifies differs. A live marker is not by itself an armed doorbell: where the watch owner IS the process the sender marker is keyed to (`ownerKind: claude-code-cli`), that owner may switch sessions in place, so deliverability also requires its sender marker to still name the same garden. That join is `ownerKind`-scoped by construction — a Copilot watch lives in a forked child with its own pid, so the join does not exist there and must never be applied to it (#101).
94
94
  - **native-push domain (currently Antigravity)**: adapter probe + direct injection; no mailbox and no resume authority.
95
95
  - `origin: "pi-session" | "meta-session" | "external-mcp"` records sender provenance. It is not the citizen identity schema and not a hierarchy.
96
96
  - `entwurf_peers` reports record citizens and liveness facts only. It never embeds routing verbs or socket addresses for peers.
@@ -139,7 +139,7 @@ LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> ./run.sh smoke-acp-cortex-live # o
139
139
  LIVE=1 AGY_CONVERSATION_ID=<id> ./run.sh smoke-agy-native-push-live
140
140
  ```
141
141
 
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).
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. The full tier carries `check-gate-manifests` (the qualification HEAD, through `check:hermetic`); the everyday core does not, and neither tier carries the mutant-executing `check-gate-qualification`, which is scheduled separately (below).
143
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.
144
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.
145
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.
@@ -157,7 +157,7 @@ implement → affected focused gates → independent review → one amendment bu
157
157
 
158
158
  - **Inner loop:** run only the gates whose subject changed. Do not open the full floor to learn what a focused gate already answers.
159
159
  - **Review before floor:** independent review and its corrections close as one bundle before the full floor runs.
160
- - **Qualification is scheduled, not ambient.** `check-gate-qualification` is not in the default check chains (core or full), so the operator inner loop never re-pays the full mutant inventory. It runs standalone once when a lane changed a gate, mutant, or matrix; machine time re-proves it everywhere else — the CI `check` job runs it on every push, and release-gate carries it as a MUST step.
160
+ - **Qualification is scheduled, not ambient — but its HEAD is not.** The mutant-EXECUTING body, `check-gate-qualification`, is not in the default check chains (core or full), so the operator inner loop never re-pays the full mutant inventory. It runs standalone once when a lane changed a gate, mutant, or matrix; machine time re-proves it everywhere else — the CI `check` job runs it on every branch push, and release-gate carries it as a MUST step. Its head — runner self-test, manifest-set validation, declared lane inventory — is `check-gate-manifests`, which executes no mutant and never snapshots the repo, and it IS in `check:hermetic`: three of the five reds qualification has ever produced in CI died there in under five seconds. A tag push runs no CI at all; the same SHA's branch run already carries every job, and that branch run is the exact-SHA evidence a release quotes.
161
161
  - **Full floor once** (`pnpm run check:full`), on the frozen commit candidate. While it runs, nothing edits the worktree or index — including the NEXT boot sectors; a moved candidate voids the run's evidence.
162
162
  - **pre-commit is not the floor.** `.husky/pre-commit` carries only fast static checks (whitespace, lint, typecheck); the full floor is owned by this protocol, not by the hook. Do not grow the hook back, and do not build receipt/cache machinery to prove the protocol was followed.
163
163
  - **Release/LIVE acceptance is untouched.** VERIFY.md floors keep full strength; a shorter inner loop never lowers release evidence.
@@ -172,6 +172,7 @@ implement → affected focused gates → independent review → one amendment bu
172
172
  | `pi-extensions/lib/entwurf-v2-*.ts` | v2 contract, decider, transports, runner, production wiring; visible resume keeps launch injected |
173
173
  | `pi-extensions/lib/mux-*.ts` | same-tmux placement plus narrow fresh-call and visible-resume launch compositions |
174
174
  | `pi-extensions/lib/entwurf-fact*.ts` | record citizens + transport-specific liveness facts |
175
+ | `pi-extensions/lib/entwurf-peer-observe.ts` | the IO half of the observed peer facts (receiver / transcript) |
175
176
  | `pi-extensions/lib/native-push/` | native-push adapter/probe/register leaf |
176
177
  | `pi-extensions/acp-provider.ts` | `entwurf` provider registration |
177
178
  | `pi-extensions/lib/acp/` | ACP adapter rail, config/overlay, augment, turn loop, event mapping |
@@ -184,8 +185,8 @@ implement → affected focused gates → independent review → one amendment bu
184
185
  - 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`.
185
186
  - Root pi extensions import TypeBox through `@earendil-works/pi-ai`; do not mix direct `@sinclair/typebox` types.
186
187
  - MCP/scripts use explicit `.ts` imports where Node strip-types requires them. Installed operator surfaces route to compiled JS.
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.
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.
188
+ - pi runtime range is `>=0.85.1 <0.86` with devDep exact `0.85.1`; 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.
189
+ - ACP pins are recorded in `package.json` and owned by `check-acp-sdk-surface`, which is the vitest contract `test/acp-sdk-surface.contract.test.ts` (`./run.sh check-acp-sdk-surface` is a transition shim into it, not a `scripts/` gate). `check-dep-versions` is the **pi** pin's oracle and reads no ACP pin — do not cite it for one. Do not describe a dependency bump as a behavioral fix without evidence.
189
190
 
190
191
  ## Working Style
191
192
 
package/CHANGELOG.md CHANGED
@@ -4,6 +4,305 @@ All notable changes to this project will be documented here. Format follows [Kee
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.18.1 - 2026-09-06
8
+
9
+ Two lanes, both landing on `main` after `v0.18.0`: the CI evidence-budget stage 1 (#102, from
10
+ research #99) and the dependency bump (#104 — pi, claude-agent-acp, and the OMP adoption rule
11
+ GLG decided to carry with them). They are not one cause; they are one release.
12
+
13
+ ### Upgrade note — this one asks something of the operator
14
+
15
+ **The pi floor moved, and entwurf does not install harnesses.** The supported range is now
16
+ `>=0.85.1 <0.86`, so a host still carrying pi 0.84.x is BELOW it. `entwurf setup` says so by
17
+ name — `pi FAIL — detected pi <version> is outside the supported range >=0.85.1 <0.86 — Pi
18
+ wiring not written` — and writes no pi wiring rather than wiring a runtime it cannot vouch
19
+ for. That refusal is the design (Hard Rule 17), not a bug to work around.
20
+
21
+ The order matters:
22
+
23
+ 1. **Upgrade pi yourself first.** `pi update`, or `pnpm add -g @earendil-works/pi-coding-agent@0.85.1`
24
+ for a pnpm-global install. entwurf never does this for you.
25
+ 2. **Then `entwurf setup`** — one command, as always. This release changes no deployment
26
+ writer (`pi-extensions/lib/meta-session.ts`, the four `install-*` paths and the packaged
27
+ plugins are byte-identical to 0.18.0), so unlike the 0.17.2 and 0.18.0 upgrades there is no
28
+ stale-writer trap here. What setup does need to redo is the pi wiring it refused to write
29
+ while the runtime was below floor.
30
+ 3. **Restart any pi session you had open.** A running pi process keeps the binary it started
31
+ with, and this release's changed extension surfaces (`entwurf-control.ts` and four ACP
32
+ modules) are loaded at session start. A live 0.84.4 session stays a 0.84.4 session.
33
+
34
+ Hosts without pi are unaffected: absence is still an explicit setup SKIP, not a failure.
35
+
36
+ ### Verification
37
+
38
+ All of the following ran on oracle (Linux, Claude Code 2.1.263, node 24.18.1, pi 0.85.1,
39
+ omp 18.0.0).
40
+
41
+ - **`LIVE=1 ./run.sh release-gate /tmp/entwurf-release-gate-0.18.1.PtUm0r --cut` → `cut: OK`.**
42
+ **MUST: PASS=23 FAIL=0 SKIP=0**, BEHAVIOR: PASS=1 FAIL=0 SKIP=0, exit 0.
43
+ 2026-09-06 18:30:51 → 19:23:40 KST (52m49s). Log preserved at
44
+ `<scratch>/release-gate.log`.
45
+ - `check-gate-qualification` as its MUST step: **369/369 KILLED**.
46
+ - `smoke-acp-raw-turn-live` — the Dep-bump track's designated lock for an ACP adapter
47
+ move — PASS on 0.75.1: launch source `package:@agentclientprotocol/claude-agent-acp`
48
+ (not a PATH fallback), model `claude-sonnet-5`, `protocolVersion=1`,
49
+ `stopReason=end_turn`, 65,780 bytes of NDJSON captured.
50
+ - Both #91 drift sentinels green: `smoke-omp-receive-live`, and `smoke-omp-fresh-live`
51
+ with 21 assertions (step 9 clause 7) — on omp **18.0.0**, below the documented weak
52
+ floor of 18.1.10, which is exactly what "the floor records what was proven, not what is
53
+ permitted" means.
54
+ - `pnpm run check:full` on the prepared tree: exit 0, 480s.
55
+ - Pre-push landing run for the implementation HEAD `c247594`: CI run `34018205091`,
56
+ `check` + `install-surface` + `artifact-consumer` all `success` (`check` 34m14s,
57
+ `check:full` 328s, qualification 369/369).
58
+
59
+ **The first `--cut` was BLOCKED, and the reason was not this release.** MUST PASS=20 FAIL=3
60
+ SKIP=0: `smoke-entwurf-chain-live`, `smoke-mux-lifecycle-live` and `smoke-omp-fresh-live`
61
+ each failed waiting on a sibling that never took its turn, and all three drive their sibling
62
+ on `openai-codex/*` while every Claude-rail step passed. Measured at the time: the codex
63
+ weekly window was at **100%**. The chain's own timeout instrumentation is what made this
64
+ readable rather than mysterious — it printed `terminus fixture at timeout: ownerPid=… alive=true
65
+ ownerAlive=true watchArmed=true`, so the mailbox was provably healthy and the chain had simply
66
+ stalled upstream. After the quota reset the same gate went green with no code change. Recorded
67
+ because an exhausted subscription rail looks exactly like a broken product until someone
68
+ measures it.
69
+
70
+ ### Changed
71
+
72
+ - **The qualification HEAD runs in the everyday floor; the mutant BODY stays scheduled (#102).**
73
+ Across 549 CI runs the qualification step went red five times and **none of the five came from
74
+ mutant execution** — three died in the head in four or five seconds, two were a gate already red
75
+ on a clean tree (#99 B-3). So the head stops waiting behind the ~28-minute body.
76
+ `check-gate-qualification` gains a `--manifests-only` entrypoint, shipped as
77
+ `run.sh check-gate-manifests` and placed in `check:hermetic` (~8s): runner self-test, manifest-set
78
+ validation against the origin index, and the declared lane inventory — **zero mutants executed and
79
+ no snapshot of this repo**. The body runs the same head first; its contract, output and
80
+ mutant-execution semantics are unchanged.
81
+ - **CI push is filtered to branch refs (#102).** All 66 semver-tag runs in this repo's history
82
+ rebuilt a SHA a branch push had already built, and not one reported a fact its branch run had not
83
+ (the single non-green tag run failed at the same step as its main run, two seconds later). The
84
+ exact-SHA evidence a release quotes is the branch run, which is what the release skill's oracle
85
+ already selects.
86
+ - **`check-omp-birth-hook` joins `check:hermetic` (#102).** It was the one gate a committed mutant
87
+ named that ran nowhere else, so the control-pre of a 28-minute run was the only thing in the repo
88
+ that could notice it going red. `check-release-gate-outcomes` cell 9 now owns that as a contract:
89
+ every mutant-named gate is inside `check:full` **or** states its exclusion in prose an operator
90
+ reads (`scripts/check-setup-qualification.sh` takes the second arm, as its own header declared).
91
+ - **pi runtime 0.84.4 → 0.85.1** (#104): devDep exact ×3, peer range `>=0.85.1 <0.86` ×3, the
92
+ `run.sh` pack-install pins, `pnpm-workspace.yaml`, the lockfile, and the five baseline docs
93
+ `check-dep-versions` binds. **0.85.0 is deliberately skipped**: its published `exports` advertised
94
+ `./client` and `./experimental/plugin` at `dist/*` paths the tarball does not contain, which
95
+ upstream named and fixed in 0.85.1 ("SDK import failures caused by unintentionally publishing
96
+ internal experimental code and dependencies in 0.85.0") by making both subpaths source-only.
97
+ entwurf imports neither, so it never reached us — but the landing coordinate is 0.85.1.
98
+ - **`@agentclientprotocol/claude-agent-acp` 0.73.0 → 0.75.1** (#104). The other two ACP pins do
99
+ **not** move: all four tags v0.73.0–v0.75.1 declare the same `@agentclientprotocol/sdk 1.4.0`,
100
+ `@anthropic-ai/claude-agent-sdk 0.3.257`, `zod ^4.0.0` and `engines.node >=22`, so
101
+ `@anthropic-ai/sdk` stays pinned at `0.100.1` against an unchanged `>=0.93.0` peer floor.
102
+ - **Compaction now reaches operators as a tool lifecycle, not assistant text** (upstream #991, in
103
+ 0.75.0). A compacting turn shows a `[tool:start] Compact conversation` / `[tool:…]` notice pair
104
+ where 0.73.0 wrote `Compacting completed.` into the assistant's own text. No mapper change was
105
+ needed and no accounting path is touched; the measurement, the join through the production event
106
+ mapper, and the limits are in `scripts/raw-acp-compaction-measure/README.md` and
107
+ `docs/acp-backend-rail.md` §11-8.
108
+ - **OMP gets a documented *weak* floor instead of a version gate (#91).** entwurf still detects omp
109
+ by presence alone — no `entwurf.ompFloor`, no coherence gate, no exact pin — because a floor is
110
+ the answer to a vendor that fails SILENTLY, which OMP has never been observed to do, and its
111
+ contact surface is guarded by two release-gate MUST smokes that go loudly red. What is recorded
112
+ instead is the last version with a LIVE receipt (**18.1.10**, 2026-09-04 thinkpad), the rule that
113
+ the number moves only when a new receipt exists, and the two sentinels
114
+ (`smoke-omp-fresh-live`, `smoke-omp-receive-live`). Code change: none.
115
+ `docs/setup-clean-host.md` §4b.
116
+
117
+ ### Fixed
118
+
119
+ - **An undeclared pi 0.85.x break, caught by our own typecheck (#104).** `pi-tui`'s `Container`
120
+ gained a `private mouseLayout?` in the 0.85.0 mouse work (0.84.4's `Container` had no private
121
+ member at all) while `Box` declares a separate private field of the same name, so TypeScript's
122
+ private-member identity rule broke the `Box → Container` structural assignment that had held
123
+ through 0.84.4 (`TS2322` at `entwurf-control.ts`). Upstream's Breaking section names only
124
+ `createGatewayBindingFetch`. The repair narrows the annotation to what the vendor contract
125
+ actually asks for — `MessageRenderer` returns `Component | undefined` — which is what the helper
126
+ always needed.
127
+ - **The pack-install pin-leak matcher was blind to a new closure member (#104).** pi 0.85.0 added
128
+ `@earendil-works/chord` as a runtime dependency of pi-coding-agent, pi-agent-core, pi-client and
129
+ pi-protocol — inside the runtime closure, with a name carrying no `pi-` prefix. Measured on a real
130
+ 0.85.1 install tree, `@earendil-works+chord@0.85.1` sits beside the seven pi entries and the
131
+ `^@earendil-works+pi-` filter did not see it, so an unpinned caret would have floated while
132
+ `check-pack-install` printed a verified pin — the same class as the 2026-07-21 `pi-agent-core`
133
+ incident, and it made the "covers every other pi package" comment false. The matcher now filters on
134
+ the org prefix, chord is pinned explicitly, and the self-test splits into two cells so each
135
+ property fails under its own name. New claim `[QK:PACK-INSTALL-PIN-MATCHER-COVERS-CLOSURE]` with
136
+ its exact-once replant; inventory **368 → 369 mutants across 40 lanes**.
137
+ - **`check-pack-install`'s "every harness absent" row was not actually absent of OMP.** The row
138
+ pins harness probes away with explicit `*_BIN` seams, and `OMP_BIN` was never added when 0.16.0
139
+ admitted OMP — the two sibling fixtures (`smoke-setup-verdict.sh`,
140
+ `check-setup-qualification.sh`) both got it. On a host that HAS `omp` on PATH the row's premise
141
+ was therefore false: setup detected omp, and the omp config/receiver installers refused —
142
+ correctly — because the row also exports `PI_CODING_AGENT_DIR`, which omp reads too, leaving the
143
+ target directory ambiguous. The product was right and the fixture was wrong. It stayed invisible
144
+ because CI runners carry no omp **and** `release_gate()` does not run this gate (it is
145
+ `prepublishOnly` plus the CI install-surface job), so no green floor ever covered it. Measured
146
+ here with a control: a clean clone of the pre-bump HEAD fails at the same row for the same
147
+ reason. The seam is added to both setup rows, and the zero-state probe now requires **five**
148
+ SKIPs so a sixth harness cannot be admitted while this seam is left behind again.
149
+ - **`VERIFY.md` still advertised a pi range two bumps stale, and now nothing can leave it there.**
150
+ It sat outside `check-dep-versions`' `BASELINE_DOCS`, so nothing read it; it has joined that
151
+ list (six docs). Its declaration is a plain range, so the existing range scan binds it — no new
152
+ prose pattern was added for it.
153
+ - **`AGENTS.md` claimed `check-dep-versions` checks the ACP pins.** It does not — it is the **pi**
154
+ pin's oracle and reads no ACP pin (`run.sh:1820-1919`). The ACP pins are owned by
155
+ `check-acp-sdk-surface`, which is the vitest contract `test/acp-sdk-surface.contract.test.ts`;
156
+ the `run.sh` name is a transition shim into it.
157
+
158
+ ### Added
159
+
160
+ - **`scripts/raw-acp-compaction-measure/`** — a raw measurement probe (not a gate, in no check
161
+ tier) that drives one live `/compact` turn on the pinned adapter and replays the captured
162
+ notifications through the production event mapper, so the vendor wire and our rendered notice sit
163
+ in one receipt.
164
+ - Four new qualification claims from #102, each with its exact-once replant:
165
+ `MANIFEST-SET-INTEGRITY-REFUSED` and `LANE-INVENTORY-DECLARED` (new `gate-qualification` lane,
166
+ replanting the 08-20 / 08-21 / 08-28 catches), `MUTANT-GATES-INSIDE-FULL-FLOOR` and
167
+ `CI-TAG-PUSH-NOT-REBUILT` (`release-gate` lane).
168
+
169
+ ### Notes
170
+
171
+ - **`allowBuilds: esbuild: false`.** chord's only dependency is esbuild, and its arrival made
172
+ `pnpm install` stop on `ERR_PNPM_IGNORED_BUILDS` until the key carried a decision. Denied like the
173
+ other two entries: nothing here executes esbuild.
174
+ - **`bridge-command-boot`'s three agy gates moved to the `run.sh` argv the other manifests use**,
175
+ merging a duplicate group (#102). Group count is unchanged at 52 — the merge removes one and the
176
+ new head gate adds one — so the saving is a 66s control pair traded for a 16s one, not fewer
177
+ groups.
178
+
179
+ ## 0.18.0 - 2026-09-04
180
+
181
+ ### Fixed
182
+
183
+ - **A Claude window that changes which session it serves no longer leaves an armed receiver behind
184
+ (#101).** One process serves one garden at a time but can change which: open a bare `claude` and
185
+ then `/resume` or `/clear` inside it, and a second `SessionStart` arrives under the same pid,
186
+ seconds later, naming a different session. The garden it stopped serving kept a receiver marker
187
+ naming a LIVE owner, so dispatch read an armed doorbell nobody held — a sibling's message was
188
+ reported as sent and then sat unread in that mailbox for over 50 minutes (oracle, 2026-09-04; that
189
+ unread letter is what opened the issue).
190
+ - **Deliverability now asks about *now*, not *ever*.** `watchArmed` was a copy of the identity
191
+ match; it is a measurement — the receiver owner's own sender marker
192
+ (`meta-senders/<backend>/<pid>.json`) must still name the same garden. Both consumers, the v2
193
+ production seam and `entwurf_self`, go through one shared composition, so a citizen's
194
+ self-report and dispatch cannot disagree.
195
+ - **The hook retires what it stopped serving** — the marker only, never the record (a record is a
196
+ citizen's identity, not sweepable state), only a marker its own pid owns, and only on an
197
+ arm-capable event. `UserPromptSubmit` cannot emit `watchPaths`, so retiring on a keystroke would
198
+ disarm the session the operator is sitting in with no way to re-arm. The vendor's `source` is
199
+ logged on every line and branched on nowhere: the switch is settled by what is on disk.
200
+ - **The join is scoped by the marker's `ownerKind`, not by backend** — today that is
201
+ `claude-code-cli`. A Copilot watch lives in a forked extension child with its own pid while its
202
+ sender marker carries the CLI's `process.ppid`, so applying the same check there would make
203
+ every Copilot citizen permanently `mailbox-undeliverable`. That is a scope decision with a
204
+ measured reason, not an omission.
205
+
206
+ ### Added
207
+
208
+ - **A rejected delivery now names which receiver axis failed (#101).** A bare `mailbox-undeliverable`
209
+ sent one sibling hunting for a live session it had read as dead. The rejection now says whether
210
+ the doorbell is not armed, the owner is not alive, or there is no record.
211
+ - **`entwurf_peers` separates a live citizen from a phantom (#101).** Every claude-code row reads
212
+ `liveness=unsupported`, so two rows were byte-identical whether or not anyone was home. Each row
213
+ now carries two observed facts — `receiver=` (doorbell state) and `transcript=` (whether a
214
+ conversation was ever written):
215
+
216
+ ```text
217
+ - 20260904T072015-e09b66 liveness=unsupported receiver=active transcript=exists
218
+ - 20260904T093135-ac7a1a liveness=unsupported receiver=inactive transcript=absent
219
+ ```
220
+
221
+ - **`check-meta-hook-session-switch` (28 assertions) and the first mutant lane the Claude hook has
222
+ ever had (#101).** The gate drives the shipped launcher twice under one fake owner pid and
223
+ requires that exactly one of the two gardens is deliverable — and that it is the one the operator
224
+ is sitting in. `scripts/mutants/meta-hook-session-switch.json` carries **17 claims**, 1:1 with the
225
+ gate's `[QK:…]` labels, each re-planting the defect this lane closed and requiring the gate to go
226
+ red at its claimed signature. Three of those six new mutants exist because cross-review found two
227
+ QK labels with no mutant behind them and three real weakenings walking straight through: the
228
+ join's `ownerKind` scope, the sender marker's start-key guard, and its backend equality.
229
+
230
+ ### Changed
231
+
232
+ - **One inherited sentence about the resume picker is retired, and it was retired by measurement
233
+ (#101).** The diagnosis said the picker fires `SessionStart` twice — a placeholder id, then the
234
+ picked one. Six LIVE cells on this host (Claude Code 2.1.260, hook log verbatim in
235
+ `scripts/raw-claude-session-switch/README.md`) show it fires **once**, carrying the real id,
236
+ whether the id comes from the picker or from argv. The two-`SessionStart` shape is a bare `claude`
237
+ followed by an in-session `/resume` or `/clear` — which is exactly what the field case did, four
238
+ seconds before its second envelope. The repair is unaffected (either way one pid stops serving one
239
+ garden), so only the prose moved, in four places. Compaction, manual and automatic, re-fires
240
+ `SessionStart` for the **same** native id and retires nothing; the same-garden rule already
241
+ covered it.
242
+ - **A `UserPromptSubmit` envelope is trusted for the receiver join, on a measured footing.** The
243
+ cross-review threat model ("a stale or out-of-order UPS") was withdrawn rather than defended: it
244
+ claimed the sender pointer is untrustworthy for the join while the same pointer stays authoritative
245
+ for sender identity — two incompatible readings of one file. Across the raw lab's four pids, every
246
+ `UserPromptSubmit` named the native id its own pid's preceding `SessionStart` had established,
247
+ **8 of 8**. The arm-capable restriction stays on its own footing.
248
+ - **Three LIVE smokes stopped depending on a fixture the new join reads as retired.** Five smokes
249
+ seeded a receiver marker with no sender marker beside it — three of them release MUSTs — so each
250
+ would have failed on its own fixture rather than on the rail it exists to prove. They now seed
251
+ both and sandbox the senders root, the shape `smoke-mux-lifecycle-live` and `smoke-omp-fresh-live`
252
+ already had. `smoke-entwurf-chain-live` additionally gives its terminus an owner **outside hop 1's
253
+ ancestry**: seeding it under the smoke's own pid put two garden citizens on one host process and
254
+ the bridge refused the hop outright with `ambiguous sender identity`.
255
+
256
+ ### Upgrade note
257
+
258
+ **Run `entwurf setup` once after upgrading — every rail, not just Claude.** This release changes
259
+ `pi-extensions/lib/meta-session.ts` and the Claude hook, and **four install paths deploy that
260
+ file** — `install-meta-bridge` (Claude), `install-omp-bridge` and `install-omp-receive` (OMP),
261
+ `install-copilot-bridge` (Copilot). Every one of them present on the host now carries a STALE
262
+ writer until it is re-installed, and its own doctor says so by name. Re-installing only the Claude
263
+ rail leaves the others stale, and that does not surface until a LIVE gate turns red — measured in
264
+ the 0.17.2 cut, where it blocked `smoke-omp-receive-live` and made the first `--cut` run BLOCKED.
265
+
266
+ `entwurf setup` is presence-driven and re-synthesizes exactly the units this host has, which is why
267
+ it is the upgrade command rather than any single `install-*`. An already-open Claude Code session
268
+ keeps the old manifest until it restarts — and, for this release specifically, an already-open
269
+ session also keeps the old hook, which is the code that retires a switched-away receiver.
270
+
271
+ ### Verification
272
+
273
+ All of the following ran on oracle (Linux, Claude Code 2.1.260, node 24.18.1, pi 0.84.4, omp 18.0.0).
274
+
275
+ - **`pnpm run check:full` — exit 0**, 440s on the prepared tree and 446s inside the release gate.
276
+ - **`LIVE=1 ./run.sh release-gate /tmp/entwurf-release-gate-0.18.0c.xZlKjZ --cut` — `cut: OK`,
277
+ exit 0.** **MUST PASS=23 FAIL=0 SKIP=0**, **BEHAVIOR PASS=1 FAIL=0 SKIP=0**. Run 18:57→19:48 KST
278
+ on `faee8f6`, with `env -u CLAUDE_CONFIG_DIR -u PI_SESSION_ID -u PI_AGENT_ID`. Log:
279
+ `/tmp/entwurf-release-gate-0.18.0c.xZlKjZ/release-gate.log`. It carried `check:full` and
280
+ `check-gate-qualification` (**364/364 KILLED**, up from 347 — the 17 new claims are this lane's
281
+ `MHSS-*`) as MUST steps.
282
+ - **This lane's own gate: `check-meta-hook-session-switch` — 28 assertions passed**, and all 17
283
+ `MHSS-*` mutants killed at their claimed signatures inside that qualification run.
284
+ - **The three LIVE smokes whose fixtures this lane changed are green**, including
285
+ `smoke-entwurf-chain-live` — **24 assertions**, four citizens across three harnesses, and its new
286
+ pre-flight cell `fixture: the terminus is a deliverable citizen at the moment the chain starts`
287
+ passed before the chain ran.
288
+ - **One unattributed `smoke-entwurf-chain-live` failure is on the record and is NOT closed.** During
289
+ the lane (2026-09-04 16:58 KST) one run rejected at hop 3 with `mailbox-undeliverable (observed
290
+ liveness: unsupported)`; the runs before and after it passed and it has not reproduced. A
291
+ start-key race and an early idle-owner exit were excluded by measurement; a memory-pressure
292
+ hypothesis is neither confirmed nor refuted (no OOM or kill entries in the host journal for that
293
+ window). The instrument that will split the next occurrence shipped in this release — the fixture
294
+ pre-flight assertion above, plus a `terminus fixture at timeout:` line naming owner liveness and
295
+ both receiver facts.
296
+ - **The first `--cut` attempt of this cut was BLOCKED, and the cause was operator error, not the
297
+ product.** `check-gate-qualification` aborted with `origin HEAD changed during qualification`
298
+ because a commit was created while the gate was running. Every other MUST step in that run passed
299
+ (`MUST PASS=22 FAIL=1 SKIP=0`). The run above is the re-measurement on a frozen HEAD. One LIVE
300
+ gate run before that was killed by the host harness's low-memory watchdog rather than by any
301
+ assertion, and was re-run under tmux.
302
+ - **Exact-SHA CI on the pre-version HEAD `e56eee0`** — `check`, `install-surface`,
303
+ `artifact-consumer` all `success`.
304
+ Run: https://github.com/junghan0611/entwurf/actions/runs/33853363923
305
+
7
306
  ## 0.17.2 - 2026-09-03
8
307
 
9
308
  ### Added
package/CONTRIBUTING.md CHANGED
@@ -30,7 +30,7 @@ pnpm check # everyday core (prints wall time; <=60s on the referenc
30
30
  pnpm run check:full # full deterministic floor — the required PR gate
31
31
  ```
32
32
 
33
- The deterministic floor is tiered (#70). `pnpm check` is the everyday core (biome, tsc, the vitest lanes, and the fast contract gates); `pnpm run check:full` adds the hermetic-integration and package/install tiers — all but the separately scheduled `check-gate-qualification`, which CI runs on every push and a gate-changing PR must run once itself. Exact membership is the named `check:*` scripts in `package.json`. Run `check:full` once on your frozen commit candidate — the pre-commit hook (`.husky/pre-commit`) carries only fast static checks (whitespace, lint, typecheck), not the full floor, so a green `pnpm run check:full` before commit is the evidence that your change holds (scheduling contract: AGENTS.md "Verification scheduling").
33
+ The deterministic floor is tiered (#70). `pnpm check` is the everyday core (biome, tsc, the vitest lanes, and the fast contract gates); `pnpm run check:full` adds the hermetic-integration and package/install tiers — including `check-gate-manifests`, the qualification HEAD (runner self-test, manifest-set validation, declared lane inventory, zero mutants executed, ~8s), but not the separately scheduled mutant-EXECUTING body `check-gate-qualification`, which CI runs on every branch push and a gate-changing PR must run once itself. Exact membership is the named `check:*` scripts in `package.json`. Run `check:full` once on your frozen commit candidate — the pre-commit hook (`.husky/pre-commit`) carries only fast static checks (whitespace, lint, typecheck), not the full floor, so a green `pnpm run check:full` before commit is the evidence that your change holds (scheduling contract: AGENTS.md "Verification scheduling").
34
34
 
35
35
  For changes that touch backend launch, session lifecycle, or `_meta` shape, also run
36
36
  the live ACP smokes that cover the touched rail — at minimum:
package/README.md CHANGED
@@ -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.4 <0.85`), Claude Code, agy, and the Copilot CLI
160
+ already installed — a compatible `pi` (`>=0.85.1 <0.86`), 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.4 <0.85`). Then point pi at
191
+ separately (`@earendil-works/pi-coding-agent >=0.85.1 <0.86`). Then point pi at
192
192
  the npm-installed package or development clone:
193
193
 
194
194
  ```bash
@@ -433,6 +433,7 @@ pnpm run check:full # full deterministic floor (adds the her
433
433
  # source-maintainer only — qualification snapshots the git work surface, and both
434
434
  # commands are source-contract gates rather than installed operator checks:
435
435
  ./run.sh check-agy-permission-matrix # AGY permission contract space as a literal table (declared cells + stated exclusions)
436
+ ./run.sh check-gate-manifests # the qualification HEAD alone: self-test + manifest-set validation + declared lane inventory, zero mutants run (in check:hermetic)
436
437
  ./run.sh check-gate-qualification # kill-proof: committed defect mutants must turn their gates red for the claimed reason
437
438
 
438
439
  # agy LIVE acceptance — requires an already-running conversation:
@@ -450,9 +451,12 @@ LIVE=1 ./run.sh release-gate /tmp/scratch --cut # the single cut gate (MUST + BE
450
451
  LIVE=1 ENTWURF_ACP_CORTEX_CONNECTION=<conn> ./run.sh smoke-acp-cortex-live # Cortex is on-demand: the aggregate does not re-certify it
451
452
  ```
452
453
 
453
- `pnpm run check:full` includes the AGY permission contract matrix; the committed-mutant
454
- gate qualification is scheduled separately (`./run.sh check-gate-qualification` — the CI
455
- `check` job runs it on every push, and release-gate carries it as a MUST step). A gate a
454
+ `pnpm run check:full` includes the AGY permission contract matrix and the qualification
455
+ HEAD (`check-gate-manifests` — runner self-test, manifest-set validation, declared lane
456
+ inventory, zero mutants executed); the committed-mutant EXECUTION is scheduled separately
457
+ (`./run.sh check-gate-qualification` — the CI `check` job runs it on every branch push,
458
+ and release-gate carries it as a MUST step; a tag push runs no CI, since the same SHA's
459
+ branch run already carries every job). A gate a
456
460
  release touches must kill its known defect for the claimed `[QK:<claim>]` reason —
457
461
  the descriptions above name what each smoke covers, and no check count is quality
458
462
  evidence on its own. Gate qualification needs the git work surface, while the matrix
package/VERIFY.md CHANGED
@@ -40,8 +40,8 @@ Verification here is not a benchmark. In production we exchange short turns and
40
40
 
41
41
  ### The canonical floor — two entry points
42
42
 
43
- - **Deterministic floor — two tiers (#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 frozen-candidate protocol, push CI, release-gate, and `prepublishOnly` run. Exact membership is the named `check:*` group scripts in `package.json` (the executable SSOT — this document records meaning and principles, not the command list); a gate changes tier by semantic-class decision, never by getting faster or slower. Both tiers exclude the separately scheduled `check-gate-qualification`.
44
- - **Discriminating power of that floor:** `./run.sh check-gate-qualification` (scheduled, not ambient: standalone when a lane changes a gate/mutant/matrix, in the CI `check` job on every push, and as a release-gate MUST step — no longer inside the default check chains) re-plants committed defect mutants (`scripts/mutants/*.json`, one per closed defect class) in an isolated snapshot repo and requires each to turn its gate red **bounded and at its claimed `[QK:<claim>]` signature** — a wrong-reason red fails, a baseline-red control voids the whole group, and the runner is negative-controlled on every run (zero-match/multi-match/survived/wrong-reason/hang/control-red/impurity). This measures whether the deterministic gates still *block* what they claim to block; it is **not a new evidence level** (L0–L5 are untouched) and never substitutes for LIVE evidence. Per-cut records cite claim IDs + killed mutant IDs — "N checks passed" alone is not evidence. `check-agy-permission-matrix` complements it with the enumerated permission contract space (literal cells + stated exclusion rules, oracle independent of the SUT).
43
+ - **Deterministic floor — two tiers (#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 frozen-candidate protocol, push CI, release-gate, and `prepublishOnly` run. Exact membership is the named `check:*` group scripts in `package.json` (the executable SSOT — this document records meaning and principles, not the command list); a gate changes tier by semantic-class decision, never by getting faster or slower. The FULL tier carries the qualification HEAD (`check-gate-manifests`, through `check:hermetic`); the everyday core does not, and neither tier carries the separately scheduled mutant-executing body (`check-gate-qualification`).
44
+ - **Discriminating power of that floor:** `./run.sh check-gate-qualification` (scheduled, not ambient: standalone when a lane changes a gate/mutant/matrix, in the CI `check` job on every branch push, and as a release-gate MUST step — no longer inside the default check chains; a semver TAG push triggers no CI at all, because the same SHA's branch run already carries every job and is the exact-SHA evidence a release quotes) re-plants committed defect mutants (`scripts/mutants/*.json`, one per closed defect class) in an isolated snapshot repo and requires each to turn its gate red **bounded and at its claimed `[QK:<claim>]` signature** — a wrong-reason red fails, a baseline-red control voids the whole group, and the runner is negative-controlled on every run (zero-match/multi-match/survived/wrong-reason/hang/control-red/impurity). This measures whether the deterministic gates still *block* what they claim to block; it is **not a new evidence level** (L0–L5 are untouched) and never substitutes for LIVE evidence. Per-cut records cite claim IDs + killed mutant IDs — "N checks passed" alone is not evidence. `check-agy-permission-matrix` complements it with the enumerated permission contract space (literal cells + stated exclusion rules, oracle independent of the SUT). The gate's HEAD is separable and IS in the floor: `check-gate-manifests` runs the runner self-test, validates the committed manifest set against the origin index, and asserts the declared lane inventory, while executing zero mutants and never snapshotting the repo — three of the five reds qualification has ever produced in CI died in that head, in under five seconds.
45
45
  - **Live floor:** `LIVE=1 ./run.sh release-gate <scratch-project-dir> --cut` — `pnpm run check:full` + the v2-native live gates + the ACP plugin acceptance floor. It reports a **two-tier summary**:
46
46
  - **MUST tier** (release-blocking — owns the exit code; "green" applies only here): `pnpm run check:full`, `check-gate-qualification`, `smoke-entwurf-v2-matrix-live`, `check-bridge`, `doctor-pi-provider` (#81 — `check-bridge` proves the launcher this checkout SHIPS; this proves the invocation the operator's pi provider actually EXECS, by booting it and requiring the entwurf verb set back, because `command -v` answers yes for a command that exits 127; wired as a step on 2026-08-19 after a relocated pnpm cmd-shim cost a cut sixteen LIVE steps before the same fact surfaced at `smoke-acp-bundled-mcp-live`), the resident-garden-guard zero-token half (record birth / record-keyed socket / attach-on-reopen), the `smoke-acp-*-live` ACP plugin smokes (socket-citizen / raw-turn / overlay / provider / session-reuse / carrier-augment / memory-containment / rgg / mcp / skill / bundled-mcp / v2-send), the axis wired in on 2026-07-31 that the aggregate had simply never listed (`smoke-claude-native-resume-live`), `smoke-entwurf-chain-live` — the cross-harness delivery chain (native Claude Code → pi GPT → pi ACP Sonnet → mailbox terminus) proving sender identity and replyability at every hop plus a real read receipt at the end — the integrated `smoke-mux-lifecycle-live` (its own note below), and the two OMP steps 0.16.0 wired: `smoke-omp-receive-live` (the addressed roundtrip into a live omp citizen; it reads the capability registry and decides its own outcome rather than passing by default) and `smoke-omp-fresh-live` (the clause 7 receipt the release stop now requires). (`smoke-session-id-name` is gone — #50 C3: its `--session-id`/`--name` substrate has no entwurf consumer anymore.)
47
47
  - **BEHAVIOR tier** (advisory, non-blocking): the resident-garden-guard positive (a model-in-loop `entwurf_self` turn). A BEHAVIOR FAIL is surfaced with its artifact path but **never blocks the cut**. The lane holds what the model *chooses*, never what our wiring fails to deliver — a gate that TELLS the model which tool to call stays MUST, because its failure is ours — measured 2026-07-24, when the tool turned out to be absent from the session schema in both observed failures (the bundled-MCP readiness gap recorded in `scripts/smoke-acp-v2-send-live.ts`).
@@ -137,7 +137,7 @@ The goal is not merely "invoke Claude Code." We want:
137
137
  **One install command to remember: `./run.sh setup <project>`.** It is idempotent — re-run the exact same command whenever anything looks wrong. There is no second install surface to juggle: from a clone `setup` runs the whole floor in order.
138
138
 
139
139
  1. `pnpm install` — installs the pinned development dependencies and builds the bridge (source-checkout bootstrap only; an installed package never runs npm/pnpm inside `node_modules`)
140
- 2. pi wiring → `<project>/.pi/settings.json` + user-scope registration — only when a `pi` inside the supported range (`>=0.84.3 <0.85`) is on PATH; absent pi is an explicit zero-state SKIP, a below-floor pi is a detected FAIL. The user-scope entry is owner-recorded (#86 C2): another root's live-or-missing ownership makes this step a zero-write refusal (setup: pi FAIL) that names `takeover-user-scope`; the install-states bind the exact managed settings path (a drifted/symlinked/corrupt target is a zero-write refusal) and the inverse removes only the recorded owner's exact entry; `doctor-pi-package` reports the verdict
140
+ 2. pi wiring → `<project>/.pi/settings.json` + user-scope registration — only when a `pi` inside the supported range (`>=0.85.1 <0.86`) is on PATH; absent pi is an explicit zero-state SKIP, a below-floor pi is a detected FAIL. The user-scope entry is owner-recorded (#86 C2): another root's live-or-missing ownership makes this step a zero-write refusal (setup: pi FAIL) that names `takeover-user-scope`; the install-states bind the exact managed settings path (a drifted/symlinked/corrupt target is a zero-write refusal) and the inverse removes only the recorded owner's exact entry; `doctor-pi-package` reports the verdict
141
141
  3. Claude meta-bridge global plugin — only when `claude` is on PATH; otherwise skipped cleanly
142
142
  4. source stable-bin exposure — including certified `entwurf` → this checkout's `run.sh`, the managed runtime Copilot fresh resolves; helper units are attempted independently and a foreign helper is a named FAIL
143
143
  5. agy bridge + exact permission + statusline + `PreInvocation` hook — only when `agy` is on PATH; each adapter is idempotent and independently doctorable
package/demo/README.md CHANGED
@@ -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.4)
142
+ - `pi` on PATH (current floor 0.85.1)
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)
@@ -93,9 +93,9 @@ undifferentiated "supported" column is what let a Claude PASS read as if it also
93
93
  | Surface | Declaration | Class | What a green actually says |
94
94
  |---|---|---|---|
95
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 |
96
+ | pi runtime | devDep exact `0.85.1`, peer `>=0.85.1 <0.86` | **exact** oracle + **closed range** | built and certified against 0.85.1; hosts inside the range are accepted, and the ceiling moves only on measurement |
97
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 |
98
+ | Claude ACP adapter | `@agentclientprotocol/claude-agent-acp 0.75.1` | **exact**, bundled | the adapter we ship and certify; resolved before any PATH fallback |
99
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 |
@@ -144,9 +144,19 @@ different reasons, and collapsing them would hide a real risk**:
144
144
  - **Advertised but never called.** Some surfaces carry no capability prerequisite at all — the
145
145
  `providers/list` / `providers/set` / `providers/disable` trio added in 0.70.0 is advertised
146
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
147
+ forks, AI-generated session titles and permission-mode kinds on the same footing. 0.74.0–0.75.1
148
+ extend the same list: the `authStatus` extension (0.75.0, #1080), Markdown-rendered usage
149
+ statistics (0.75.0, #1085) and restored session forks (0.75.1, #1089). The `--hide-claude-auth`
150
+ subscription refusal (0.74.0, #1079) is unreachable for a second reason — `[측정 2026-09-06]`
151
+ entwurf passes that flag nowhere (repo grep, 0 hits). They are
148
152
  unreachable only because the common loop never invokes them (nor `logout`). Nothing upstream
149
153
  enforces that; it is our own call-site discipline, and it stops holding the moment we use one.
154
+ - **The one 0.73.0 → 0.75.1 change that DOES reach us:** context compaction is now surfaced as a
155
+ synthetic ACP tool lifecycle (0.75.0, #991) — a `tool_call` with `kind: "think"`, title
156
+ `Compact conversation`, and `_meta.contextCompaction` schema v1 — where it used to arrive as
157
+ assistant text. Our mapper routes every `tool_call`/`tool_call_update` through
158
+ `renderToolUpdate` (`event-mapper.ts`), so this is not a type break; what changes is what an
159
+ operator SEES in a compacting turn. See §11-8 for the measurement.
150
160
 
151
161
  Adopting either class requires a separate observed need plus a complete rendering/lifecycle/evidence
152
162
  contract. An optional upstream feature is not a core-value gap.
@@ -257,17 +267,19 @@ caller-session `_meta`, and cross-machine certification.
257
267
 
258
268
  A backend can return `newSession` before its declared MCP server is callable. This was
259
269
  observed intermittently on the Claude rail and directly on Cortex's private `mcp.json`
260
- path. Neither `claude-agent-acp` 0.73.0 nor the Cortex landing adds a client-side
270
+ path. Neither `claude-agent-acp` 0.75.1 nor the Cortex landing adds a client-side
261
271
  readiness fence over a session's declared MCP servers, and entwurf's common loop
262
272
  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:
273
+ (Re-measured at the 0.73.0 → 0.75.1 bump, not inherited from the previous one — and the
274
+ 0.70.0 0.73.0 argument is not reused either. `mcpServerStatus` call sites in
275
+ `src/acp-agent.ts` are **2 at both v0.73.0 and v0.75.1** `[측정 2026-09-06, git grep -c]`;
276
+ they first appeared in 0.71.0 via `0cbbaf3` (MCP OAuth, LLM-25012), so the ADAPTER calls it
277
+ where it once did not. Both were re-read at `v0.75.1 src/acp-agent.ts:1736` and `:1829`
278
+ (the 0.73.0 coordinates were `:1618` and `:1711`): the first sits inside
279
+ `authenticateMcpServers` behind `supportsMcpOAuth(query)` and skips every status that is not
280
+ `needs-auth`; the second polls a SINGLE named server to `connected` under an OAuth deadline.
281
+ Neither waits on every declared server before `newSession` returns. That is an auth
282
+ handshake, not a readiness fence, so the boundary below is unchanged. The other reachable-surface findings also re-measured:
271
283
  AIR typed failures and the AIR file-change report stay capability-gated and
272
284
  unadvertised by entwurf; `providers/set` / `providers/disable` stay advertised
273
285
  unconditionally and uncalled; native subagents, async tasks, session forks, session
@@ -338,6 +350,28 @@ a cut that changes or ships the Cortex rail must run and read its dedicated smok
338
350
  Per-cut counts, digests, versions, and host observations belong in BASELINE/CHANGELOG,
339
351
  not this standing contract.
340
352
 
353
+ ## 11-8. Compaction is a tool lifecycle (0.75.0 onward)
354
+
355
+ The one change in the `0.73.0 → 0.75.1` bump that REACHES the common loop. Upstream #991
356
+ (`f74a517`) replaced compaction's assistant text with a synthetic ACP tool call: `kind:
357
+ "think"`, title `Compact conversation`, `_meta.contextCompaction` schema v1.
358
+
359
+ `[측정 2026-09-06, oracle, adapter 0.75.1, claude-sonnet-5]` one live `/compact` turn emitted
360
+ exactly two notifications (`tool_call` in-progress → `tool_call_update`), and those verbatim
361
+ objects replayed through the production `applyAcpSessionUpdate` produced a
362
+ `[tool:start] Compact conversation` / `[tool:…] Compact conversation` notice pair. No new
363
+ mapper branch is needed — `renderToolUpdate` routes every tool call regardless of `kind` —
364
+ and `_meta.contextCompaction` is dropped by the mapper, so no accounting path sees it.
365
+
366
+ What changed is therefore what an OPERATOR sees in a compacting turn, not what entwurf
367
+ computes. The post-compaction occupancy refresh is NOT new: `v0.73.0 src/acp-agent.ts:3460`
368
+ already emitted a `usage_update` at `compact_boundary` and 0.75.1 still does
369
+ (`dist/acp-agent.js:2740-2752`, now reading `compact_metadata.post_tokens` instead of a
370
+ `getContextUsage` control request), so the shrinking-`used_end` case that
371
+ `backend.ts:1286-1288` names as #96's weak floor gains no new trigger here.
372
+
373
+ Receipt, limits and the `completed`-branch gap: `scripts/raw-acp-compaction-measure/README.md`.
374
+
341
375
  ## Open work
342
376
 
343
377
  - causal MCP-readiness diagnosis and, only with proof, a backend-invariant fence;