@junghanacs/entwurf 0.30.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +229 -0
  3. package/DELIVERY.md +1 -0
  4. package/README.md +49 -4
  5. package/VERIFY.md +23 -4
  6. package/demo/README.md +1 -1
  7. package/docs/acp-backend-rail.md +111 -18
  8. package/docs/setup-clean-host.md +104 -4
  9. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +7 -7
  10. package/mcp/entwurf-bridge/dist/pi-extensions/entwurf-capabilities.json +1 -0
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +43 -0
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-deliverability.js +7 -1
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +34 -3
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +7 -1
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +28 -2
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +30 -2
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +92 -0
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js +288 -0
  19. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +744 -0
  20. package/mcp/entwurf-bridge/src/index.ts +7 -7
  21. package/mcp/entwurf-bridge/tsconfig.build.json +8 -1
  22. package/package.json +15 -12
  23. package/pi/entwurf-capabilities.json +1 -0
  24. package/pi/pi-durable/bootstrap.mjs +281 -0
  25. package/pi/pi-durable/overlay/runtime-contacts.patch +91 -0
  26. package/pi/pi-durable/overlay/upstream-pin.json +16 -0
  27. package/pi-extensions/lib/acp/backend-adapter.ts +68 -0
  28. package/pi-extensions/lib/acp/backend.ts +41 -5
  29. package/pi-extensions/lib/entwurf-deliverability.ts +7 -1
  30. package/pi-extensions/lib/fresh-call-composition.ts +42 -7
  31. package/pi-extensions/lib/meta-sender-identity.ts +7 -1
  32. package/pi-extensions/lib/meta-session.ts +28 -2
  33. package/pi-extensions/lib/mux-fresh-call.ts +34 -2
  34. package/pi-extensions/lib/pi-durable-fresh-preflight.ts +113 -0
  35. package/pi-extensions/lib/pi-durable-runtime.ts +384 -0
  36. package/pi-extensions/meta-bridge-pi-durable.ts +1020 -0
  37. package/run.sh +214 -36
  38. package/scripts/check-fresh-call-dispatch.ts +18 -0
  39. package/scripts/check-gate-qualification.ts +4 -2
  40. package/scripts/check-herdr-plugin-build.ts +159 -57
  41. package/scripts/check-pi-durable-contact.ts +834 -0
  42. package/scripts/check-pi-durable-receive.ts +1116 -0
  43. package/scripts/check-pi-durable-send.ts +1661 -0
  44. package/scripts/fixtures/herdr-supply.json +8 -8
  45. package/scripts/lib/pi-durable-fresh-live-receipts.ts +139 -0
  46. package/scripts/lib/pi-durable-r1.ts +100 -0
  47. package/scripts/lib/pi-durable-reap.ts +27 -0
  48. package/scripts/lib/pi-durable-receive.ts +137 -0
  49. package/scripts/mutants/acp-prompt-lifecycle.json +2 -2
  50. package/scripts/mutants/acp-vendor-rejection.json +62 -0
  51. package/scripts/mutants/control-send-receipt.json +1 -1
  52. package/scripts/mutants/copilot-birth.json +2 -2
  53. package/scripts/mutants/herdr-plugin-build.json +11 -0
  54. package/scripts/mutants/herdr-supply.json +3 -3
  55. package/scripts/mutants/meta-hook-session-switch.json +4 -2
  56. package/scripts/mutants/mux-fresh-call.json +69 -3
  57. package/scripts/mutants/omp-birth.json +2 -2
  58. package/scripts/mutants/omp-fresh.json +6 -4
  59. package/scripts/mutants/pack-install.json +3 -3
  60. package/scripts/mutants/pi-durable.json +390 -0
  61. package/scripts/mutants/pi-floor.json +2 -2
  62. package/scripts/pi-durable-contact-host.mjs +105 -0
  63. package/scripts/pi-durable-receive-host.mjs +127 -0
  64. package/scripts/pi-durable-send-host.mjs +213 -0
  65. package/scripts/smoke-acp-raw-turn-live.ts +1 -1
  66. package/scripts/smoke-herdr-fresh-call-live.ts +21 -7
  67. package/scripts/smoke-pi-durable-fresh-live.ts +624 -0
  68. package/scripts/smoke-setup-verdict.sh +21 -2
  69. package/scripts/tsconfig.json +1 -0
package/run.sh CHANGED
@@ -132,6 +132,10 @@ Usage:
132
132
  ./run.sh check-mailbox-receipt-state # deterministic gate (0.11 Stage 0 step 3B): mailbox receipt state schema + store (stamp→persist→read-back) in a temp mailbox, strict keyset, no API
133
133
  ./run.sh check-copilot-birth-hook # #82 gate: drives the real Copilot assembler into a temp dir, fires the baked launcher with NO ARGV (the way Copilot's `exec`-string schema forces), and requires a backend:"copilot" v3 record + attach + peer row + a SENDER marker the resolver joins back to that record, and still zero mailbox/receiver marker (who-sent needs a shared parent; a receiver needs a doorbell this backend has not got). Hermetic; no Copilot, no model turn
134
134
  ./run.sh check-omp-birth-hook # #87 gate: drives the real OMP assembler into a temp dir, imports the ASSEMBLED index.ts into a MOCK omp host and fires session_start/session_switch. tui mints one backend:"omp" record + a sender marker keyed to the host's OWN pid (the one-process join) + the garden id on the status line; print/rpc/json mint NOTHING (hasUI is true on the rpc rows, as in the vendor); switch attaches on the same native id and mints the replacement on a new one; the CERTIFIED receiver reader still finds no marker. Also the four-root joint binding: extension and omp-labeled bridge child resolve the same sessions/mailbox/senders/receivers bundle against test-built literals (never the production resolver as its own oracle), under a poisoned PI_CODING_AGENT_DIR and under four distinct ENTWURF_META_* overrides; the override grammar is absolute-or-~ and both halves plus the doctor refuse anything else by name; and a drifted provenance label selects no root policy at all. Hermetic; no omp, no model turn
135
+ ./run.sh check-pi-durable-contact # #129 L-identity gate, CHECKOUT-ONLY (an installed package refuses it in run_ts). Opens the REAL durable app from a provisioned overlay checkout (ENTWURF_PI_DURABLE_RUNTIME: tracked tree = pin + patch exactly, git-ignored model data = the pin's recorded digest) with the pi-durable contact, births the citizen and runs its tools adapter-direct through a privately emitted branch bridge (ENTWURF_PI_DURABLE_BRIDGE_ENTRY; its registry must match this checkout). Matrix roots {default, explicit} x {fresh, --continue}: one record keyed by the durable session dir, sender marker on the spawned host pid, exactly one bridge child whose /proc PPID is that host, poisoned identity carriers kept from it, its four root carriers bound to the roots written, the receiver ARMED (raw marker owned by the host pid + start key as pi-durable-host, joined to the sender marker; removed on close), self = the record and replyable, peers from an outside bridge, same garden id on reopen; the bundle's compiled owner join must admit pi-durable-host. Reaps only the host/bridges it owns on any failure. Harness-bypassed: no model turn, no ToolTask. Missing inputs → SKIP (97), never a pass
136
+ ./run.sh check-pi-durable-send # #129 L-send FIRST CELL, CHECKOUT-ONLY. Same two inputs as check-pi-durable-contact. One native durable turn: the app's own controller input → GenerationTask → ToolTask → the contact's entwurf_v2 → the private bridge → a seeded self-fetch target (C2 recipe, owned by a separate idle child). S = a 127.0.0.1 scripted OpenAI-compatible endpoint configured through the app's documented models.json (dummy key, PI_OFFLINE); H = the native ToolTask path; V (a vendor turn) is NOT run. Oracles: the wire (tools offered, v2 schema = the bridge's tools/list, exactly two requests, the tool result + receipt in the second), the raw mailbox (.msg 1, signal, sender = the host record), the native view (one call, its settled result, root current). Not exactly-once, not recovery, not a recipient turn. Missing inputs → SKIP (97)
137
+ ./run.sh check-pi-durable-beside # #129 mutant execution coordinate (VERIFY rule 3): run_vitest over the four pi-durable beside tests (adapter, runtime, preflight, owner join) — the pi-durable qualification lane's gate argv. Same files check-tests-beside-behavior already runs.
138
+ ./run.sh check-pi-durable-receive # #129 L-receive FIRST CELL, CHECKOUT-ONLY. Same two inputs (the bundle must carry the pi-durable-host owner join). The host driver gives the app NO input; a seeded claude-code sender (marker owned by a separate idle child, handed to a gate-owned sending bridge through ENTWURF_META_SENDER_MARKER) enqueues one message, the host's doorbell admits its notice to the ROOT conversation, and the scripted model drains it with entwurf_inbox_read. S withholds response #1 until the ENQUEUE receipt, the .delivered bytes, the signal, the host-owned receiver marker and the notice are captured. Oracles: receiver() admission, native view, raw archive (.delivered.read + lastReadAt), wire (exactly two requests, drain result = view), and after a green reap a forensic read-only SQLite copy (submission row by request id = reported id, root, input, done, placed entry = the notice). V NOT run. Missing inputs → SKIP (97)
135
139
  ./run.sh check-copilot-receive-arm # #82 RAIL 5 gate: the REAL receiver installer + the REAL extension.mjs forked with a stubbed SDK. Arms only after birth, marker owned by the WATCHER pid, self-fetch dispatch answer, doorbell carries the garden id and NOT the body, id-drift/foreign-parent refusals. Hermetic; no Copilot, no model turn
136
140
  ./run.sh check-copilot-launch # #82 RAIL 7 gate: the MANAGED launch `entwurf copilot`, driven through its public address against a FAKE VENDOR on a sandbox PATH. Receiver precondition refusals, EXTENSIONS token + operator token preservation, injected defaults before the `--` terminator, byte-identical argv, the 11 explicit permission/surface policy overrides that suppress `--yolo`, exec (not fork) pid identity, exit passthrough, recursion refusal. Hermetic; no Copilot, no model turn
137
141
  ./run.sh check-codex-app-server-launch # #95 gate: the MANAGED app-server launch `entwurf codex-app-server`, driven through its public address against a FAKE VENDOR on a sandbox PATH. The injected `app-server --listen unix://<socket>` required to equal the product's own resolveCodexDefaultSocketPath over an env matrix that includes the inputs a bash transcription was MEASURED to diverge on (BOM-only CODEX_HOME, trailing slash, `..`), non-absolute and control-character addresses refused before any write, byte-identical operator argv, exec (not fork) parent identity, exit passthrough, live-socket and indeterminate-socket and second---listen and recursion refusals, dead-socket launch-over, the tmux fact line, and both identity carriers stripped. Hermetic; no Codex, no app-server, no model turn
@@ -199,6 +203,7 @@ Usage:
199
203
  ./run.sh smoke-setup-verdict # #86 C1+C3b aggregate setup verdict fixture: all-absent SKIP/green, pi presence+floor, detected-FAIL nonzero, copilot four-unit composition (present=independent PASS/FAIL rows, absent=zero-state SKIP), installed-mode named branch, credential store untouched. Offline/deterministic
200
204
  ./run.sh check-agy-permission-matrix # AGY permission CONTRACT SPACE as a literal table (55 cells): parser-state × operation × settings × ownership × precedence with stated exclusion rules; expectations are hand-written literals, never read from the SUT. Offline/deterministic (deps: python3)
201
205
  ./run.sh smoke-omp-fresh-live # #87 bundle C acceptance, RELEASE MUST: omp opened as ONE visible fresh sibling through the PUBLIC entwurf_fresh_call surface (never raw tmux), exact nonce callback -> garden id from the SENDER ENVELOPE -> record/backend identity -> addressed receive -> lastReadAt -> the drain visible in that same native session's own transcript. Needs LIVE=1 and spends real model turns. Self-deciding: omp outside FRESH_CALL_BACKENDS or without a mailbox rail is a protocol SKIP, which --cut reads as RED
206
+ ./run.sh smoke-pi-durable-fresh-live # #129 first-admission acceptance, RELEASE MUST: real Pi opens pi-durable through entwurf_fresh_call (managed verb), exact-nonce callback into Pi, Pi→pd addressed receive drained by pd's live turn, pd→Pi dispatch relayed to the collector; pd's actual model + task-wide offering read from its native store (online sqlite backup). LIVE=1; models ENTWURF_PI_DURABLE_FRESH_MODEL / _PI_MODEL (default openai-codex/gpt-6.1-sol)
202
207
  ./run.sh smoke-omp-receive-live # #87 bundle B acceptance: another harness wakes an ALREADY-OPEN omp citizen, which reads its inbox and answers in the same session. Decides its own outcome from the capability registry — omp without a drainable mailbox is a protocol SKIP (no receiver unit exists to arm), which an unattended release-gate reports and `--cut` reads as RED; a registry that claims a receive rail with no acceptance body here is a FAIL
203
208
  ./run.sh smoke-entwurf-chain-live # LIVE cross-harness delivery chain: native Claude Code -> pi GPT -> pi ACP Sonnet -> mailbox terminus, proving sender identity/replyable at every hop and a real read receipt at the end. Prerequisites (claude on PATH, pi credentials per backend) report protocol SKIP, never a pass
204
209
  ./run.sh check-release-gate-outcomes # release-gate STEP OUTCOME protocol (P1): one skip exit code shared by the shell + TS halves, classifier never rounds a skip up to a pass, `--cut` refuses a MUST SKIP while a bare diagnostic stays exit 0, no LIVE smoke keeps the old exit-0 skip shape, and both real skip surfaces are INVOKED and observed to propagate the code
@@ -221,6 +226,7 @@ Usage:
221
226
  ./run.sh uninstall-meta-bridge # 1.0.0 meta-bridge Phase 2: stateful GLOBAL uninstall (restore only keys/items captured in install-state)
222
227
  ./run.sh doctor-meta-bridge # THE RELEASE ORACLE (#51, Linux-certified repair axis). exit 0 = every required layer was MEASURED on this Linux host: toolchain + state + plugin/MCP + resolved-artifact launch-form classification (all 3 owner hooks + doorbell static contract) + synthetic owner join + store scan + hook errors + SessionStart evidence + REQUIRED live MCP↔marker join + writer-version parity. Missing live evidence is NOT CERTIFIED (open a Claude session and re-run), never a pass; Darwin is NOT CERTIFIED and stays nonzero, on ONE narrowed reason — start-key and ppid are portable there (`ps` fallback, same string the TS core mints), so what holds the lane closed is per-process environment DISCOVERY alone, pending physical-host evidence. Detection power is held by check-meta-doctor-oracle
223
228
  ./run.sh copilot [args...] # #82 RAIL 7: the MANAGED Copilot launch. exec()s the vendor CLI in THIS terminal (cwd/pid/exit preserved) with COPILOT_CLI_ENABLED_FEATURE_FLAGS=EXTENSIONS set for that one invocation — entwurf owns no part of your shell and writes nothing to it, but it owns the process it is about to become. Without that flag Copilot skips the extension scan SILENTLY. Refuses to launch unless the receiver unit is actually installed. Injects `--model auto` when no --model was given and `--yolo` when no explicit permission/surface policy flag was given, both BEFORE any `--`; every argument you pass is forwarded byte-identical. RUNNING THIS IS YOUR CONSENT to that profile — use plain `copilot` for stock vendor behaviour. Not tmux, not fresh-call, mints no citizen (birth is still the first prompt)
229
+ ./run.sh pi-durable [args...] # #129: the MANAGED durable launch. Locates the operator-provided durable runtime at its ONE fixed place ($XDG_DATA_HOME/entwurf/pi-durable/runtime, else ~/.local/share/...), refuses by name unless it is the packaged pin (HEAD, exact overlay patch, no untracked file, pinned model data), then exec()s node under THAT runtime's source resolver with the packaged bootstrap (pi/pi-durable/bootstrap.mjs) in this terminal. A new session needs --provider <p> --model <exact catalog id> --width task-wide [--entwurf-bootstrap <{v:2,task}>]; --continue takes none of them. Entwurf never installs, repairs or moves the runtime
224
230
  ./run.sh install-copilot-bridge # #82: GLOBAL install of the Copilot BIRTH plugin (own marketplace root; node+entry baked into the no-argv exec string). MCP wiring and the RECEIVER extension are separate install surfaces (install-copilot-mcp, install-copilot-receive). Also retires the stale Claude unit (--keep-stale-claude-unit opts out)
225
231
  ./run.sh uninstall-copilot-bridge # #86 C3a: honest inverse of install-copilot-bridge from the package-owned install-state (exact qualified id + exact marketplace name/path + recorded assembly only; state deleted LAST; a failing vendor list is UNKNOWN and refuses; never --force, never the stale Claude unit)
226
232
  ./run.sh doctor-copilot-bridge # #82/#86: fail-loud surface for that unit — runtime axis (red = a hook that RAN and failed, or a broken/unbaked artifact; "installed with zero records" is NOT red and is reported as NOT-YET: a Copilot session is born on its FIRST PROMPT, not when the window opens, measured) + ownership axis (install-state shape/binding, marketplace path drift, UNKNOWN vendor lists; legacy no-state install = named non-green, repair = install-copilot-bridge adoption). Either axis red = red
@@ -282,7 +288,7 @@ Usage:
282
288
  ./run.sh check-pack-pin-matcher # pure self-test of check-pack-install's pin-leak matcher against synthetic .pnpm lookalikes, one cell per property: version boundary (a prefix-extended @0.86.0-beta.1 and an off-pin @0.85.1 must leak, @0.86.0 bare or with any measured peer-hash must pass) and closure prefix (an off-pin @earendil-works/chord must leak — it carries no `pi-` prefix); snapshot-safe qualification oracle, also run first inside check-pack-install
283
289
  ./run.sh check-pack-consumer-roots # pure self-test of check-pack-install's consumer sandbox: under a poisoned inherited HOME/XDG/PI_CODING_AGENT_DIR every writable root a consumer subcall sees resolves inside its own consumer home; snapshot-safe qualification oracle, also run first inside check-pack-install
284
290
  ./run.sh check-fresh-cut-gate # SOURCE cell of the generation-boundary proof (IN pnpm run check:full): drives real install/setup/fresh-cut in a sandbox; certification refusal is pre-write, quiescence is fail-closed, archives preserve bytes, and the #54 exit matrix distinguishes complete / no-move / usage / incomplete transition / complete-with-cleanup-residue. No model/network/cost
285
- ./run.sh check-pack-install # heavy publish gate (prepublishOnly): actual npm pack + tar -tf + fresh-temp install smoke + the #116 M3-b1 herdr-plugin RUNTIME VERIFIER run against that actual installed tarball (the focused check-herdr-runtime-bootstrap drives a fixture; THIS is where the real package is proven) with the pinned pi peers (pins derived from the package.json devDep; check-dep-versions binds them) + the npm-installed bridge BOOTS (tools/list) and DELIVERS (tools/call entwurf_v2 → .msg lands) + the installed all-absent and copilot-present (four-unit fake-vendor) `entwurf setup` rows + the INSTALLED generation lifecycle on a seeded previous-generation host (REFUSE before activation writes / zero Claude invocations → installed fresh-cut archives + opens empty → install-meta-bridge PASSES) + the INSTALLED-PACKAGE branch of the Copilot and OMP birth installers actually RUN (compiled entry selected, no raw .ts, and a real birth edge mints a citizen — the half a required-artifact list can never stand in for)
291
+ ./run.sh check-pack-install # heavy publish gate (prepublishOnly): actual npm pack + tar -tf + fresh-temp install smoke + the #116 M3-b1 herdr-plugin RUNTIME VERIFIER run against that actual installed tarball (the focused check-herdr-runtime-bootstrap drives a fixture; THIS is where the real package is proven) with the pinned pi peers (pins derived from the package.json devDep; check-dep-versions binds them) + the npm-installed bridge BOOTS (tools/list) and DELIVERS (tools/call entwurf_v2 → .msg lands) + the installed all-absent and copilot-present (four-unit fake-vendor) `entwurf setup` rows + the INSTALLED generation lifecycle on a seeded previous-generation host (REFUSE before activation writes / zero Claude invocations → installed fresh-cut archives + opens empty → install-meta-bridge PASSES) + the INSTALLED-PACKAGE branch of the Copilot and OMP birth installers actually RUN (compiled entry selected, no raw .ts, and a real birth edge mints a citizen — the half a required-artifact list can never stand in for) + the INSTALLED #129 durable bootstrap (pi/pi-durable/bootstrap.mjs) imported from node_modules: main/openDurableCitizen exported and PACKAGED_BRIDGE_ENTRY = the installed package's own dist entry — import only, no runtime/TUI/birth/bridge started
286
292
  ./run.sh check-install-container # 0.12.8 (#51 C): Linux artifact-CONSUMER gate — one candidate .tgz handed read-only to a checkout-invisible node:<engines-major>-bookworm cell. Default packs once to temp; ENTWURF_CANDIDATE_TGZ=/absolute/preserved.tgz consumes those exact bytes with no re-pack and prints canonical path+sha256 for release. Non-root global PATH install, frozen package, MCP tools/list, fake-Claude install-meta-bridge, path+sha256 fence, strict doctor, and the GENERATION host-state matrix (clean / v3-only store bytes unchanged / previous-generation REFUSE→fresh-cut→retry PASS) seeded inline. Docker missing = honest SKIP; ENTWURF_REQUIRE_DOCKER=1 makes that RED (required CI)
287
293
  ./run.sh install [project-dir] # INTERNAL part of `setup` (project .pi/settings.json wiring) + npm-consumer entry — prefer `setup`, don't call directly for dev
288
294
  ./run.sh remove [project-dir] # remove entwurf entries from project .pi/settings.json (project scope only; global user-scope citizen left intact)
@@ -968,6 +974,19 @@ smoke_omp_fresh_live() {
968
974
  run_ts scripts/smoke-omp-fresh-live.ts
969
975
  }
970
976
 
977
+ smoke_pi_durable_fresh_live() {
978
+ # #129 first-admission acceptance, RELEASE MUST: docs/adding-a-harness.md step 9 clause 7 and
979
+ # the two cross-harness dispatch receipts. A collector fixture opens a REAL visible Pi through
980
+ # the public entwurf_fresh_call; Pi's live turn opens pi-durable through the managed verb; pd's
981
+ # first action calls back into Pi by exact nonce; Pi's live turn → pd (mailbox, drained by pd's
982
+ # live turn) and pd's live turn → Pi (control socket), relayed to the fixture. pd's actual
983
+ # offering and model are read from its native store via an online node:sqlite backup. Private
984
+ # tmux server; REAL meta roots (one Pi + one pd minted, asserted); records/transcripts/DB kept.
985
+ # pi-durable absent from FRESH_CALL_BACKENDS or without a mailbox rail, or LIVE!=1, is a
986
+ # protocol SKIP; with LIVE=1 every missing prerequisite (operator runtime, pi, tmux, bridge) FAILs.
987
+ run_ts scripts/smoke-pi-durable-fresh-live.ts
988
+ }
989
+
971
990
  smoke_codex_fresh_live() {
972
991
  # #95 first-admission RELEASE MUST. The explicit operator-owned app-server PID is the
973
992
  # seat authority: validate its Codex app-server argv and /proc TMUX/TMUX_PANE, then use
@@ -1348,6 +1367,14 @@ check_acp_foreground_only() {
1348
1367
  run_vitest pi-extensions/lib/acp/backend-adapter.test.ts
1349
1368
  }
1350
1369
 
1370
+ check_acp_vendor_rejection() {
1371
+ # Behaviour-adjacent tests are discovered by the glob lane. This narrow case is the
1372
+ # mutant execution coordinate: include the adapter admission and real turn pipeline,
1373
+ # and keep run_vitest's failed-title attribution for qualification.
1374
+ section "ACP vendor rejection admission (mutant execution coordinate)"
1375
+ run_vitest pi-extensions/lib/acp/backend-adapter.test.ts pi-extensions/lib/acp/backend.vendor-rejection.test.ts
1376
+ }
1377
+
1351
1378
  check_pi_mcp_register() {
1352
1379
  # NOT a discovery path — check-tests-beside-behavior finds the test by glob. This case is the
1353
1380
  # MUTANT EXECUTION COORDINATE for scripts/mutants/pi-mcp-register.json (same reasoning as
@@ -1484,7 +1511,20 @@ check_mux_fresh_call() {
1484
1511
  # codex-caller-seat rides here for the same reason as the two preflights: the leaf exists
1485
1512
  # only as freshCall's Codex placement input (#95 lane B), so certifying it apart from the
1486
1513
  # composition it feeds would let the two halves of one seat decision drift.
1487
- run_vitest test/mux-fresh-call.test.ts test/fresh-call-composition.contract.test.ts test/copilot-fresh-preflight.test.ts test/codex-fresh-preflight.test.ts test/fresh-call-surfaces.contract.test.ts test/fresh-call-provider.contract.test.ts test/omp-fresh-bootstrap.contract.test.ts test/tmux-coordinate-row.test.ts test/codex-fresh-live-protocol.test.ts test/codex-caller-seat.test.ts
1514
+ # pi-durable-fresh-live-receipts rides here for tmux-coordinate-row's reason: it is the
1515
+ # deterministic half of a LIVE sibling (smoke-pi-durable-fresh-live, #129) — the oracles that judge
1516
+ # that gate's receipts, proven on inline rows, so no model turn and no durable host is needed.
1517
+ run_vitest test/mux-fresh-call.test.ts test/fresh-call-composition.contract.test.ts test/copilot-fresh-preflight.test.ts test/codex-fresh-preflight.test.ts test/fresh-call-surfaces.contract.test.ts test/fresh-call-provider.contract.test.ts test/omp-fresh-bootstrap.contract.test.ts test/tmux-coordinate-row.test.ts test/codex-fresh-live-protocol.test.ts test/codex-caller-seat.test.ts test/pi-durable-fresh-live-receipts.test.ts
1518
+ }
1519
+
1520
+ check_pi_durable_beside() {
1521
+ # #129: the pi-durable qualification lane's execution coordinate (VERIFY 「Four rules」 rule 3). It is
1522
+ # not a discovery path — check-tests-beside-behavior already finds these files. It exists because the
1523
+ # qualification runner re-invokes a gate argv once per mutant, and the narrow filter keeps one mutant
1524
+ # from re-running every beside test. The four files are one contact: the adapter and its packaged
1525
+ # bootstrap, the runtime locator/verifier (which also reads the three checkout gates as SOURCE), the
1526
+ # fresh preflight, and the owner join that admits the durable host.
1527
+ run_vitest pi-extensions/meta-bridge-pi-durable.test.ts pi-extensions/lib/pi-durable-runtime.test.ts pi-extensions/lib/pi-durable-fresh-preflight.test.ts pi-extensions/lib/entwurf-deliverability.owner-join.test.ts
1488
1528
  }
1489
1529
 
1490
1530
  smoke_mux_fresh_call_live() {
@@ -1622,9 +1662,9 @@ smoke_acp_session_reuse_live() {
1622
1662
  # needs LIVE=1. Forces process-scoped (pushes --entwurf-control into argv) and
1623
1663
  # drives TWO real ACP turns over ONE reused claude-agent-acp child via the real
1624
1664
  # streamShellAcp: turn 1 introduces a codeword (full transcript), turn 2 sends
1625
- # ONLY the latest user delta and must recall the codeword — proving the child
1626
- # was reused and the live ACP session kept turn-1 history (a respawn-per-turn
1627
- # backend would forget it). The one-shot exit0 half is owned by
1665
+ # ONLY the latest user delta. The REUSE lifecycle notice, with no new bootstrap,
1666
+ # is the verdict; codeword recall is supporting only (a rebuilt full transcript
1667
+ # could recall it too). The one-shot exit0 half is owned by
1628
1668
  # smoke-acp-provider-live.
1629
1669
  # Model override: ENTWURF_ACP_PROVIDER_MODEL (default claude-sonnet-5).
1630
1670
  # LIVE=1 ./run.sh smoke-acp-session-reuse-live
@@ -2798,6 +2838,18 @@ check_pi_import_surface() {
2798
2838
  # `/providers/*`, and any deeper path stay FORBIDDEN (we use only `/compat`).
2799
2839
  # Do NOT widen this to a `providers/*` subpath — it typechecks but CANNOT
2800
2840
  # resolve under the extension loader.
2841
+ #
2842
+ # #129 EXCEPTION — exactly TWO specifiers, in exactly ONE file:
2843
+ # @earendil-works/pi-coding-agent/experimental/durable/runtime.ts and …/tui.ts
2844
+ # in pi/pi-durable/bootstrap.mjs
2845
+ # The durable app is source-only upstream (the published pi-coding-agent excludes
2846
+ # dist/experimental and ships no src/), so these resolve ONLY against the operator's
2847
+ # pinned runtime checkout, through the source resolver `entwurf pi-durable` loads with
2848
+ # --import — never against this package's pi dependency. They are the one way to reach
2849
+ # the app at all; building the string to dodge this scan would be the evasion this gate
2850
+ # exists to stop. Only those quoted specifiers on that file's lines are neutralised
2851
+ # before the forbidden pattern is applied again, so any other pi subpath in that file,
2852
+ # and the same specifier in any other file, stay FORBIDDEN (proved on the fixture below).
2801
2853
  # ONE corpus, TWO callers. The real scan and the denominator fixture must run the
2802
2854
  # SAME two lines, or the fixture would prove a command the gate does not use.
2803
2855
  pi_import_work_surface() {
@@ -2806,7 +2858,9 @@ check_pi_import_surface() {
2806
2858
  pi_import_scan() {
2807
2859
  pi_import_work_surface \
2808
2860
  | xargs -0r grep -HnE "[\"'\`]@earendil-works/pi-(ai|coding-agent|tui)/" 2>/dev/null \
2809
- | grep -vE "[\"'\`]@earendil-works/pi-ai/compat[\"'\`]" 2>/dev/null || true
2861
+ | grep -vE "[\"'\`]@earendil-works/pi-ai/compat[\"'\`]" 2>/dev/null \
2862
+ | sed -E "/^pi\/pi-durable\/bootstrap\.mjs:[0-9]+:/ s#[\"'\`]@earendil-works/pi-coding-agent/experimental/durable/(runtime|tui)\.ts[\"'\`]#(durable-runtime-specifier)#g" \
2863
+ | grep -E "[\"'\`]@earendil-works/pi-(ai|coding-agent|tui)/" 2>/dev/null || true
2810
2864
  }
2811
2865
 
2812
2866
  local probe_dir probe_hits hits
@@ -2817,6 +2871,13 @@ check_pi_import_surface() {
2817
2871
  printf '%s\n' 'import { getModels } from "@earendil-works/pi-ai/compat";' > tracked-allowed.ts
2818
2872
  git add tracked-allowed.ts
2819
2873
  printf '%s\n' 'import "@earendil-works/pi-ai/private-probe";' > untracked-forbidden.ts
2874
+ mkdir -p pi/pi-durable
2875
+ printf '%s\n' 'const RUNTIME = "@earendil-works/pi-coding-agent/experimental/durable/runtime.ts";' \
2876
+ 'import "@earendil-works/pi-coding-agent/experimental/other.ts";' \
2877
+ 'const TUI = "@earendil-works/pi-coding-agent/experimental/durable/tui.ts"; import "@earendil-works/pi-tui/private";' \
2878
+ > pi/pi-durable/bootstrap.mjs
2879
+ printf '%s\n' 'const RUNTIME = "@earendil-works/pi-coding-agent/experimental/durable/runtime.ts";' > elsewhere.mjs
2880
+ git add pi/pi-durable/bootstrap.mjs elsewhere.mjs
2820
2881
  ) || { fail "[check-pi-import-surface] could not build the denominator fixture in $probe_dir"; rm -rf "$probe_dir"; return 1; }
2821
2882
  probe_hits=$(cd "$probe_dir" && pi_import_scan)
2822
2883
  rm -rf "$probe_dir"
@@ -2830,6 +2891,15 @@ check_pi_import_surface() {
2830
2891
  fail "[QK:PIIMPORT-WORK-SURFACE] the denominator fixture disagrees with the corpus — untracked-forbidden reached=$saw_untracked_forbidden (want yes: a brand-new forbidden import must not escape until it is staged), tracked-allowed flagged=$saw_tracked_allowed (want no: the /compat exception must survive)"
2831
2892
  return 1
2832
2893
  fi
2894
+ local durable_allowed_flagged="no" durable_other_reached="no" durable_sameline_reached="no" durable_elsewhere_reached="no"
2895
+ grep -qE '^pi/pi-durable/bootstrap\.mjs:1:' <<<"$probe_hits" && durable_allowed_flagged="yes"
2896
+ grep -qE '^pi/pi-durable/bootstrap\.mjs:2:' <<<"$probe_hits" && durable_other_reached="yes"
2897
+ grep -qE '^pi/pi-durable/bootstrap\.mjs:3:' <<<"$probe_hits" && durable_sameline_reached="yes"
2898
+ grep -qE '^elsewhere\.mjs:1:' <<<"$probe_hits" && durable_elsewhere_reached="yes"
2899
+ if [ "$durable_allowed_flagged" != "no" ] || [ "$durable_other_reached" != "yes" ] || [ "$durable_sameline_reached" != "yes" ] || [ "$durable_elsewhere_reached" != "yes" ]; then
2900
+ fail "[QK:PIIMPORT-DURABLE-BOOTSTRAP-EXACT] the #129 durable exception is not exact — its runtime specifier in pi/pi-durable/bootstrap.mjs flagged=$durable_allowed_flagged (want no), another experimental subpath in that file reached=$durable_other_reached (want yes), a forbidden specifier sharing a line with an allowed one reached=$durable_sameline_reached (want yes), the same specifier in another file reached=$durable_elsewhere_reached (want yes)"
2901
+ return 1
2902
+ fi
2833
2903
 
2834
2904
  hits=$(cd "$REPO_DIR" && pi_import_scan)
2835
2905
  if [ -n "$hits" ]; then
@@ -2837,7 +2907,7 @@ check_pi_import_surface() {
2837
2907
  echo "$hits"
2838
2908
  exit 1
2839
2909
  fi
2840
- ok "[check-pi-import-surface] pi references are root/compat-only across tracked + untracked-non-ignored ts/js; the untracked denominator is proved on an external fixture repo"
2910
+ ok "[check-pi-import-surface] pi references are root/compat-only (plus the exact #129 durable bootstrap pair) across tracked + untracked-non-ignored ts/js; the untracked denominator and the durable exception's exactness are proved on an external fixture repo"
2841
2911
  }
2842
2912
 
2843
2913
  check_env_namespace() {
@@ -3500,19 +3570,25 @@ check_pack() {
3500
3570
  # Intent + policy live in NEXT.md Phase 2.3.
3501
3571
  section "pack invariants (dry-run)"
3502
3572
 
3503
- local json
3504
- # --silent so the `prepack` build (pnpm --silent run build-bridge → tsc) and
3505
- # npm's own lifecycle banner stay off stdout; otherwise they pollute the --json
3506
- # payload this parses. prepack runs on dry-run too, which is how dist lands in
3507
- # this gate's file list.
3573
+ local json pack_err
3574
+ # --silent keeps npm's own lifecycle banner off stdout, and prepack sends its build
3575
+ # output to stderr (`pnpm run build-bridge 1>&2`); either on stdout would pollute the
3576
+ # --json payload this parses. --foreground-scripts streams that stderr instead of
3577
+ # npm's pipe buffer, and it is CAPTURED, not discarded: a pnpm verify-deps refusal
3578
+ # inside prepack used to fail this gate with no reason anywhere. prepack runs on
3579
+ # dry-run too, which is how dist lands in this gate's file list.
3508
3580
  # with-dist-lock wraps the WHOLE pack (prepack build-bridge emit + npm's
3509
3581
  # post-build dist read) so a concurrent pack/build can't `rm -rf dist` mid-read
3510
3582
  # (the 2026-07-03 phantom "dist missing" race). The nested prepack build-bridge
3511
3583
  # is reentrant via ENTWURF_BUILD_LOCK_HELD.
3512
- json=$(cd "$REPO_DIR" && bash scripts/with-dist-lock.sh npm pack --dry-run --json --silent 2>/dev/null) || {
3584
+ pack_err=$(mktemp)
3585
+ json=$(cd "$REPO_DIR" && bash scripts/with-dist-lock.sh npm pack --dry-run --json --silent --foreground-scripts 2>"$pack_err") || {
3513
3586
  fail "[check-pack] npm pack --dry-run failed"
3587
+ sed 's/^/ /' "$pack_err" >&2
3588
+ rm -f "$pack_err"
3514
3589
  return 1
3515
3590
  }
3591
+ rm -f "$pack_err"
3516
3592
 
3517
3593
  local file_list
3518
3594
  file_list=$(node -e '
@@ -3607,6 +3683,12 @@ check_pack() {
3607
3683
  # check before assembling anything. The unit skeleton's package.json below is the other
3608
3684
  # half — the installer copies it, and `pi/` ships per-FILE.
3609
3685
  "mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js"
3686
+ "mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js"
3687
+ "mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js"
3688
+ "mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js"
3689
+ "pi/pi-durable/bootstrap.mjs"
3690
+ "pi/pi-durable/overlay/upstream-pin.json"
3691
+ "pi/pi-durable/overlay/runtime-contacts.patch"
3610
3692
  "pi/meta-bridge-omp/entwurf-meta-omp/package.json"
3611
3693
  "mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js"
3612
3694
  "scripts/postinstall-chmod.cjs"
@@ -3735,7 +3817,7 @@ check_pack() {
3735
3817
  # which is exactly what property (1)'s `(_|$)` absorbs; all three shapes are fixtures
3736
3818
  # below so a future suffix change cannot pass vacuously.)
3737
3819
  pack_install_leaked_pi() {
3738
- grep '^@earendil-works+' | grep -Ev '@1\.0\.0(_|$)' || true
3820
+ grep '^@earendil-works+' | grep -Ev '@1\.0\.2(_|$)' || true
3739
3821
  }
3740
3822
 
3741
3823
  # Matcher self-test on SYNTHETIC lookalikes: a healthy install tree cannot exercise either
@@ -3755,15 +3837,15 @@ check_pack_pin_matcher() {
3755
3837
  # None may leak; the two lookalikes must — a PREFIX-EXTENDED version (`0.86.0-beta.1`,
3756
3838
  # the prerelease shape that an unanchored match would bless) and an off-pin version.
3757
3839
  matcher_probe=$(printf '%s\n' \
3758
- '@earendil-works+pi-ai@1.0.0' \
3759
- '@earendil-works+pi-ai@1.0.0_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6' \
3760
- '@earendil-works+pi-ai@1.0.0_ws@8.21.3' \
3761
- '@earendil-works+pi-ai@1.0.0_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6__ws@8.21.3_zod@4.3.6' \
3762
- '@earendil-works+pi-ai@1.0.0-beta.1' \
3840
+ '@earendil-works+pi-ai@1.0.2' \
3841
+ '@earendil-works+pi-ai@1.0.2_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6' \
3842
+ '@earendil-works+pi-ai@1.0.2_ws@8.21.3' \
3843
+ '@earendil-works+pi-ai@1.0.2_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6__ws@8.21.3_zod@4.3.6' \
3844
+ '@earendil-works+pi-ai@1.0.2-beta.1' \
3763
3845
  '@earendil-works+pi-agent-core@0.87.1' | pack_install_leaked_pi)
3764
- if [ "$matcher_probe" != '@earendil-works+pi-ai@1.0.0-beta.1
3846
+ if [ "$matcher_probe" != '@earendil-works+pi-ai@1.0.2-beta.1
3765
3847
  @earendil-works+pi-agent-core@0.87.1' ]; then
3766
- fail "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED] the pin-leak matcher must flag the prefix-extended 1.0.0-beta.1 and the off-pin 0.87.1 lookalikes, and pass 1.0.0 bare or with any measured peer-hash — got: ${matcher_probe:-<nothing leaked>}"
3848
+ fail "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED] the pin-leak matcher must flag the prefix-extended 1.0.2-beta.1 and the off-pin 0.87.1 lookalikes, and pass 1.0.2 bare or with any measured peer-hash — got: ${matcher_probe:-<nothing leaked>}"
3767
3849
  return 1
3768
3850
  fi
3769
3851
 
@@ -3771,9 +3853,9 @@ check_pack_pin_matcher() {
3771
3853
  # (0.85.0 onward, still true at 0.99.1). An off-pin chord MUST leak; the pinned one must not. A matcher narrowed
3772
3854
  # back to `^@earendil-works+pi-` sees nothing here and dies at this signature.
3773
3855
  matcher_probe=$(printf '%s\n' \
3774
- '@earendil-works+chord@1.0.0' \
3856
+ '@earendil-works+chord@1.0.2' \
3775
3857
  '@earendil-works+chord@0.87.1' \
3776
- '@earendil-works+pi-ai@1.0.0' | pack_install_leaked_pi)
3858
+ '@earendil-works+pi-ai@1.0.2' | pack_install_leaked_pi)
3777
3859
  if [ "$matcher_probe" != '@earendil-works+chord@0.87.1' ]; then
3778
3860
  fail "[QK:PACK-INSTALL-PIN-MATCHER-COVERS-CLOSURE] the pin-leak matcher must cover every @earendil-works closure member, not just the pi-* families — an off-pin @earendil-works/chord has to leak (it is a runtime dependency of pi-coding-agent, pi-agent-core, pi-client and pi-protocol — `[측정 2026-09-20]` at 0.86.0, re-measured `[측정 2026-09-22]` at 0.87.0, `[측정 2026-09-23]` at 0.87.1 and `[측정 2026-09-30]` at 0.99.1, where pi-coding-agent adds pi-codemode and pi-mcp beside them) — got: ${matcher_probe:-<nothing leaked>}"
3779
3861
  return 1
@@ -3973,6 +4055,12 @@ _check_pack_install_impl() {
3973
4055
  # check before assembling anything. The unit skeleton's package.json below is the other
3974
4056
  # half — the installer copies it, and `pi/` ships per-FILE.
3975
4057
  "mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js"
4058
+ "mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js"
4059
+ "mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js"
4060
+ "mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js"
4061
+ "pi/pi-durable/bootstrap.mjs"
4062
+ "pi/pi-durable/overlay/upstream-pin.json"
4063
+ "pi/pi-durable/overlay/runtime-contacts.patch"
3976
4064
  "pi/meta-bridge-omp/entwurf-meta-omp/package.json"
3977
4065
  "mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js"
3978
4066
  "scripts/postinstall-chmod.cjs"
@@ -4114,20 +4202,23 @@ _check_pack_install_impl() {
4114
4202
  # @earendil-works/pi-codemode and @earendil-works/pi-mcp as runtime dependencies (the built-in
4115
4203
  # codemode and MCP extensions), so both are pinned beside the eight. pi-client and pi-protocol are
4116
4204
  # still published at 0.99.1 and keep their pins under the rule above.
4205
+ # `[측정 2026-10-05]` at 1.0.2 the constellation is unchanged: `npm view <pkg>@1.0.2 dependencies`
4206
+ # gives pi-coding-agent {chord, pi-agent-core, pi-ai, pi-codemode, pi-mcp, pi-tui}, pi-ai
4207
+ # {pi-telemetry}, pi-client {chord, pi-protocol}, pi-protocol {chord}, all ten published at 1.0.2.
4117
4208
  echo "[check-pack-install] pnpm add into $tmp (with 1.0.x peers + chord + codemode + mcp + typebox)"
4118
4209
  local install_log
4119
4210
  install_log=$(cd "$tmp" && pnpm add \
4120
4211
  "$tgz_path" \
4121
- "@earendil-works/pi-ai@1.0.0" \
4122
- "@earendil-works/pi-coding-agent@1.0.0" \
4123
- "@earendil-works/pi-tui@1.0.0" \
4124
- "@earendil-works/pi-agent-core@1.0.0" \
4125
- "@earendil-works/pi-client@1.0.0" \
4126
- "@earendil-works/pi-protocol@1.0.0" \
4127
- "@earendil-works/pi-telemetry@1.0.0" \
4128
- "@earendil-works/chord@1.0.0" \
4129
- "@earendil-works/pi-codemode@1.0.0" \
4130
- "@earendil-works/pi-mcp@1.0.0" \
4212
+ "@earendil-works/pi-ai@1.0.2" \
4213
+ "@earendil-works/pi-coding-agent@1.0.2" \
4214
+ "@earendil-works/pi-tui@1.0.2" \
4215
+ "@earendil-works/pi-agent-core@1.0.2" \
4216
+ "@earendil-works/pi-client@1.0.2" \
4217
+ "@earendil-works/pi-protocol@1.0.2" \
4218
+ "@earendil-works/pi-telemetry@1.0.2" \
4219
+ "@earendil-works/chord@1.0.2" \
4220
+ "@earendil-works/pi-codemode@1.0.2" \
4221
+ "@earendil-works/pi-mcp@1.0.2" \
4131
4222
  "typebox@latest" \
4132
4223
  --ignore-workspace --ignore-scripts 2>&1) || {
4133
4224
  fail "[check-pack-install] pnpm add failed:"
@@ -4143,11 +4234,11 @@ _check_pack_install_impl() {
4143
4234
  local leaked_pi
4144
4235
  leaked_pi=$(ls "$tmp/node_modules/.pnpm" 2>/dev/null | pack_install_leaked_pi)
4145
4236
  if [ -n "$leaked_pi" ]; then
4146
- fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 1.0.0):"
4237
+ fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 1.0.2):"
4147
4238
  printf '%s\n' "$leaked_pi" | sed 's/^/ /' >&2
4148
4239
  return 1
4149
4240
  fi
4150
- echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works package is 1.0.0 (chord included)"
4241
+ echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works package is 1.0.2 (chord included)"
4151
4242
 
4152
4243
  # Resolve the installed package.json and confirm pi.extensions
4153
4244
  # arrived intact. If pi.extensions is empty or missing, the
@@ -4166,6 +4257,34 @@ _check_pack_install_impl() {
4166
4257
  }
4167
4258
  echo "[check-pack-install] installed: $probe"
4168
4259
 
4260
+ # #129 — the packaged durable bootstrap, imported FROM the installed tree. The tarball listing
4261
+ # above proves the file ships, not that its static imports reach the shipped compiled closure
4262
+ # under node_modules (where a checkout .ts import would be refused by the strip-types fence), nor
4263
+ # that the bridge entry it hands the adapter is this package's own dist. Import only: main() runs
4264
+ # solely when the file is the process entry, and the runtime/TUI are dynamic imports inside it, so
4265
+ # no runtime, TUI, birth or bridge starts here.
4266
+ local durable_proof
4267
+ durable_proof=$(cd "$tmp" && node --input-type=module -e "
4268
+ const { realpathSync } = await import('node:fs');
4269
+ const pkg = realpathSync(process.cwd() + '/node_modules/@junghanacs/entwurf');
4270
+ const m = await import(pkg + '/pi/pi-durable/bootstrap.mjs');
4271
+ for (const name of ['main', 'openDurableCitizen']) {
4272
+ if (typeof m[name] !== 'function') { console.error(name + ' is not an exported function'); process.exit(1); }
4273
+ }
4274
+ const want = pkg + '/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js';
4275
+ const entry = m.PACKAGED_BRIDGE_ENTRY;
4276
+ if (typeof entry !== 'string' || realpathSync(entry) !== want) {
4277
+ console.error('PACKAGED_BRIDGE_ENTRY ' + entry + ' is not the dist entry of the installed package (' + want + ')');
4278
+ process.exit(1);
4279
+ }
4280
+ console.log('exports main/openDurableCitizen; PACKAGED_BRIDGE_ENTRY is the installed dist entry');
4281
+ " 2>&1) || {
4282
+ fail "[check-pack-install] installed durable bootstrap import failed:"
4283
+ echo "$durable_proof" | sed 's/^/ /' >&2
4284
+ return 1
4285
+ }
4286
+ echo "[check-pack-install] installed durable bootstrap: $durable_proof"
4287
+
4169
4288
  # #116 M3-b1/b2 — THE ACTUAL-PACKAGE RUNTIME PROOF, THROUGH THE SHIPPED OWNER.
4170
4289
  # The Herdr plugin's runtime verifier decides whether a tree npm placed is a runtime the scoped
4171
4290
  # wiring may name: exact name@version, the compiled entry, all three required bins present AND
@@ -4551,7 +4670,7 @@ sys.exit(0 if any(isinstance(s,str) and s.rstrip('/')== '$npm2_pkg' for s in src
4551
4670
  return 1
4552
4671
  fi
4553
4672
  local skip_probe
4554
- for skip_probe in "pi: SKIP" "claude: SKIP" "agy: SKIP" "copilot: SKIP" "omp: SKIP" "codex: SKIP"; do
4673
+ for skip_probe in "pi: SKIP" "claude: SKIP" "agy: SKIP" "copilot: SKIP" "omp: SKIP" "codex: SKIP" "pi-durable: SKIP"; do
4555
4674
  if ! grep -q "$skip_probe" <<<"$setup_out"; then
4556
4675
  fail "[QK:CODEX-PACK-SETUP-ZERO-STATE] [check-pack-install] installed all-absent setup missing explicit zero-state '$skip_probe':"
4557
4676
  echo "$setup_out" | tail -25 | sed 's/^/ /' >&2
@@ -6016,6 +6135,20 @@ setup_all() {
6016
6135
  setup_result codex-terminal-title FAIL "detected codex, but the terminal-title setting did not complete (see above)"
6017
6136
  fi
6018
6137
  fi
6138
+ # ── pi-durable ── #129: presence-driven like every harness, but the "harness" is the
6139
+ # operator-provided durable runtime at its ONE fixed place, and setup writes NOTHING for it.
6140
+ # Absent is a zero-state SKIP; present and the pin is PASS; present but not the pin is a
6141
+ # named FAIL the operator repairs. The verdict is the same leaf the managed launch and the
6142
+ # fresh preflight ask — 0 verified, 4 absent, anything else refused. Entwurf never installs
6143
+ # the runtime (docs/setup-clean-host.md §2b).
6144
+ local pd_rc pd_out
6145
+ pd_rc=0; pd_out=$(run_ts pi-extensions/lib/pi-durable-runtime.ts resolve 2>&1) || pd_rc=$?
6146
+ case "$pd_rc" in
6147
+ 0) setup_result pi-durable PASS "operator runtime is the pin — launch with 'entwurf pi-durable'" ;;
6148
+ 4) setup_result pi-durable SKIP "no operator-provided durable runtime at the fixed location — zero durable wiring written (Entwurf never installs it)" ;;
6149
+ *) setup_result pi-durable FAIL "a durable runtime is present but refused — ${pd_out##*$'\n'}" ;;
6150
+ esac
6151
+
6019
6152
  # ── core bridge boundary ── deterministic preflight lives in `pnpm run
6020
6153
  # check:full`; live substrate acceptance lives in `LIVE=1 ./run.sh
6021
6154
  # release-gate <scratch> --cut`. Setup is the install path, so it verifies the
@@ -6528,6 +6661,8 @@ release_gate() {
6528
6661
  # Codex then opens the outbound Pi and correlates that callback. Actor coordinates travel,
6529
6662
  # and fresh Codex == app-server/MCP inherited session == outbound Pi is a hard check.
6530
6663
  run_live_step "smoke-codex-fresh-live (#95: visible Pi -> visible Codex/callback/addressed v2 -> visible Pi/callback)" gate bash "$self" smoke-codex-fresh-live
6664
+ # #129 first-admission: step 9 clause 7 + the two cross-harness receipts for pi-durable.
6665
+ run_live_step "smoke-pi-durable-fresh-live (#129: visible Pi -> visible pi-durable/callback/addressed v2 -> Pi relay)" gate bash "$self" smoke-pi-durable-fresh-live
6531
6666
 
6532
6667
  # 4. BEHAVIOR lane (advisory, non-blocking). Model-in-loop gates that probe
6533
6668
  # whether the model AUTONOMOUSLY drives the MCP entwurf surface. These never
@@ -6652,6 +6787,9 @@ case "$cmd" in
6652
6787
  smoke-omp-fresh-live)
6653
6788
  smoke_omp_fresh_live
6654
6789
  ;;
6790
+ smoke-pi-durable-fresh-live)
6791
+ smoke_pi_durable_fresh_live
6792
+ ;;
6655
6793
  check-capability-bundle-reach)
6656
6794
  check_capability_bundle_reach
6657
6795
  ;;
@@ -6691,6 +6829,26 @@ case "$cmd" in
6691
6829
  # on the status line; and still zero mailbox/receiver marker. Hermetic; no omp, no model turn
6692
6830
  run_ts scripts/check-omp-birth-hook.ts
6693
6831
  ;;
6832
+ check-pi-durable-contact)
6833
+ # #129 L-identity G-contact, checkout-only. Spawning gate (VERIFY: a vitest lane cannot hold
6834
+ # it): the real overlay durable app as a child host, the branch-emitted bridge as ITS child,
6835
+ # /proc as the PPID oracle. Both runtime inputs are operator-provisioned; absent → SKIP 97,
6836
+ # never pass. Installed: run_ts refuses it (no compiled twin) before any input is read.
6837
+ run_ts scripts/check-pi-durable-contact.ts
6838
+ ;;
6839
+ check-pi-durable-send)
6840
+ # #129 L-send first cell, checkout-only spawning gate: one native durable turn against a
6841
+ # gate-owned scripted endpoint (S) through the native ToolTask (H) to a seeded target. No
6842
+ # vendor turn (V). Inputs as check-pi-durable-contact; absent → SKIP 97.
6843
+ run_ts scripts/check-pi-durable-send.ts
6844
+ ;;
6845
+ check-pi-durable-receive)
6846
+ # #129 L-receive first cell, checkout-only spawning gate: no controller input; a gate-owned
6847
+ # sending bridge enqueues, the host's doorbell admits to the root, the scripted endpoint (S)
6848
+ # holds request #1 until the enqueue is captured, the native turn (H) drains it. No vendor (V).
6849
+ # Inputs as check-pi-durable-contact; absent → SKIP 97.
6850
+ run_ts scripts/check-pi-durable-receive.ts
6851
+ ;;
6694
6852
  check-copilot-statusline)
6695
6853
  check_copilot_statusline
6696
6854
  ;;
@@ -6769,6 +6927,9 @@ case "$cmd" in
6769
6927
  check-mux-fresh-call)
6770
6928
  check_mux_fresh_call
6771
6929
  ;;
6930
+ check-pi-durable-beside)
6931
+ check_pi_durable_beside
6932
+ ;;
6772
6933
  check-mux-resume-call)
6773
6934
  check_mux_resume_call
6774
6935
  ;;
@@ -7407,6 +7568,20 @@ case "$cmd" in
7407
7568
  shift || true
7408
7569
  exec bash "$REPO_DIR/scripts/copilot-launch.sh" "$@"
7409
7570
  ;;
7571
+ pi-durable)
7572
+ # #129 W (L3): the MANAGED durable launch. Same shape and reasons as `copilot` above: `exec`,
7573
+ # no subshell and no cd — the durable TUI must own this terminal, the caller's cwd, this pid
7574
+ # and its exit status — and `shift`, because everything after the verb is the bootstrap's argv.
7575
+ #
7576
+ # The durable app is source-only upstream, so its runtime is the OPERATOR's, at ONE fixed
7577
+ # place, and it is verified against the packaged pin BEFORE any of it runs: its source
7578
+ # resolver is the first code this launch would load from it. Absent or drifted is a named
7579
+ # refusal from the same leaf the fresh preflight and setup ask (exit 4 absent, 3 otherwise).
7580
+ # Entwurf never installs, repairs or moves that runtime.
7581
+ shift || true
7582
+ pd_resolver=$(run_ts pi-extensions/lib/pi-durable-runtime.ts resolve) || exit $?
7583
+ exec node --import "$pd_resolver" "$REPO_DIR/pi/pi-durable/bootstrap.mjs" "$@"
7584
+ ;;
7410
7585
  pi)
7411
7586
  # #118 홉 2: the managed pi launch. Same shape and same reasons as `copilot` above —
7412
7587
  # `exec`, no subshell and no cd, because this operates on the operator's session; and
@@ -7716,6 +7891,9 @@ case "$cmd" in
7716
7891
  check-acp-prompt-lifecycle)
7717
7892
  check_acp_prompt_lifecycle
7718
7893
  ;;
7894
+ check-acp-vendor-rejection)
7895
+ check_acp_vendor_rejection
7896
+ ;;
7719
7897
  check-acp-launch-namespace)
7720
7898
  check_acp_launch_namespace
7721
7899
  ;;
@@ -138,6 +138,24 @@ async function main(): Promise<void> {
138
138
  !herdrBranch.includes("codexCallerFreshPreflight"),
139
139
  );
140
140
 
141
+ // #129: pi-durable joined the tmux fresh set, NOT the herdr pilot set (GLG, 2026-10-06: no herdr
142
+ // pi-durable support in 0.31). Same axis as the codex cell: refused by the herdr reason, nothing
143
+ // spawned, no tmux fallback, and its operator-runtime preflight is never asked on this branch.
144
+ const durable = recordingSpawn();
145
+ const durableRefused = await dispatchFreshCall(
146
+ { ...REQUEST, backend: "pi-durable", model: "loopback/scripted" },
147
+ HERDR_ENV,
148
+ durable.spawn,
149
+ );
150
+ ok(
151
+ "[QK:FCD-HERDR-PI-DURABLE-UNSUPPORTED] inside herdr pi-durable is not a pilot backend: it refuses by the herdr reason with nothing spawned, never falls back to a tmux window, and its runtime preflight is never consulted there",
152
+ durableRefused.rail === "herdr" &&
153
+ !durableRefused.result.ok &&
154
+ durableRefused.result.reason === "herdr-backend-unsupported" &&
155
+ durable.calls.length === 0 &&
156
+ !herdrBranch.includes("piDurable"),
157
+ );
158
+
141
159
  // ── the tmux rail keeps its pre-existing ordering ────────────────────────────────────
142
160
  // The import line names the preflight too, so the body is what gets counted: the claim is
143
161
  // "called once, on one path", not "the word appears once".
@@ -3328,6 +3328,7 @@ let manifestCount: number;
3328
3328
  "acp-stop-reason": 6,
3329
3329
  "acp-stream-hooks": 10,
3330
3330
  "acp-usage-accounting": 12,
3331
+ "acp-vendor-rejection": 4,
3331
3332
  "agy-permission": 6,
3332
3333
  "bridge-boot-resume": 5,
3333
3334
  "bridge-command-boot": 12,
@@ -3345,6 +3346,7 @@ let manifestCount: number;
3345
3346
  "pi-bridge-sender": 4,
3346
3347
  "pi-mcp-bridge": 2,
3347
3348
  "pi-mcp-register": 3,
3349
+ "pi-durable": 24,
3348
3350
  "copilot-receive": 20,
3349
3351
  "entwurf-peers": 1,
3350
3352
  "fresh-call-dispatch": 11,
@@ -3354,7 +3356,7 @@ let manifestCount: number;
3354
3356
  "herdr-plugin": 25,
3355
3357
  "herdr-plugin-profile": 14,
3356
3358
  "herdr-activation": 25,
3357
- "herdr-plugin-build": 13,
3359
+ "herdr-plugin-build": 14,
3358
3360
  "herdr-runtime-bootstrap": 31,
3359
3361
  "herdr-fresh-call": 38,
3360
3362
  "herdr-supply": 11,
@@ -3364,7 +3366,7 @@ let manifestCount: number;
3364
3366
  "meta-identity": 4,
3365
3367
  "meta-retire": 4,
3366
3368
  "mux-boundary": 16,
3367
- "mux-fresh-call": 59,
3369
+ "mux-fresh-call": 65,
3368
3370
  "mux-launcher-fence": 8,
3369
3371
  "mux-parent-artifact": 3,
3370
3372
  "pack-install": 3,