@junghanacs/entwurf 0.41.0 → 0.41.2

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 (91) hide show
  1. package/AGENTS.md +1 -1
  2. package/BASELINE.md +7 -5
  3. package/CHANGELOG.md +426 -0
  4. package/DELIVERY.md +37 -20
  5. package/README.md +83 -81
  6. package/VERIFY.md +10 -10
  7. package/demo/README.md +1 -1
  8. package/docs/acp-backend-rail.md +1 -1
  9. package/docs/external-mcp-host.md +1 -1
  10. package/docs/setup-clean-host.md +95 -29
  11. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +14 -11
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +10 -0
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +23 -2
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +7 -1
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +3 -0
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +21 -10
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +4 -2
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +5 -0
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +6 -4
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/native-push/register.js +13 -2
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/omp-fresh-preflight.js +2 -2
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +1 -1
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-receive.js +25 -17
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/remote-pair-send.js +94 -28
  25. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +57 -7
  26. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +2 -2
  27. package/mcp/entwurf-bridge/dist/scripts/remote-pair-receive.js +12 -12
  28. package/mcp/entwurf-bridge/src/index.ts +14 -11
  29. package/native/win32-x64/entwurf_win.node +0 -0
  30. package/native/win32-x64/manifest.json +2 -2
  31. package/package.json +15 -14
  32. package/pi/pi-durable/carrier/tui.js +18 -3
  33. package/pi/pi-durable/overlay/upstream-pin.json +12 -12
  34. package/pi-extensions/lib/acp/backend-adapter.ts +5 -3
  35. package/pi-extensions/lib/acp/backend.ts +12 -9
  36. package/pi-extensions/lib/entwurf-self-address.ts +13 -0
  37. package/pi-extensions/lib/entwurf-v2-contract.ts +25 -2
  38. package/pi-extensions/lib/entwurf-v2-decider.ts +7 -0
  39. package/pi-extensions/lib/entwurf-v2-surface.ts +3 -0
  40. package/pi-extensions/lib/fresh-call-composition.ts +21 -10
  41. package/pi-extensions/lib/herdr-fresh-call.ts +4 -2
  42. package/pi-extensions/lib/meta-sender-identity.ts +4 -0
  43. package/pi-extensions/lib/mux-fresh-call.ts +6 -4
  44. package/pi-extensions/lib/native-push/register.ts +17 -2
  45. package/pi-extensions/lib/omp-fresh-preflight.ts +2 -2
  46. package/pi-extensions/lib/pi-durable-fresh-preflight.ts +1 -1
  47. package/pi-extensions/lib/remote-pair-receive.ts +27 -19
  48. package/pi-extensions/lib/remote-pair-send.ts +117 -30
  49. package/pi-extensions/meta-bridge-omp.ts +69 -7
  50. package/pi-extensions/meta-bridge-pi-durable.ts +3 -3
  51. package/run.sh +218 -49
  52. package/scripts/check-agy-sender-identity.ts +6 -4
  53. package/scripts/check-entwurf-self-address.ts +34 -3
  54. package/scripts/check-entwurf-v2-contract.ts +24 -2
  55. package/scripts/check-entwurf-v2-production.ts +45 -31
  56. package/scripts/check-gate-qualification.ts +11 -9
  57. package/scripts/check-native-push-adapter.ts +4 -0
  58. package/scripts/check-native-push-register.ts +32 -82
  59. package/scripts/check-remote-pair-installed.ts +2 -2
  60. package/scripts/dev-bin.sh +112 -1
  61. package/scripts/lib/chain-terminus-publication.ts +58 -0
  62. package/scripts/lib/codex-fresh-live-protocol.ts +33 -2
  63. package/scripts/lib/codex-fresh-source-receipts.ts +112 -3
  64. package/scripts/lib/pi-durable-fresh-live-receipts.ts +29 -1
  65. package/scripts/mutants/chain-terminus-publication.json +17 -0
  66. package/scripts/mutants/codex-native.json +98 -0
  67. package/scripts/mutants/control-send-receipt.json +3 -3
  68. package/scripts/mutants/mux-fresh-call.json +16 -1
  69. package/scripts/mutants/native-push-paused.json +17 -0
  70. package/scripts/mutants/omp-fresh.json +13 -2
  71. package/scripts/mutants/pack-install.json +50 -4
  72. package/scripts/mutants/pi-durable.json +60 -0
  73. package/scripts/mutants/pi-floor.json +5 -5
  74. package/scripts/mutants/remote-pair-display.json +3 -3
  75. package/scripts/mutants/remote-pair-receive.json +41 -11
  76. package/scripts/mutants/remote-pair-roundtrip.json +67 -7
  77. package/scripts/mutants/remote-pair-send.json +156 -17
  78. package/scripts/mutants/remote-pair-transport.json +4 -4
  79. package/scripts/mutants/source-install.json +66 -0
  80. package/scripts/remote-pair-receive.ts +12 -12
  81. package/scripts/smoke-agy-install-state.sh +121 -0
  82. package/scripts/smoke-agy-native-push-live.ts +13 -0
  83. package/scripts/smoke-codex-fresh-live.ts +52 -28
  84. package/scripts/smoke-entwurf-chain-live.ts +7 -9
  85. package/scripts/smoke-herdr-fresh-call-live.ts +4 -4
  86. package/scripts/smoke-herdr-plugin-build-live.ts +179 -50
  87. package/scripts/smoke-herdr-raw-install-live.sh +10 -3
  88. package/scripts/smoke-mux-lifecycle-live.ts +140 -8
  89. package/scripts/smoke-omp-fresh-live.ts +5 -4
  90. package/scripts/smoke-pi-durable-fresh-live.ts +29 -7
  91. package/scripts/smoke-setup-verdict.sh +27 -2
package/run.sh CHANGED
@@ -184,6 +184,7 @@ Usage:
184
184
  ./run.sh check-pi-durable-beside # #129 mutant execution coordinate (VERIFY rule 3): run_vitest over four pi-durable beside tests (adapter, carrier/SDK-set verifier, preflight, owner join) — a pi-durable qualification lane gate argv. Same files check-tests-beside-behavior already runs.
185
185
  ./run.sh check-pi-durable-carrier # #144 mutant execution coordinate (VERIFY rule 3): run_vitest over the carrier test alone (relocation/resolver, owned-source emission and typecheck fence, package file list, task footer, the real bootstrap on the 1.2 SDK, stream transport, skills prompt) — the gate argv of the carrier claims, whose QKs live only in that file. Same file check-tests-beside-behavior already runs.
186
186
  ./run.sh check-pi-durable-native-module # #144 mutant execution coordinate (VERIFY rule 3): run_vitest over the bootstrap's native-module file and directory ingress test alone — the gate argv of the ingress claims, whose QKs live only in that file. Same file check-tests-beside-behavior already runs.
187
+ ./run.sh check-remote-pair-beside # #149 mutant execution coordinate (VERIFY rule 3): run_vitest over the five remote-pair beside tests (send, receive, roundtrip, transport, mailbox body) — the gate argv of the remote-pair claims. Same files check-tests-beside-behavior already runs.
187
188
  ./run.sh check-pi-durable-receive # #129 L-receive FIRST CELL, CHECKOUT-ONLY. Same input (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)
188
189
  ./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
189
190
  ./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
@@ -221,10 +222,10 @@ Usage:
221
222
  ./run.sh check-typing-call-fence # deterministic gate for the boundary nobody has broken: this product composes ARGUMENTS and LETTERS, never keystrokes. Scans every tracked production source under pi-extensions/ and mcp/ — comments blanked, string literals kept — for tmux send-keys/paste-buffer/load-buffer and herdr's PTY verbs (agent.prompt, agent.send_keys, pane.send_text, pane.input.set). Prose is exempt on purpose: the sentence stating the rule names the very calls it forbids, and a scanner that read prose could be made green by deleting the law. Carries its own oracle — the detector is proven on planted code, proven blind to comments, and the scanned inventory is measured
222
223
  ./run.sh check-resume-launch-identity # deterministic gate for resume-launch-identity.ts, the record-authoritative launch-identity leaf preserved through the visible-first cut (spawn-bg and all its callers are gone; this leaf answers "which being is this, and which conversation is theirs"). Temp meta-store fixture: gardenId→record.transcriptPath happy path with header cwd/provider/model; C3 integrity (header id ≠ record.nativeSessionId → refused, never resumed); #52 ADDRESSABLE read (a gid that no longer holds its nativeSessionId alone is refused from EITHER side — the plain targeted read would resume one transcript twice under two locks); cause fidelity per impossible resume incl. the F7 pin (recorded-but-deleted transcript → MISSING, not "no recorded model"); header↔gate SSOT. No spawn/socket/timer
223
224
  ./run.sh smoke-entwurf-v2-matrix-live # LIVE sentinel (0.11 Stage 0 step 5d-5, D4-b) — OUT of pnpm check, needs LIVE=1. Drives REAL production runEntwurfV2 deps over REAL OS objects, 4 cells: C1 control-socket (real pi --entwurf-control resident → RPC send → lock acquire→release ×1), C1b record-less socket (#50 C4: live record-less pi → EVERY intent rejected pre-probe record-less-socket, no lock, rendered hint names record authority + fresh-cut), C2 meta-mailbox deliverable (armed self-fetch citizen → real .msg enqueue, lock-free), C3 meta-mailbox guard (no armed receiver → reject, no garbage). Model-in-loop OUT (transport/lock/enqueue gate, GPT Q2); negative/timeout stay deterministic. Model: ENTWURF_LIVE_TARGET=<provider>/<model> (default openai-codex/gpt-5.6-luna). LIVE=1 ./run.sh smoke-entwurf-v2-matrix-live
224
- ./run.sh smoke-agy-native-push-live # 봉인 8 LIVE acceptance for the native-push (agy) rail — OUT of pnpm check, needs LIVE=1 + AGY_CONVERSATION_ID (a live agy conversation). Drives the REAL antigravity adapter + register core + runEntwurfV2 (production deps): doctor-static preflight (dangling→FAIL, the ③ gate), probe route, register create/attach idempotency, fire→native-push delivered, post-send re-probe (D7 partial), bogus-conv→native-push-probe-indeterminate. Meta-store isolated to a temp dir (only the agy round-trip is real; no real-store residue). COST FENCE: open that agy conversation on gemini-3.6-flash (free account) — never a Pro tier; entwurf never selects the agy model and no assertion reads it. LIVE=1 AGY_CONVERSATION_ID=<convId> ./run.sh smoke-agy-native-push-live
225
+ ./run.sh smoke-agy-native-push-live # PAUSED (#152): exits 2 with [entwurf:paused] before it reads LIVE or AGY_CONVERSATION_ID — not a PASS and not a SKIP; OUT of pnpm check. Open no agy conversation for it while the pause holds. Its 봉인 8 native-push round trip (real antigravity adapter + register core + runEntwurfV2) runs only after a separate change lifts the pause.
225
226
  ./run.sh smoke-codex-native-push-live # on-demand LIVE acceptance for Codex native-push — needs LIVE=1 + CODEX_LIVE_THREAD_ID loaded by a visible TUI attached to the default app-server; probes the real UDS, routes through production runEntwurfV2, queues one exact-token turn without retry, re-probes, and leaves the visible TUI to show the model result
226
227
  ./run.sh smoke-codex-fresh-live # #95 RELEASE MUST: record-backed receipt fixture -> PUBLIC initial Pi fresh/callback in a session S that is NOT the app-server's -> Pi PUBLIC omitted-placement Codex fresh/exact callback, also in S -> Pi-to-Codex addressed native-push v2 -> Codex PUBLIC omitted-placement AND omitted-cwd fresh Pi/exact callback IN S beside its own TUI (lane B claim: the app-server env names another session, so S can only come from the caller's pane title; lane C claim: no cwd was asked for, so the directory can only come from the Codex caller's own record — and the Codex thread's own rollout session_meta.cwd, its pane and its record must all be the requested scratch, not the app-server's repo) -> Codex final v2 evidence. Needs LIVE=1 + ENTWURF_CODEX_APP_SERVER_PID + ENTWURF_CODEX_FRESH_MODEL + ENTWURF_CODEX_FRESH_PI_MODEL, and its ONE stable launch directory answered 'Trust' once in a plain codex -C there: codex records its direct consent per exact directory, so a directory with no answer at all opens a consent screen instead of a first turn. Only THIS gate treats that as a precondition and fails up front as codex-launch-cwd-undecided rather than timing out on a callback — an ordinary entwurf_fresh_call only prints the note and opens the window, because a human can answer the screen. The smoke PRINTS that directory as 'launch-cwd <dir>' and repeats it in the failure as a ready-to-run repair command — do not retype it from memory, and do not write it as \$TMPDIR/... (os.tmpdir() falls back to /tmp when TMPDIR is unset, so that spelling names a different directory in the filesystem root). The PID names the operator-owned app-server, which must sit in a DIFFERENT session from S; the smoke checks/prints all four coordinates, never starts/stops/guesses the app-server/session, never reads screen text, interrupts only its exact still-running Codex turn, and cleans only receipt-named window ids
227
- ./run.sh smoke-mux-lifecycle-live # RELEASE MUST integrated LIVE lifecycle acceptance for mux, through the REAL MCP surface — OUT of pnpm check, needs LIVE=1 and spends model turns (two pi siblings: native + recorded-ACP provider, each resumed once; one Claude Code sibling). tools/call fresh_call -> nonce callback sender envelope -> v2 control send landing in the sibling's own transcript -> resume_call REFUSED while live (window count unchanged) -> stable-handle close (pane gone, socket dead, record kept) -> dormant delivery refused honestly -> public entwurf_resume_call with LAUNCH and OBSERVATION receipts kept apart, same-gid socket alive, zero new citizens, zero lock residue, resumed pane_start_path == RECORD cwd (separate tmux query), transcript byte-identical across the resume -> v2 recall of the pre-close fact. claude-code resume of the still-live sibling refused target-live, no window opened and no lock residue (its dormant same-id resume is deterministic-only, #147 L2). LIVE=1 ./run.sh smoke-mux-lifecycle-live
228
+ ./run.sh smoke-mux-lifecycle-live # RELEASE MUST integrated LIVE lifecycle acceptance for mux, through the REAL MCP surface — OUT of pnpm check, needs LIVE=1 and spends model turns (two pi siblings: native + recorded-ACP provider, each resumed once; one Claude Code sibling). tools/call fresh_call (the native pi cell names a caller-distinct seat and tmux is asked where the pane landed, #108) -> nonce callback sender envelope -> v2 control send landing in the sibling's own transcript -> resume_call REFUSED while live (window count unchanged) -> stable-handle close (pane gone, socket dead, record kept) -> dormant delivery refused honestly -> public entwurf_resume_call with LAUNCH and OBSERVATION receipts kept apart, same-gid socket alive, zero new citizens, zero lock residue, resumed pane_start_path == RECORD cwd (separate tmux query), transcript byte-identical across the resume -> v2 recall of the pre-close fact. claude-code resume of the still-live sibling refused target-live, no window opened and no lock residue (its dormant same-id resume is deterministic-only, #147 L2). LIVE=1 ./run.sh smoke-mux-lifecycle-live
228
229
  ./run.sh check-meta-facts # deterministic gate for the meta-facts projection (#65): drives the REAL CLI — full-record join, parse-before-uniqueness, no-winner duplicates, drift/symlink/invalid-UTF-8 defects in-band, deterministic bytes, exit contract 0/2/3, dispatch+emit reachability
229
230
  ./run.sh check-herdr-plugin # deterministic gate for the #116 M2-b herdr plugin (`plugins/herdr/`): STATIC manifest shape for herdr 0.9.0 + the forbidden sections ([[startup]]/[[events]]/[[actions]]/[[link_handlers]]) asserted absent ([[build]] is check-herdr-plugin-build's subject since #116 M3-b3); BEHAVIOURAL drive of the REAL pane entry against a stub `entwurf` and stub HERDR_BIN_PATH that LOG every call — exactly one `herdr integration status` + one `entwurf peer-facts` + one `herdr agent list` per open, in that order (counted, not claimed), through ONE spawn call site; the four blocks each naming their own owner/observation time with no aggregate verdict; the D3=C fallback reading the activation ledger from the path its WRITER owns (XDG state, never the runtime's data root) and only when the phase is `active`, with ENTWURF_BIN and PATH still winning outright; the citizen axis alone skipping by name when no binary is reachable; every broken-evidence state a named red rather than a crash or an empty table, and a selected herdr atom that is `outdated`/`needs repair` as block [1]'s OWN red (`not installed` stays a reported skip); ambiguous never first-wins, diagnostics shown, activity in its own column, and zero writes to the plugin state/config dirs. No herdr binary, no Entwurf install
230
231
  ./run.sh check-herdr-runtime-bootstrap # deterministic gate for the #116 M3-b1 runtime leaf (`plugins/herdr/lib/runtime-bootstrap.mjs`): the Entwurf-owned stable active root as a REAL directory (nothing is put on PATH — the scoped wiring above names ABSOLUTE commands under it), ownership decided BEFORE the first mkdir, disk facts from lstat (a dangling symlink is not 'absent' and a link into another tree is not a directory), exact name@version + compiled entry + all three required bins present AND executable + a real `check-bridge`, the plugin-owned artifact lock coherent with the checkout, the owned npm cache and --ignore-scripts, a CERTIFIED journal (non-object/array/scalar/blank-identity/phase-contradicting-digest all refused) as the only ownership proof, all EIGHT active/staging/previous combinations named, the last good runtime never lost (torn swap AND corrupt-active beside a good backup), prior provenance carried across an install that may not finish, a failed candidate leaving the running runtime byte-identical, idempotent same-spec reinstall, a journal believed only while the disk backs it, a preflight-then-mutate inverse taking runtime last, and the refusal to record Herdr's commit as ours. NETWORK ZERO, npm ZERO — it drives a FIXTURE package, so the ACTUAL package proof lives in check-pack-install
@@ -240,6 +241,8 @@ Usage:
240
241
  ./run.sh check-entwurf-deliverability # deterministic gate (SE-1/SE-2 slice 2c): conversational-mailbox deliverability predicate — computeMetaReceiverActive (recordBacked ∧ ownerAlive ∧ watchArmed) + mailboxConversationalDeliverable (self-fetch AND active); direct-inject pi refused (SE-1), self-fetch dead/unarmed refused (SE-2); self-address shares the same atom
241
242
  ./run.sh check-native-push-adapter # deterministic gate (봉인 3/8): native-push adapter leaf (antigravity) via a FAKE runner — FULL pid scan (not head -1), dead vs indeterminate, VOLATILE route re-discovery (no cache), send argv+ANTIGRAVITY_LS_ADDRESS env, non-zero exit throws, NO adapter-level retry (executor-owned), resolveNativePushAdapter fail-fast
242
243
  ./run.sh check-native-push-register # deterministic gate (봉인 5): registerNativeConversation (entwurf_register_native core) via fake adapter + isolated mkdtemp store — live probe→CREATE, re-register→ATTACH (same gid, cwd refreshed, no dup), not-live probe→REFUSE (throws, no record), receiver-marker abstinence (보정① source guard)
244
+ ./run.sh check-native-push-paused # #152 mutant execution coordinate: run_vitest over paused.test.ts alone — dispatch, sender replyable and entwurf_register_native refuse a paused backend (antigravity) before any probe. Same file check-tests-beside-behavior already runs.
245
+ ./run.sh check-chain-terminus-publication # mutant execution coordinate: run_vitest over test/chain-terminus-publication.test.ts alone — smoke-entwurf-chain-live's terminus wait ends only on a body AND inbox.signal, inside the same deadline.
243
246
  ./run.sh check-agy-sender-identity # deterministic gate (#46 sender lane): WHO is calling the bridge — real agy hook as a child process writes an antigravity sender marker keyed by its PARENT pid (never on upsert failure), and resolveTrustedMetaSenderIdentity over isolated stores yields 0→null / 1→identity on EITHER backend / two distinct live identities on one owner pid→THROW (never guess, never downgrade to anonymous). This is what turns an agy send from external-mcp/unknown-host into a replyable garden citizen
244
247
  ./run.sh check-package-source-routing # deterministic gate (#29): package-source -> install-root mapping + fail-fast routing (local/git/npm/missing/project/no-source × local+remote, self-root, resume), no backend
245
248
  ./run.sh new-session-id # print one fresh garden id from the generateSessionId SSOT (#50 C2: no launcher injection — the record layer is the consumer; pi mints its own session id)
@@ -338,6 +341,7 @@ Usage:
338
341
  ./run.sh check-node-floor-coherence # binds the Node floor (24+, single axis) across engines.node, run.sh setup preflight, meta-bridge install/doctor judgment logic, clean-host docs, the bridge launcher header, and the CI runner node-version — engines.node is the SSOT, everything else is derived; sweeps tracked contract text for an unregistered declaration
339
342
  ./run.sh check-pack # publish gate (dry-run): npm pack --dry-run + tarball invariants (runtime-critical present, dev residue absent)
340
343
  ./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
344
+ ./run.sh check-pack-argv-matcher # pure self-test of check-pack-install's dispatcher-verb leak refusal (the copilot and codex-app-server cells): a front-loaded or trailing leak in ~260 KB is refused where the retired printf | grep -qF shape misses it (teeth control measured each run), clean output passes silently, a failing grep is refused by name, and both cells still return through it; snapshot-safe qualification oracle, also run first inside check-pack-install
341
345
  ./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
342
346
  ./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
343
347
  ./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 SDK (pins derived from the package.json exact production dependency; 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 + the #130 pi-durable CARRIER on actual npm consumers (Entwurf-only / aligned host / isolated global): the installed verifier, a consumer-contained resolution oracle with a same-sandbox neighbour control, every physical SDK edge at the pin, a mixed tree refused as pi-durable-sdk-mismatch though it still imports, and the public `entwurf pi-durable` reaching the app's own explicit-model refusal before any birth, also with one explicit `--native-module` (the synthetic fixture, initialized first; a reserved extension name refused before anything opens) (offline, sandbox HOME)
@@ -1402,6 +1406,26 @@ check_entwurf_v2_native_push() {
1402
1406
  run_vitest pi-extensions/lib/entwurf-v2-native-push.test.ts
1403
1407
  }
1404
1408
 
1409
+ check_native_push_paused() {
1410
+ # MUTANT EXECUTION COORDINATE, not a discovery path — paused.test.ts is found by
1411
+ # check-tests-beside-behavior like every other beside-behaviour lane. This case exists
1412
+ # because the AGY-NATIVE-PUSH-PAUSED mutant (#152) names it as gate argv; the narrow file
1413
+ # filter keeps that mutant from re-running every beside-behaviour test. It stays inside
1414
+ # check:contracts for the same reason as check-entwurf-v2-native-push above.
1415
+ section "native-push pause policy (#152, mutant execution coordinate)"
1416
+ run_vitest pi-extensions/lib/native-push/paused.test.ts
1417
+ }
1418
+
1419
+ check_chain_terminus_publication() {
1420
+ # MUTANT EXECUTION COORDINATE for the CHAIN-TERMINUS-WAITS-FOR-SIGNAL mutant: run_vitest over
1421
+ # test/chain-terminus-publication.test.ts alone — the terminus wait of smoke-entwurf-chain-live
1422
+ # (scripts/lib/chain-terminus-publication.ts) driven offline through the two-write publication
1423
+ # enqueueMetaMessage produces (.msg first, inbox.signal last). It stays inside check:contracts
1424
+ # because a gate a mutant names must be reachable outside qualification.
1425
+ section "chain terminus publication wait (mutant execution coordinate)"
1426
+ run_vitest test/chain-terminus-publication.test.ts
1427
+ }
1428
+
1405
1429
  check_entwurf_v2_surface() {
1406
1430
  # Deterministic gate for 0.11 Stage 0 step 5d-3a: the ctx-free surface adapter
1407
1431
  # (entwurf-v2-surface.ts) + the entwurf-control.ts wiring contract. Proves the pure parts:
@@ -1693,6 +1717,14 @@ check_pi_durable_native_module() {
1693
1717
  run_vitest pi-extensions/lib/pi-durable-native-module.test.ts
1694
1718
  }
1695
1719
 
1720
+ check_remote_pair_beside() {
1721
+ # #149: the execution coordinate for the declared remote pairs (VERIFY 「Four rules」 rule 3). Not a
1722
+ # discovery path — check-tests-beside-behavior already finds these files. Every remote-pair claim
1723
+ # carries its QK in one of them, and the qualification runner re-invokes a gate argv once per
1724
+ # mutant, so the narrow filter keeps one mutant from re-running every beside test.
1725
+ run_vitest pi-extensions/lib/remote-pair-send.test.ts pi-extensions/lib/remote-pair-receive.test.ts pi-extensions/lib/remote-pair-roundtrip.test.ts pi-extensions/lib/remote-pair-transport.test.ts pi-extensions/lib/meta-mailbox-body.test.ts
1726
+ }
1727
+
1696
1728
  smoke_mux_fresh_call_live() {
1697
1729
  # LIVE acceptance for entwurf_fresh_call — OUT of pnpm check, needs LIVE=1, spends two model
1698
1730
  # turns. The one axis no deterministic gate reaches: a real window, a real runtime, a real first
@@ -4021,6 +4053,10 @@ check_pack() {
4021
4053
  # JS for installed entrypoints. The files array lists pi/pi-durable per FILE, so nothing admits these
4022
4054
  # today; this tripwire is what fails loud the day an allowlist entry would.
4023
4055
  local durable_source_pattern='^pi/pi-durable/(src/|tsconfig\.json$|overlay/runtime-contacts\.patch$)'
4056
+ # #148 S1b: the goldens and generator a beside test reads (pi-extensions/lib/fixtures/) are test input,
4057
+ # not product. `pi-extensions/` ships whole, so the `!pi-extensions/**/fixtures/**` negation is the only
4058
+ # thing keeping them out, and this is its tripwire (same 결합 규칙 as the `.test.ts` pattern above).
4059
+ local test_fixture_pattern='^pi-extensions/(.*/)?fixtures/'
4024
4060
 
4025
4061
  local pass=1 f pat hit
4026
4062
 
@@ -4047,6 +4083,13 @@ check_pack() {
4047
4083
  pass=0
4048
4084
  fi
4049
4085
 
4086
+ hit=$(grep -E "$test_fixture_pattern" <<<"$file_list" || true)
4087
+ if [ -n "$hit" ]; then
4088
+ fail "[check-pack] a beside test's fixtures (pi-extensions/**/fixtures/) ride the tarball — test input is not product; restore the files-array negation:"
4089
+ echo "$hit" | sed 's/^/ /' >&2
4090
+ pass=0
4091
+ fi
4092
+
4050
4093
  # #78 — a pack carries the Windows addon not at all, or as exactly a verified addon + manifest
4051
4094
  # pair (pack_win32_carrier_verdict, proven on owned fixtures by check-pack-win32-carrier).
4052
4095
  # "present" means the carrier verified, never that the build was approved for publication.
@@ -4157,7 +4200,7 @@ check_pack_win32_carrier() {
4157
4200
  # which is exactly what property (1)'s `(_|$)` absorbs; all three shapes are fixtures
4158
4201
  # below so a future suffix change cannot pass vacuously.)
4159
4202
  pack_install_leaked_pi() {
4160
- grep '^@earendil-works+' | grep -Ev '@1\.3\.0(_|$)' || true
4203
+ grep '^@earendil-works+' | grep -Ev '@1\.4\.0(_|$)' || true
4161
4204
  }
4162
4205
 
4163
4206
  # Matcher self-test on SYNTHETIC lookalikes: a healthy install tree cannot exercise either
@@ -4174,18 +4217,18 @@ check_pack_pin_matcher() {
4174
4217
  # Cell 1 — the version boundary. Every measured peer-hash shape is a fixture: the
4175
4218
  # 0.84.4-era `_@modelcontextprotocol+sdk@…`, the `_ws@8.21.3` observed on a real
4176
4219
  # 0.85.1 tree, and `[측정 2026-09-20]` the combined suffix a 0.86.0 install carries.
4177
- # None may leak; the two lookalikes must — a PREFIX-EXTENDED version (`0.86.0-beta.1`,
4220
+ # None may leak; the two lookalikes must — a PREFIX-EXTENDED version (`1.4.0-beta.1`,
4178
4221
  # the prerelease shape that an unanchored match would bless) and an off-pin version.
4179
4222
  matcher_probe=$(printf '%s\n' \
4180
- '@earendil-works+pi-ai@1.3.0' \
4181
- '@earendil-works+pi-ai@1.3.0_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6' \
4182
- '@earendil-works+pi-ai@1.3.0_ws@8.21.3' \
4183
- '@earendil-works+pi-ai@1.3.0_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6__ws@8.21.3_zod@4.3.6' \
4184
- '@earendil-works+pi-ai@1.3.0-beta.1' \
4223
+ '@earendil-works+pi-ai@1.4.0' \
4224
+ '@earendil-works+pi-ai@1.4.0_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6' \
4225
+ '@earendil-works+pi-ai@1.4.0_ws@8.21.3' \
4226
+ '@earendil-works+pi-ai@1.4.0_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6__ws@8.21.3_zod@4.3.6' \
4227
+ '@earendil-works+pi-ai@1.4.0-beta.1' \
4185
4228
  '@earendil-works+pi-agent-core@0.87.1' | pack_install_leaked_pi)
4186
- if [ "$matcher_probe" != '@earendil-works+pi-ai@1.3.0-beta.1
4229
+ if [ "$matcher_probe" != '@earendil-works+pi-ai@1.4.0-beta.1
4187
4230
  @earendil-works+pi-agent-core@0.87.1' ]; then
4188
- fail "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED] the pin-leak matcher must flag the prefix-extended 1.3.0-beta.1 and the off-pin 0.87.1 lookalikes, and pass 1.3.0 bare or with any measured peer-hash — got: ${matcher_probe:-<nothing leaked>}"
4231
+ fail "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED] the pin-leak matcher must flag the prefix-extended 1.4.0-beta.1 and the off-pin 0.87.1 lookalikes, and pass 1.4.0 bare or with any measured peer-hash — got: ${matcher_probe:-<nothing leaked>}"
4189
4232
  return 1
4190
4233
  fi
4191
4234
 
@@ -4193,9 +4236,9 @@ check_pack_pin_matcher() {
4193
4236
  # (0.85.0 onward, still true at 0.99.1). An off-pin chord MUST leak; the pinned one must not. A matcher narrowed
4194
4237
  # back to `^@earendil-works+pi-` sees nothing here and dies at this signature.
4195
4238
  matcher_probe=$(printf '%s\n' \
4196
- '@earendil-works+chord@1.3.0' \
4239
+ '@earendil-works+chord@1.4.0' \
4197
4240
  '@earendil-works+chord@0.87.1' \
4198
- '@earendil-works+pi-ai@1.3.0' | pack_install_leaked_pi)
4241
+ '@earendil-works+pi-ai@1.4.0' | pack_install_leaked_pi)
4199
4242
  if [ "$matcher_probe" != '@earendil-works+chord@0.87.1' ]; then
4200
4243
  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 was 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; `[측정 2026-10-13]` at 1.2.0 pi-durable alone declares it) — got: ${matcher_probe:-<nothing leaked>}"
4201
4244
  return 1
@@ -4204,12 +4247,112 @@ check_pack_pin_matcher() {
4204
4247
  echo "[check-pack-pin-matcher] ok — the pin-leak matcher is version-bounded (lookalikes leak, pinned version passes bare and with either measured peer-hash) and covers the whole @earendil-works closure (chord included)"
4205
4248
  }
4206
4249
 
4250
+ # The dispatcher-verb leak refusal of check-pack-install's copilot and codex-app-server cells: the
4251
+ # vendor stand-in prints one `ARG<…>` line per argument, and the dispatcher verb among them means it
4252
+ # leaked into the vendor's argv. The search it replaces, `printf '%s' "$op_out" | grep -qF …`,
4253
+ # refused only when that pipeline exited 0: bash's printf writes a multi-line value a line at a
4254
+ # time, `-q` closes the pipe at the first match, and pipefail turned the producer's 141 into a pass
4255
+ # (pipefail audit, 2026-10-23: latent, no false green observed in CI). A here-string has no
4256
+ # producer to kill, and the verdict reads the search's own rc: 1 (clean) passes, 0 (leaked) and any
4257
+ # other rc (the search itself failed) refuse with the cell's diagnostic.
4258
+ pack_install_refuse_argv_leak() {
4259
+ local label="$1" out="$2" token="$3" argv_search_rc=0
4260
+ grep -qF -- "$token" <<<"$out" || argv_search_rc=$?
4261
+ case "$argv_search_rc" in
4262
+ 1) return 0 ;;
4263
+ 0)
4264
+ fail "[check-pack-install] installed 'entwurf $label' leaked the dispatcher verb into the vendor's argv:"
4265
+ echo "$out" | tail -8 | sed 's/^/ /' >&2
4266
+ ;;
4267
+ *) fail "[check-pack-install] installed 'entwurf $label': the dispatcher-verb search itself failed (rc=$argv_search_rc) — refused, never read as no leak" ;;
4268
+ esac
4269
+ return 1
4270
+ }
4271
+
4272
+ # Self-test of pack_install_refuse_argv_leak — the SAME verdict both cells return through, never a
4273
+ # re-typed predicate — on synthetic stand-in output: its pass/refuse result and the diagnostic that
4274
+ # names why, one QK per verdict, plus a text check that both cells still return through it.
4275
+ # Snapshot-safe like check-pack-pin-matcher, and run first inside check-pack-install. The large
4276
+ # fixtures put the leak ahead of ~260 KB, past the default 64 KiB pipe buffer. The teeth control
4277
+ # measures, on THIS host, that the retired pipe shape misses it there (grep matched, the pipeline
4278
+ # still failed); a fixture that stops biting is a red of its own, not a vacuous green.
4279
+ check_pack_argv_matcher() {
4280
+ local cop_leak cop_clean cx_leak cx_clean big_tail big_leak_front big_leak_end big_clean
4281
+ local rc out label fx token missed shim bound_copilot bound_codex red=0 teeth=()
4282
+ cop_leak=$'FLAG=[EXTENSIONS]\nARG<copilot>\nARG<-p>\nARG<hi>\nARG<--model>\nARG<auto>\nARG<--yolo>'
4283
+ cop_clean=$'FLAG=[EXTENSIONS]\nARG<-p>\nARG<hi>\nARG<--model>\nARG<auto>\nARG<--yolo>'
4284
+ cx_leak=$'ARG<codex-app-server>\nARG<app-server>\nARG<--listen>\nARG<unix:///h/.codex/app-server.sock>\nARG<--config>\nARG<probe=1>'
4285
+ cx_clean=$'ARG<app-server>\nARG<--listen>\nARG<unix:///h/.codex/app-server.sock>\nARG<--config>\nARG<probe=1>'
4286
+ big_tail=$(seq 1 24000 | sed 's/^/ARG<x/; s/$/>/')
4287
+ big_leak_front=$'FLAG=[EXTENSIONS]\nARG<copilot>\n'"$big_tail"
4288
+ big_leak_end="$big_tail"$'\nARG<codex-app-server>'
4289
+ big_clean=$'FLAG=[EXTENSIONS]\n'"$big_tail"
4290
+
4291
+ # Teeth control: the defect's own signature — grep matched (its PIPESTATUS is 0) yet the pipeline
4292
+ # failed. A grep error is not teeth, and an exact 141 is not required (an inherited ignored
4293
+ # SIGPIPE still makes the producer fail, and the miss is just as real).
4294
+ { printf '%s' "$big_leak_front" | grep -qF -- 'ARG<copilot>'; teeth=("$?" "${PIPESTATUS[@]}"); } || true
4295
+ if [ "${teeth[0]}" -eq 0 ] || [ "${teeth[2]:-unset}" != 0 ]; then
4296
+ fail "[check-pack-argv-matcher] the planted fixture has no teeth here: the retired printf | grep -qF shape did not miss a leak it matched (pipeline=${teeth[0]} PIPESTATUS=${teeth[*]:1}), so the cells below would prove nothing — enlarge it"
4297
+ return 1
4298
+ fi
4299
+
4300
+ missed=""
4301
+ for fx in "copilot cop_leak ARG<copilot>" "codex-app-server cx_leak ARG<codex-app-server>" "copilot big_leak_front ARG<copilot>" "codex-app-server big_leak_end ARG<codex-app-server>"; do
4302
+ read -r label fx token <<<"$fx"
4303
+ rc=0; out=$(pack_install_refuse_argv_leak "$label" "${!fx}" "$token" 2>&1) || rc=$?
4304
+ { [ "$rc" -eq 1 ] && [[ "$out" == *"leaked the dispatcher verb"* ]]; } || missed+=" $fx(rc=$rc)"
4305
+ done
4306
+ if [ -n "$missed" ]; then
4307
+ fail "[QK:PACK-INSTALL-ARGV-LEAK-CAUGHT] a leaked dispatcher verb must be refused as a leak wherever it sits and however long the output — passed or misnamed:$missed"
4308
+ red=1
4309
+ fi
4310
+
4311
+ missed=""
4312
+ for fx in "copilot cop_clean ARG<copilot>" "codex-app-server cx_clean ARG<codex-app-server>" "copilot big_clean ARG<copilot>"; do
4313
+ read -r label fx token <<<"$fx"
4314
+ rc=0; out=$(pack_install_refuse_argv_leak "$label" "${!fx}" "$token" 2>&1) || rc=$?
4315
+ { [ "$rc" -eq 0 ] && [ -z "$out" ]; } || missed+=" $fx(rc=$rc)"
4316
+ done
4317
+ if [ -n "$missed" ]; then
4318
+ fail "[QK:PACK-INSTALL-ARGV-CLEAN-PASSES] stand-in output without the verb must pass silently, small or large — refused:$missed"
4319
+ red=1
4320
+ fi
4321
+
4322
+ # A search that fails is refused, never read as clean: a `grep` that exits 2 on PATH.
4323
+ shim=$(mktemp -d "${TMPDIR:-/tmp}/entwurf-argv-shim.XXXXXX")
4324
+ printf '#!/bin/sh\n: > "%s/reached"\nexit 2\n' "$shim" > "$shim/grep"
4325
+ chmod +x "$shim/grep"
4326
+ rc=0; out=$(PATH="$shim:$PATH"; pack_install_refuse_argv_leak copilot "$cop_clean" 'ARG<copilot>' 2>&1) || rc=$?
4327
+ if [ ! -e "$shim/reached" ]; then
4328
+ rm -rf "$shim"
4329
+ fail "[check-pack-argv-matcher] the failing grep stand-in was never reached, so the search-error cell would prove nothing"
4330
+ return 1
4331
+ fi
4332
+ rm -rf "$shim"
4333
+ if [ "$rc" -ne 1 ] || [[ "$out" != *"search itself failed (rc=2)"* ]]; then
4334
+ fail "[QK:PACK-INSTALL-ARGV-SEARCH-ERROR-REFUSED] a search that fails (grep rc=2) must refuse the cell and say so, never pass it as clean — got rc=$rc: ${out:-<silent>}"
4335
+ red=1
4336
+ fi
4337
+
4338
+ # Both cells must return through this verdict; a reverted cell would leave the cells above green.
4339
+ bound_copilot=$(grep -cxF " pack_install_refuse_argv_leak copilot \"\$op_out\" 'ARG<copilot>' || return 1" "$REPO_DIR/run.sh" || true)
4340
+ bound_codex=$(grep -cxF " pack_install_refuse_argv_leak codex-app-server \"\$op_out\" 'ARG<codex-app-server>' || return 1" "$REPO_DIR/run.sh" || true)
4341
+ if [ "$bound_copilot" != 1 ] || [ "$bound_codex" != 1 ]; then
4342
+ fail "[QK:PACK-INSTALL-ARGV-CELLS-BOUND] the copilot and codex-app-server leak refusals of check-pack-install must each return through pack_install_refuse_argv_leak once (copilot=$bound_copilot codex=$bound_codex)"
4343
+ red=1
4344
+ fi
4345
+
4346
+ [ "$red" -eq 0 ] || return 1
4347
+ echo "[check-pack-argv-matcher] ok — the dispatcher-verb refusal refuses a leak at the front or end of ~260 KB (where the retired pipe shape misses it), passes clean output silently, refuses a failed search by name, and both check-pack-install cells return through it"
4348
+ }
4349
+
4207
4350
  # #130 — the pi-durable carrier proved on ACTUAL npm consumers of the candidate tarball.
4208
4351
  # $1 tarball $2 the gate's npm sandbox $3 an existing Entwurf-only npm consumer root inside it.
4209
4352
  # Cells: L1s (Entwurf alone, $3), L1 (project-local beside an aligned host pi-coding-agent at the
4210
4353
  # pinned SDK version), L2 (an isolated `npm -g --prefix`), each through (a) the INSTALLED verifier
4211
4354
  # (exit 0, prints its own carrier-resolver) and (b) an independent consumer oracle; then the L3
4212
- # NEGATIVE (an override binds pi-durable's pi-ai edge to an older published version: the installed verifier must
4355
+ # NEGATIVE (an override binds pi-durable's pi-codemode edge to an older published version: the installed verifier must
4213
4356
  # refuse it by name, while a bare import still succeeds); then (c) the public launch on L1s.
4214
4357
  # The oracle observes RESOLUTIONS (a resolve hook), not every filesystem read; it asserts each
4215
4358
  # resolved product file lies inside THIS consumer's node_modules — a sandbox ancestor is not the
@@ -4304,13 +4447,18 @@ ORACLE
4304
4447
  echo "[check-pack-install] pi-durable $cell: installed verifier PASS; consumer oracle $out"
4305
4448
  done
4306
4449
 
4307
- printf '%s\n' "{\"name\":\"pd-mismatch\",\"version\":\"1.0.0\",\"private\":true,\"overrides\":{\"@earendil-works/pi-durable\":{\"@earendil-works/pi-ai\":\"1.0.3\"}}}" > "$box/pd-mismatch/package.json"
4450
+ # The wrong edge must still IMPORT, or the control proves a crash rather than a refusal. `[측정 2026-10-22]`
4451
+ # SDK 1.4 pi-durable imports pi-ai's new `utils/identity`, absent from the older pi-ai control versions
4452
+ # checked here (1.0.3 and 1.3.0 both fail to load); pi-telemetry 1.0.3/1.3.0 and chord 1.3.0 lack exports
4453
+ # it imports. pi-codemode 1.3.0 loads, and depends on no SDK member, so this tree carries exactly one
4454
+ # depth-2 mismatch (L4 keeps depth 3).
4455
+ printf '%s\n' "{\"name\":\"pd-mismatch\",\"version\":\"1.0.0\",\"private\":true,\"overrides\":{\"@earendil-works/pi-durable\":{\"@earendil-works/pi-codemode\":\"1.3.0\"}}}" > "$box/pd-mismatch/package.json"
4308
4456
  out=$(cd "$box/pd-mismatch" && npm install "$tgz" --no-audit --no-fund 2>&1) || {
4309
4457
  fail "[check-pack-install] pi-durable L3 (mismatch control) install failed:"; echo "$out" | tail -10 | sed 's/^/ /' >&2; return 1; }
4310
4458
  pkg="$box/pd-mismatch/node_modules/@junghanacs/entwurf"
4311
4459
  rc=0; out=$(cd "$box" && node "$pkg/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js" resolve 2>&1) || rc=$?
4312
- if [ "$rc" -ne 3 ] || ! grep -q "pi-durable-sdk-mismatch: @earendil-works/pi-durable → @earendil-works/pi-ai wants $pin_ver, binds 1.0.3" <<<"$out"; then
4313
- fail "[QK:PACK-INSTALL-DURABLE-MISMATCH-REFUSED] [check-pack-install] pi-durable L3: an installed tree whose pi-durable binds pi-ai 1.0.3 must be refused by name as pi-durable-sdk-mismatch (rc=$rc): $out"
4460
+ if [ "$rc" -ne 3 ] || ! grep -q "pi-durable-sdk-mismatch: @earendil-works/pi-durable → @earendil-works/pi-codemode wants $pin_ver, binds 1.3.0" <<<"$out"; then
4461
+ fail "[QK:PACK-INSTALL-DURABLE-MISMATCH-REFUSED] [check-pack-install] pi-durable L3: an installed tree whose pi-durable binds pi-codemode 1.3.0 must be refused by name as pi-durable-sdk-mismatch (rc=$rc): $out"
4314
4462
  return 1
4315
4463
  fi
4316
4464
  rc=0; out=$(cd "$box" && node --input-type=module -e "import { pathToFileURL } from 'node:url'; await import('$pkg/pi/pi-durable/carrier-resolver.mjs'); const m = await import(pathToFileURL('$pkg/pi/pi-durable/carrier/runtime.js').href); console.log(typeof m.openDurable);" 2>&1) || rc=$?
@@ -4515,6 +4663,7 @@ pack_install_sentinel_untouched() {
4515
4663
 
4516
4664
  _check_pack_install_impl() {
4517
4665
  check_pack_pin_matcher || return 1
4666
+ check_pack_argv_matcher || return 1
4518
4667
  check_pack_consumer_roots || return 1
4519
4668
 
4520
4669
  # Heavy publish gate. Runs the remaining three checks in #13's
@@ -4835,19 +4984,21 @@ _check_pack_install_impl() {
4835
4984
  # `[측정 2026-10-16]` at 1.3.0 the constellation is unchanged: the installed manifests give the
4836
4985
  # same member edges as at 1.2.0, now every one exact 1.3.0 (upstream #10865), and the consumer
4837
4986
  # oracle walked 10 member edges with 0 mismatches (.tmp-verify/145-stage1/08-pack-install.log).
4838
- echo "[check-pack-install] pnpm add into $tmp (with the aligned 1.3.x SDK + chord + codemode + mcp + durable + typebox)"
4987
+ # `[측정 2026-10-22]` at 1.4.0 the nine members are again exact 1.4.0 edges (registry manifests; #148 S0),
4988
+ # and pi-tui 1.4.0 adds one third-party dependency (marked-cjk-friendly 0.1.2) outside the closure.
4989
+ echo "[check-pack-install] pnpm add into $tmp (with the aligned 1.4.x SDK + chord + codemode + mcp + durable + typebox)"
4839
4990
  local install_log
4840
4991
  install_log=$(cd "$tmp" && pnpm add \
4841
4992
  "$tgz_path" \
4842
- "@earendil-works/pi-ai@1.3.0" \
4843
- "@earendil-works/pi-coding-agent@1.3.0" \
4844
- "@earendil-works/pi-tui@1.3.0" \
4845
- "@earendil-works/pi-agent-core@1.3.0" \
4846
- "@earendil-works/pi-telemetry@1.3.0" \
4847
- "@earendil-works/chord@1.3.0" \
4848
- "@earendil-works/pi-codemode@1.3.0" \
4849
- "@earendil-works/pi-mcp@1.3.0" \
4850
- "@earendil-works/pi-durable@1.3.0" \
4993
+ "@earendil-works/pi-ai@1.4.0" \
4994
+ "@earendil-works/pi-coding-agent@1.4.0" \
4995
+ "@earendil-works/pi-tui@1.4.0" \
4996
+ "@earendil-works/pi-agent-core@1.4.0" \
4997
+ "@earendil-works/pi-telemetry@1.4.0" \
4998
+ "@earendil-works/chord@1.4.0" \
4999
+ "@earendil-works/pi-codemode@1.4.0" \
5000
+ "@earendil-works/pi-mcp@1.4.0" \
5001
+ "@earendil-works/pi-durable@1.4.0" \
4851
5002
  "typebox@latest" \
4852
5003
  --ignore-workspace --ignore-scripts 2>&1) || {
4853
5004
  fail "[check-pack-install] pnpm add failed:"
@@ -4863,11 +5014,11 @@ _check_pack_install_impl() {
4863
5014
  local leaked_pi
4864
5015
  leaked_pi=$(ls "$tmp/node_modules/.pnpm" 2>/dev/null | pack_install_leaked_pi)
4865
5016
  if [ -n "$leaked_pi" ]; then
4866
- fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 1.3.0):"
5017
+ fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 1.4.0):"
4867
5018
  printf '%s\n' "$leaked_pi" | sed 's/^/ /' >&2
4868
5019
  return 1
4869
5020
  fi
4870
- echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works package is 1.3.0 (chord and pi-durable included)"
5021
+ echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works package is 1.4.0 (chord and pi-durable included)"
4871
5022
 
4872
5023
  # Resolve the installed package.json and confirm pi.extensions
4873
5024
  # arrived intact. If pi.extensions is empty or missing, the
@@ -5921,11 +6072,9 @@ JS
5921
6072
  return 1
5922
6073
  fi
5923
6074
  done
5924
- if printf '%s' "$op_out" | grep -qF 'ARG<copilot>'; then
5925
- fail "[check-pack-install] installed 'entwurf copilot' leaked the dispatcher verb into the vendor's argv:"
5926
- echo "$op_out" | tail -8 | sed 's/^/ /' >&2
5927
- return 1
5928
- fi
6075
+ # A refusal on a match whose search cannot read a SIGPIPE as "no leak"; the verdict is the
6076
+ # leaf check-pack-argv-matcher drives.
6077
+ pack_install_refuse_argv_leak copilot "$op_out" 'ARG<copilot>' || return 1
5929
6078
  echo "[check-pack-install] installed 'entwurf copilot' reached the vendor with the scan flag and the managed argv"
5930
6079
 
5931
6080
  # #95 — the managed app-server SPELLING, from the INSTALLED package. Same packaging risk as
@@ -5972,11 +6121,7 @@ process.stdout.write(resolveCodexDefaultSocketPath(process.env));
5972
6121
  return 1
5973
6122
  fi
5974
6123
  done
5975
- if printf '%s' "$op_out" | grep -qF 'ARG<codex-app-server>'; then
5976
- fail "[check-pack-install] installed 'entwurf codex-app-server' leaked the dispatcher verb into the vendor's argv:"
5977
- echo "$op_out" | tail -8 | sed 's/^/ /' >&2
5978
- return 1
5979
- fi
6124
+ pack_install_refuse_argv_leak codex-app-server "$op_out" 'ARG<codex-app-server>' || return 1
5980
6125
  echo "[check-pack-install] installed 'entwurf codex-app-server' reached the vendor at the INSTALLED resolver's address ($cx_want)"
5981
6126
 
5982
6127
  # The generation verb must reach its verdict from under node_modules. On the
@@ -6623,7 +6768,11 @@ setup_all() {
6623
6768
  else
6624
6769
  bins_rc=0; expose_dev_bin || bins_rc=$?
6625
6770
  if [ "$bins_rc" -eq 0 ]; then
6626
- setup_result bins PASS "source operator + helper bins exposed and certified (PATH winner: this checkout)"
6771
+ setup_result bins PASS "source operator + helper bins exposed and certified; no other root in this PATH or the npm/pnpm global bin dir (other shells not covered)"
6772
+ elif [ "$bins_rc" -eq 5 ]; then
6773
+ setup_result bins FAIL "another entwurf root is still selectable (named above; nothing removed) — remove it, then re-run setup"
6774
+ elif [ "$bins_rc" -eq 6 ]; then
6775
+ setup_result bins FAIL "the package manifest or a package manager's global bin dir could not be inspected (named above) — refused, not read as clean"
6627
6776
  elif [ "$bins_rc" -eq 3 ]; then
6628
6777
  setup_result bins FAIL "foreign helper bin(s) refused (named above; every unit was attempted) — repair them, then re-run setup"
6629
6778
  else
@@ -7012,7 +7161,13 @@ expose_dev_bin() {
7012
7161
  return 1
7013
7162
  fi
7014
7163
 
7015
- [ "$rc" -eq 0 ] && return 0
7164
+ # A certified winner is not yet a developer install: a later PATH entry or a package manager's
7165
+ # global bin dir may still hold another root under the same names, which a shell with another
7166
+ # PATH order (non-interactive SSH) selects. exclusive names it (5) or refuses an uninspectable
7167
+ # package manifest or package manager (6); nothing is removed.
7168
+ local ex_rc=0
7169
+ bash "$REPO_DIR/scripts/dev-bin.sh" exclusive || ex_rc=$?
7170
+ [ "$rc" -eq 0 ] && return "$ex_rc"
7016
7171
  if [ "$rc" -eq 3 ]; then
7017
7172
  echo "[setup] dev-bin: foreign helper bin path(s) refused, never clobbered — the refused names are in the summary above." >&2
7018
7173
  echo "[setup] Every unit was attempted independently; the source operator itself is certified." >&2
@@ -7576,6 +7731,12 @@ case "$cmd" in
7576
7731
  check-entwurf-v2-native-push)
7577
7732
  check_entwurf_v2_native_push
7578
7733
  ;;
7734
+ check-native-push-paused)
7735
+ check_native_push_paused
7736
+ ;;
7737
+ check-chain-terminus-publication)
7738
+ check_chain_terminus_publication
7739
+ ;;
7579
7740
  check-entwurf-control-rpc)
7580
7741
  check_entwurf_control_rpc
7581
7742
  ;;
@@ -7630,6 +7791,9 @@ case "$cmd" in
7630
7791
  check-pi-durable-native-module)
7631
7792
  check_pi_durable_native_module
7632
7793
  ;;
7794
+ check-remote-pair-beside)
7795
+ check_remote_pair_beside
7796
+ ;;
7633
7797
  check-mux-resume-call)
7634
7798
  check_mux_resume_call
7635
7799
  ;;
@@ -7976,12 +8140,14 @@ case "$cmd" in
7976
8140
  (cd "$REPO_DIR" && bash scripts/smoke-pi-provider-state.sh)
7977
8141
  ;;
7978
8142
  smoke-agy-native-push-live)
7979
- # 봉인 8 LIVE acceptance gate for the native-push (agy) delivery rail. Drives the REAL
7980
- # antigravity adapter + register core + runEntwurfV2 (production deps) against a live agy
7981
- # conversation (AGY_CONVERSATION_ID): probe route, register create/attach idempotency,
7982
- # fire→native-push delivered, bogus-conv probe-indeterminate reject.
7983
- # Meta-store is isolated to a temp dir (only the agy round-trip is real); honest SKIP when
7984
- # LIVE!=1. doctor-static preflight FAILs before the agy bridge is wired (③).
8143
+ # PAUSED (#152): the script exits 2 [entwurf:paused] before it reads LIVE or
8144
+ # AGY_CONVERSATION_ID — not a PASS and not a SKIP. The body after that exit is the 봉인 8
8145
+ # LIVE acceptance for the native-push (agy) rail and runs only once a separate change lifts
8146
+ # the pause: REAL antigravity adapter + register core + runEntwurfV2 (production deps)
8147
+ # against a live agy conversation (probe route, register create/attach idempotency,
8148
+ # fire→native-push delivered, bogus-conv probe-indeterminate reject), meta-store isolated
8149
+ # to a temp dir, honest SKIP when LIVE!=1, doctor-static preflight FAIL before the agy
8150
+ # bridge is wired (③).
7985
8151
  run_ts scripts/smoke-agy-native-push-live.ts
7986
8152
  ;;
7987
8153
  smoke-codex-native-push-live)
@@ -8517,8 +8683,8 @@ case "$cmd" in
8517
8683
  run_ts scripts/check-remote-pair-installed.ts "$@"
8518
8684
  ;;
8519
8685
  remote-pair-receive)
8520
- # #107 rail 1: the receiving host's entry for one declared remote pair. The pair
8521
- # check runs before the v2 dispatch, so a claim outside the pair mutates nothing;
8686
+ # #107 rail 1 / #149: the receiving host's entry for its declared remote pairs. The pair
8687
+ # check runs before the v2 dispatch, so a claim outside every declared pair mutates nothing;
8522
8688
  # a match reaches the same record/owner/deliverability checks every local send does.
8523
8689
  # Installed, run_ts runs its compiled twin; the sending host's ssh reaches it through the bin.
8524
8690
  shift || true
@@ -8705,6 +8871,9 @@ case "$cmd" in
8705
8871
  check-pack-pin-matcher)
8706
8872
  check_pack_pin_matcher
8707
8873
  ;;
8874
+ check-pack-argv-matcher)
8875
+ check_pack_argv_matcher
8876
+ ;;
8708
8877
  check-pack-win32-carrier)
8709
8878
  check_pack_win32_carrier
8710
8879
  ;;
@@ -442,7 +442,7 @@ try {
442
442
  }
443
443
 
444
444
  ok(
445
- "antigravity is a native-push backend → its replyable comes from the adapter probe",
445
+ "antigravity stays a native-push backend (its reply rail is native-push; while #152 pauses it, that rail answers replyable:false by policy)",
446
446
  nativePushSupported("antigravity"),
447
447
  );
448
448
  ok(
@@ -455,10 +455,12 @@ try {
455
455
  // probe can only be a broken registry or a probe that could not run at all. Folding that into
456
456
  // replyable:false would tell the receiver a lie about this sender AND bury the defect — the
457
457
  // exact Crash-Don't-Warn shape this lane exists to remove. It must propagate.
458
+ // The mapping is exercised on codex, the native-push backend that is not paused: antigravity is
459
+ // paused (#152) and answers false by policy without resolving an adapter (paused.test.ts).
458
460
  {
459
461
  const identity: Pick<MetaIdentity, "backend" | "nativeSessionId"> = {
460
- backend: "antigravity",
461
- nativeSessionId: "conv-x",
462
+ backend: "codex",
463
+ nativeSessionId: "thread-x",
462
464
  };
463
465
  const adapterWith = (probe: NativePushAdapter["probe"]) => ({ resolveAdapter: () => ({ probe }) });
464
466
 
@@ -466,7 +468,7 @@ try {
466
468
  "probe alive → replyable true",
467
469
  (await probeNativeSenderAlive(
468
470
  identity,
469
- adapterWith(async () => ({ status: "alive", route: { backend: "antigravity", lsAddress: "127.0.0.1:1" } })),
471
+ adapterWith(async () => ({ status: "alive", route: { backend: "codex", socketPath: "/tmp/gate-codex.sock" } })),
470
472
  )) === true,
471
473
  );
472
474
 
@@ -112,7 +112,7 @@ function row(facts: SelfAddressabilityFacts): { replyable: boolean; socketState:
112
112
  ok("meta/self-fetch + record but undefined owner/watch → fail-closed NOT replyable", partial.replyable === false);
113
113
  }
114
114
 
115
- // meta-session / NATIVE-PUSH rail (antigravity): a SEPARATE axis — recordBacked ∧ probeAlive.
115
+ // meta-session / NATIVE-PUSH rail (Codex; antigravity is paused, #152): a SEPARATE axis — recordBacked ∧ probeAlive.
116
116
  // The two rails must not be able to borrow each other's facts (보정①): a mailbox signal
117
117
  // deciding an agy reply, or a probe deciding a Claude reply, is a category error.
118
118
  {
@@ -124,8 +124,8 @@ function row(facts: SelfAddressabilityFacts): { replyable: boolean; socketState:
124
124
  const reachable = row(push({ recordBacked: true, probeAlive: true }));
125
125
  ok("meta/native-push + record + probe-alive → replyable", reachable.replyable === true);
126
126
 
127
- // THE ROW THIS RAIL EXISTS FOR: an agy citizen never arms a mailbox watch. Under the
128
- // self-fetch atom it would be un-replyable forever; on its own rail it is reachable.
127
+ // THE ROW THIS RAIL EXISTS FOR: a native-push citizen never arms a mailbox watch. Under
128
+ // the self-fetch atom it would be un-replyable forever; on its own rail it is reachable.
129
129
  ok(
130
130
  "meta/native-push + record + probe-alive + watch NEVER armed → STILL replyable (no mailbox axis leak)",
131
131
  row(push({ recordBacked: true, probeAlive: true, watchArmed: false })).replyable === true,
@@ -156,6 +156,37 @@ function row(facts: SelfAddressabilityFacts): { replyable: boolean; socketState:
156
156
  );
157
157
  }
158
158
 
159
+ // #152 policy row: a PAUSED native-push backend is decided BEFORE the pure record+probe
160
+ // predicate. Record-backed + paused → not replyable with a reason naming the pause, even if
161
+ // a probe fact says alive (none ran for this answer). The record axis keeps precedence: a
162
+ // record-less citizen keeps its own reason and is never relabelled as paused.
163
+ {
164
+ const paused = computeSelfAddressability({
165
+ origin: "meta-session",
166
+ metaDeliveryDomain: "native-push",
167
+ recordBacked: true,
168
+ probeAlive: true,
169
+ nativePushPaused: true,
170
+ });
171
+ ok(
172
+ "meta/native-push + record + PAUSED (#152) → NOT replyable, socket none",
173
+ paused.replyable === false && paused.socketState === "none",
174
+ );
175
+ ok("meta/native-push + record + PAUSED (#152) → the reason names the pause", /paused \(#152\)/.test(paused.reason));
176
+ const recordless = computeSelfAddressability({
177
+ origin: "meta-session",
178
+ metaDeliveryDomain: "native-push",
179
+ recordBacked: false,
180
+ nativePushPaused: true,
181
+ });
182
+ ok(
183
+ "meta/native-push + NO record + paused → the record-less reason wins (pause never masks it)",
184
+ recordless.replyable === false &&
185
+ /no backing meta-record/.test(recordless.reason) &&
186
+ !/paused/.test(recordless.reason),
187
+ );
188
+ }
189
+
159
190
  // meta-session / NONE rail: no inbound transport at all. Mailbox facts and a live probe
160
191
  // cannot buy replyability — there is nothing to land on. Distinct from the unsupplied
161
192
  // (undefined) domain row below, which stays fail-closed for a caller that forgot the axis.