@junghanacs/entwurf 0.30.1 → 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 (58) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +112 -0
  3. package/DELIVERY.md +1 -0
  4. package/README.md +47 -7
  5. package/VERIFY.md +17 -1
  6. package/demo/README.md +1 -1
  7. package/docs/acp-backend-rail.md +1 -1
  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/entwurf-deliverability.js +7 -1
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +34 -3
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +7 -1
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +28 -2
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +30 -2
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +92 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js +288 -0
  18. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +744 -0
  19. package/mcp/entwurf-bridge/src/index.ts +7 -7
  20. package/mcp/entwurf-bridge/tsconfig.build.json +8 -1
  21. package/package.json +13 -10
  22. package/pi/entwurf-capabilities.json +1 -0
  23. package/pi/pi-durable/bootstrap.mjs +281 -0
  24. package/pi/pi-durable/overlay/runtime-contacts.patch +91 -0
  25. package/pi/pi-durable/overlay/upstream-pin.json +16 -0
  26. package/pi-extensions/lib/entwurf-deliverability.ts +7 -1
  27. package/pi-extensions/lib/fresh-call-composition.ts +42 -7
  28. package/pi-extensions/lib/meta-sender-identity.ts +7 -1
  29. package/pi-extensions/lib/meta-session.ts +28 -2
  30. package/pi-extensions/lib/mux-fresh-call.ts +34 -2
  31. package/pi-extensions/lib/pi-durable-fresh-preflight.ts +113 -0
  32. package/pi-extensions/lib/pi-durable-runtime.ts +384 -0
  33. package/pi-extensions/meta-bridge-pi-durable.ts +1020 -0
  34. package/run.sh +200 -33
  35. package/scripts/check-fresh-call-dispatch.ts +18 -0
  36. package/scripts/check-gate-qualification.ts +2 -1
  37. package/scripts/check-pi-durable-contact.ts +834 -0
  38. package/scripts/check-pi-durable-receive.ts +1116 -0
  39. package/scripts/check-pi-durable-send.ts +1661 -0
  40. package/scripts/lib/pi-durable-fresh-live-receipts.ts +139 -0
  41. package/scripts/lib/pi-durable-r1.ts +100 -0
  42. package/scripts/lib/pi-durable-reap.ts +27 -0
  43. package/scripts/lib/pi-durable-receive.ts +137 -0
  44. package/scripts/mutants/control-send-receipt.json +1 -1
  45. package/scripts/mutants/copilot-birth.json +2 -2
  46. package/scripts/mutants/meta-hook-session-switch.json +4 -2
  47. package/scripts/mutants/mux-fresh-call.json +69 -3
  48. package/scripts/mutants/omp-birth.json +2 -2
  49. package/scripts/mutants/omp-fresh.json +6 -4
  50. package/scripts/mutants/pack-install.json +3 -3
  51. package/scripts/mutants/pi-durable.json +390 -0
  52. package/scripts/mutants/pi-floor.json +2 -2
  53. package/scripts/pi-durable-contact-host.mjs +105 -0
  54. package/scripts/pi-durable-receive-host.mjs +127 -0
  55. package/scripts/pi-durable-send-host.mjs +213 -0
  56. package/scripts/smoke-pi-durable-fresh-live.ts +624 -0
  57. package/scripts/smoke-setup-verdict.sh +21 -2
  58. 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
@@ -1492,7 +1511,20 @@ check_mux_fresh_call() {
1492
1511
  # codex-caller-seat rides here for the same reason as the two preflights: the leaf exists
1493
1512
  # only as freshCall's Codex placement input (#95 lane B), so certifying it apart from the
1494
1513
  # composition it feeds would let the two halves of one seat decision drift.
1495
- 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
1496
1528
  }
1497
1529
 
1498
1530
  smoke_mux_fresh_call_live() {
@@ -2806,6 +2838,18 @@ check_pi_import_surface() {
2806
2838
  # `/providers/*`, and any deeper path stay FORBIDDEN (we use only `/compat`).
2807
2839
  # Do NOT widen this to a `providers/*` subpath — it typechecks but CANNOT
2808
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).
2809
2853
  # ONE corpus, TWO callers. The real scan and the denominator fixture must run the
2810
2854
  # SAME two lines, or the fixture would prove a command the gate does not use.
2811
2855
  pi_import_work_surface() {
@@ -2814,7 +2858,9 @@ check_pi_import_surface() {
2814
2858
  pi_import_scan() {
2815
2859
  pi_import_work_surface \
2816
2860
  | xargs -0r grep -HnE "[\"'\`]@earendil-works/pi-(ai|coding-agent|tui)/" 2>/dev/null \
2817
- | 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
2818
2864
  }
2819
2865
 
2820
2866
  local probe_dir probe_hits hits
@@ -2825,6 +2871,13 @@ check_pi_import_surface() {
2825
2871
  printf '%s\n' 'import { getModels } from "@earendil-works/pi-ai/compat";' > tracked-allowed.ts
2826
2872
  git add tracked-allowed.ts
2827
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
2828
2881
  ) || { fail "[check-pi-import-surface] could not build the denominator fixture in $probe_dir"; rm -rf "$probe_dir"; return 1; }
2829
2882
  probe_hits=$(cd "$probe_dir" && pi_import_scan)
2830
2883
  rm -rf "$probe_dir"
@@ -2838,6 +2891,15 @@ check_pi_import_surface() {
2838
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)"
2839
2892
  return 1
2840
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
2841
2903
 
2842
2904
  hits=$(cd "$REPO_DIR" && pi_import_scan)
2843
2905
  if [ -n "$hits" ]; then
@@ -2845,7 +2907,7 @@ check_pi_import_surface() {
2845
2907
  echo "$hits"
2846
2908
  exit 1
2847
2909
  fi
2848
- 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"
2849
2911
  }
2850
2912
 
2851
2913
  check_env_namespace() {
@@ -3508,19 +3570,25 @@ check_pack() {
3508
3570
  # Intent + policy live in NEXT.md Phase 2.3.
3509
3571
  section "pack invariants (dry-run)"
3510
3572
 
3511
- local json
3512
- # --silent so the `prepack` build (pnpm --silent run build-bridge → tsc) and
3513
- # npm's own lifecycle banner stay off stdout; otherwise they pollute the --json
3514
- # payload this parses. prepack runs on dry-run too, which is how dist lands in
3515
- # 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.
3516
3580
  # with-dist-lock wraps the WHOLE pack (prepack build-bridge emit + npm's
3517
3581
  # post-build dist read) so a concurrent pack/build can't `rm -rf dist` mid-read
3518
3582
  # (the 2026-07-03 phantom "dist missing" race). The nested prepack build-bridge
3519
3583
  # is reentrant via ENTWURF_BUILD_LOCK_HELD.
3520
- 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") || {
3521
3586
  fail "[check-pack] npm pack --dry-run failed"
3587
+ sed 's/^/ /' "$pack_err" >&2
3588
+ rm -f "$pack_err"
3522
3589
  return 1
3523
3590
  }
3591
+ rm -f "$pack_err"
3524
3592
 
3525
3593
  local file_list
3526
3594
  file_list=$(node -e '
@@ -3615,6 +3683,12 @@ check_pack() {
3615
3683
  # check before assembling anything. The unit skeleton's package.json below is the other
3616
3684
  # half — the installer copies it, and `pi/` ships per-FILE.
3617
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"
3618
3692
  "pi/meta-bridge-omp/entwurf-meta-omp/package.json"
3619
3693
  "mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js"
3620
3694
  "scripts/postinstall-chmod.cjs"
@@ -3743,7 +3817,7 @@ check_pack() {
3743
3817
  # which is exactly what property (1)'s `(_|$)` absorbs; all three shapes are fixtures
3744
3818
  # below so a future suffix change cannot pass vacuously.)
3745
3819
  pack_install_leaked_pi() {
3746
- grep '^@earendil-works+' | grep -Ev '@1\.0\.0(_|$)' || true
3820
+ grep '^@earendil-works+' | grep -Ev '@1\.0\.2(_|$)' || true
3747
3821
  }
3748
3822
 
3749
3823
  # Matcher self-test on SYNTHETIC lookalikes: a healthy install tree cannot exercise either
@@ -3763,15 +3837,15 @@ check_pack_pin_matcher() {
3763
3837
  # None may leak; the two lookalikes must — a PREFIX-EXTENDED version (`0.86.0-beta.1`,
3764
3838
  # the prerelease shape that an unanchored match would bless) and an off-pin version.
3765
3839
  matcher_probe=$(printf '%s\n' \
3766
- '@earendil-works+pi-ai@1.0.0' \
3767
- '@earendil-works+pi-ai@1.0.0_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6' \
3768
- '@earendil-works+pi-ai@1.0.0_ws@8.21.3' \
3769
- '@earendil-works+pi-ai@1.0.0_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6__ws@8.21.3_zod@4.3.6' \
3770
- '@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' \
3771
3845
  '@earendil-works+pi-agent-core@0.87.1' | pack_install_leaked_pi)
3772
- 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
3773
3847
  @earendil-works+pi-agent-core@0.87.1' ]; then
3774
- 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>}"
3775
3849
  return 1
3776
3850
  fi
3777
3851
 
@@ -3779,9 +3853,9 @@ check_pack_pin_matcher() {
3779
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
3780
3854
  # back to `^@earendil-works+pi-` sees nothing here and dies at this signature.
3781
3855
  matcher_probe=$(printf '%s\n' \
3782
- '@earendil-works+chord@1.0.0' \
3856
+ '@earendil-works+chord@1.0.2' \
3783
3857
  '@earendil-works+chord@0.87.1' \
3784
- '@earendil-works+pi-ai@1.0.0' | pack_install_leaked_pi)
3858
+ '@earendil-works+pi-ai@1.0.2' | pack_install_leaked_pi)
3785
3859
  if [ "$matcher_probe" != '@earendil-works+chord@0.87.1' ]; then
3786
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>}"
3787
3861
  return 1
@@ -3981,6 +4055,12 @@ _check_pack_install_impl() {
3981
4055
  # check before assembling anything. The unit skeleton's package.json below is the other
3982
4056
  # half — the installer copies it, and `pi/` ships per-FILE.
3983
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"
3984
4064
  "pi/meta-bridge-omp/entwurf-meta-omp/package.json"
3985
4065
  "mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js"
3986
4066
  "scripts/postinstall-chmod.cjs"
@@ -4122,20 +4202,23 @@ _check_pack_install_impl() {
4122
4202
  # @earendil-works/pi-codemode and @earendil-works/pi-mcp as runtime dependencies (the built-in
4123
4203
  # codemode and MCP extensions), so both are pinned beside the eight. pi-client and pi-protocol are
4124
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.
4125
4208
  echo "[check-pack-install] pnpm add into $tmp (with 1.0.x peers + chord + codemode + mcp + typebox)"
4126
4209
  local install_log
4127
4210
  install_log=$(cd "$tmp" && pnpm add \
4128
4211
  "$tgz_path" \
4129
- "@earendil-works/pi-ai@1.0.0" \
4130
- "@earendil-works/pi-coding-agent@1.0.0" \
4131
- "@earendil-works/pi-tui@1.0.0" \
4132
- "@earendil-works/pi-agent-core@1.0.0" \
4133
- "@earendil-works/pi-client@1.0.0" \
4134
- "@earendil-works/pi-protocol@1.0.0" \
4135
- "@earendil-works/pi-telemetry@1.0.0" \
4136
- "@earendil-works/chord@1.0.0" \
4137
- "@earendil-works/pi-codemode@1.0.0" \
4138
- "@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" \
4139
4222
  "typebox@latest" \
4140
4223
  --ignore-workspace --ignore-scripts 2>&1) || {
4141
4224
  fail "[check-pack-install] pnpm add failed:"
@@ -4151,11 +4234,11 @@ _check_pack_install_impl() {
4151
4234
  local leaked_pi
4152
4235
  leaked_pi=$(ls "$tmp/node_modules/.pnpm" 2>/dev/null | pack_install_leaked_pi)
4153
4236
  if [ -n "$leaked_pi" ]; then
4154
- 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):"
4155
4238
  printf '%s\n' "$leaked_pi" | sed 's/^/ /' >&2
4156
4239
  return 1
4157
4240
  fi
4158
- 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)"
4159
4242
 
4160
4243
  # Resolve the installed package.json and confirm pi.extensions
4161
4244
  # arrived intact. If pi.extensions is empty or missing, the
@@ -4174,6 +4257,34 @@ _check_pack_install_impl() {
4174
4257
  }
4175
4258
  echo "[check-pack-install] installed: $probe"
4176
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
+
4177
4288
  # #116 M3-b1/b2 — THE ACTUAL-PACKAGE RUNTIME PROOF, THROUGH THE SHIPPED OWNER.
4178
4289
  # The Herdr plugin's runtime verifier decides whether a tree npm placed is a runtime the scoped
4179
4290
  # wiring may name: exact name@version, the compiled entry, all three required bins present AND
@@ -4559,7 +4670,7 @@ sys.exit(0 if any(isinstance(s,str) and s.rstrip('/')== '$npm2_pkg' for s in src
4559
4670
  return 1
4560
4671
  fi
4561
4672
  local skip_probe
4562
- 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
4563
4674
  if ! grep -q "$skip_probe" <<<"$setup_out"; then
4564
4675
  fail "[QK:CODEX-PACK-SETUP-ZERO-STATE] [check-pack-install] installed all-absent setup missing explicit zero-state '$skip_probe':"
4565
4676
  echo "$setup_out" | tail -25 | sed 's/^/ /' >&2
@@ -6024,6 +6135,20 @@ setup_all() {
6024
6135
  setup_result codex-terminal-title FAIL "detected codex, but the terminal-title setting did not complete (see above)"
6025
6136
  fi
6026
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
+
6027
6152
  # ── core bridge boundary ── deterministic preflight lives in `pnpm run
6028
6153
  # check:full`; live substrate acceptance lives in `LIVE=1 ./run.sh
6029
6154
  # release-gate <scratch> --cut`. Setup is the install path, so it verifies the
@@ -6536,6 +6661,8 @@ release_gate() {
6536
6661
  # Codex then opens the outbound Pi and correlates that callback. Actor coordinates travel,
6537
6662
  # and fresh Codex == app-server/MCP inherited session == outbound Pi is a hard check.
6538
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
6539
6666
 
6540
6667
  # 4. BEHAVIOR lane (advisory, non-blocking). Model-in-loop gates that probe
6541
6668
  # whether the model AUTONOMOUSLY drives the MCP entwurf surface. These never
@@ -6660,6 +6787,9 @@ case "$cmd" in
6660
6787
  smoke-omp-fresh-live)
6661
6788
  smoke_omp_fresh_live
6662
6789
  ;;
6790
+ smoke-pi-durable-fresh-live)
6791
+ smoke_pi_durable_fresh_live
6792
+ ;;
6663
6793
  check-capability-bundle-reach)
6664
6794
  check_capability_bundle_reach
6665
6795
  ;;
@@ -6699,6 +6829,26 @@ case "$cmd" in
6699
6829
  # on the status line; and still zero mailbox/receiver marker. Hermetic; no omp, no model turn
6700
6830
  run_ts scripts/check-omp-birth-hook.ts
6701
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
+ ;;
6702
6852
  check-copilot-statusline)
6703
6853
  check_copilot_statusline
6704
6854
  ;;
@@ -6777,6 +6927,9 @@ case "$cmd" in
6777
6927
  check-mux-fresh-call)
6778
6928
  check_mux_fresh_call
6779
6929
  ;;
6930
+ check-pi-durable-beside)
6931
+ check_pi_durable_beside
6932
+ ;;
6780
6933
  check-mux-resume-call)
6781
6934
  check_mux_resume_call
6782
6935
  ;;
@@ -7415,6 +7568,20 @@ case "$cmd" in
7415
7568
  shift || true
7416
7569
  exec bash "$REPO_DIR/scripts/copilot-launch.sh" "$@"
7417
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
+ ;;
7418
7585
  pi)
7419
7586
  # #118 홉 2: the managed pi launch. Same shape and same reasons as `copilot` above —
7420
7587
  # `exec`, no subshell and no cd, because this operates on the operator's session; and
@@ -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".
@@ -3346,6 +3346,7 @@ let manifestCount: number;
3346
3346
  "pi-bridge-sender": 4,
3347
3347
  "pi-mcp-bridge": 2,
3348
3348
  "pi-mcp-register": 3,
3349
+ "pi-durable": 24,
3349
3350
  "copilot-receive": 20,
3350
3351
  "entwurf-peers": 1,
3351
3352
  "fresh-call-dispatch": 11,
@@ -3365,7 +3366,7 @@ let manifestCount: number;
3365
3366
  "meta-identity": 4,
3366
3367
  "meta-retire": 4,
3367
3368
  "mux-boundary": 16,
3368
- "mux-fresh-call": 59,
3369
+ "mux-fresh-call": 65,
3369
3370
  "mux-launcher-fence": 8,
3370
3371
  "mux-parent-artifact": 3,
3371
3372
  "pack-install": 3,