@junghanacs/entwurf 0.30.1 → 0.32.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.
- package/AGENTS.md +4 -2
- package/CHANGELOG.md +194 -0
- package/DELIVERY.md +1 -0
- package/README.md +64 -14
- package/VERIFY.md +36 -1
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +1 -1
- package/docs/setup-clean-host.md +121 -4
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +7 -7
- package/mcp/entwurf-bridge/dist/pi-extensions/entwurf-capabilities.json +1 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-deliverability.js +7 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +34 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +7 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +28 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +28 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +92 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js +236 -0
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +746 -0
- package/mcp/entwurf-bridge/src/index.ts +7 -7
- package/mcp/entwurf-bridge/tsconfig.build.json +8 -1
- package/package.json +19 -19
- package/pi/entwurf-capabilities.json +1 -0
- package/pi/pi-durable/bootstrap.mjs +404 -0
- package/pi/pi-durable/carrier/LICENSE +21 -0
- package/pi/pi-durable/carrier/harness-setup.js +123 -0
- package/pi/pi-durable/carrier/prompt.js +74 -0
- package/pi/pi-durable/carrier/runtime.js +402 -0
- package/pi/pi-durable/carrier/sessions.js +56 -0
- package/pi/pi-durable/carrier/subagent.js +55 -0
- package/pi/pi-durable/carrier/tui.js +655 -0
- package/pi/pi-durable/carrier-relocation.mjs +160 -0
- package/pi/pi-durable/carrier-resolver.mjs +38 -0
- package/pi/pi-durable/overlay/runtime-contacts.patch +91 -0
- package/pi/pi-durable/overlay/upstream-pin.json +108 -0
- package/pi-extensions/lib/entwurf-deliverability.ts +7 -1
- package/pi-extensions/lib/fresh-call-composition.ts +42 -7
- package/pi-extensions/lib/meta-sender-identity.ts +7 -1
- package/pi-extensions/lib/meta-session.ts +28 -2
- package/pi-extensions/lib/mux-fresh-call.ts +32 -2
- package/pi-extensions/lib/pi-durable-fresh-preflight.ts +113 -0
- package/pi-extensions/lib/pi-durable-runtime.ts +318 -0
- package/pi-extensions/meta-bridge-pi-durable.ts +1024 -0
- package/run.sh +624 -184
- package/scripts/check-entwurf-bridge-pi-free.ts +7 -5
- package/scripts/check-fresh-call-dispatch.ts +18 -0
- package/scripts/check-gate-qualification.ts +87 -34
- package/scripts/check-herdr-activation.ts +5 -4
- package/scripts/check-pi-durable-contact.ts +767 -0
- package/scripts/check-pi-durable-receive.ts +1079 -0
- package/scripts/check-pi-durable-send.ts +1821 -0
- package/scripts/emit-pi-durable-carrier.ts +355 -0
- package/scripts/lib/mutation-qualify.ts +21 -45
- package/scripts/lib/pi-durable-fresh-live-receipts.ts +139 -0
- package/scripts/lib/pi-durable-operator-guard.ts +89 -0
- package/scripts/lib/pi-durable-r1.ts +100 -0
- package/scripts/lib/pi-durable-reap.ts +27 -0
- package/scripts/lib/pi-durable-receive.ts +137 -0
- package/scripts/lib/qualification-receipt.ts +7 -11
- package/scripts/mutants/control-send-receipt.json +1 -1
- package/scripts/mutants/copilot-birth.json +2 -2
- package/scripts/mutants/gate-qualification.json +13 -0
- package/scripts/mutants/meta-hook-session-switch.json +4 -2
- package/scripts/mutants/mux-fresh-call.json +69 -3
- package/scripts/mutants/omp-birth.json +2 -2
- package/scripts/mutants/omp-fresh.json +6 -4
- package/scripts/mutants/pack-install.json +3 -3
- package/scripts/mutants/pi-durable.json +723 -0
- package/scripts/mutants/pi-floor.json +24 -2
- package/scripts/pi-durable-contact-host.mjs +105 -0
- package/scripts/pi-durable-receive-host.mjs +127 -0
- package/scripts/pi-durable-send-host.mjs +284 -0
- package/scripts/smoke-herdr-raw-install-live.sh +1 -1
- package/scripts/smoke-pi-durable-fresh-live.ts +624 -0
- package/scripts/smoke-setup-verdict.sh +22 -3
- package/scripts/smoke-user-scope-citizen.sh +1 -1
- 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 THIS checkout's verified carrier (pi/pi-durable/carrier/ under its resolver, against the checkout's pinned pi SDK set — refused by name first) 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 input 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 five pi-durable beside tests (adapter, carrier/SDK-set verifier, preflight, owner join, carrier relocation/resolver/emitter) — 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 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)
|
|
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/#130: the MANAGED durable launch. Verifies this package's pi-durable CARRIER (pi/pi-durable/carrier/, the pinned upstream app emitted by the maintainer) and the pi SDK set it resolves — every declared member/direct edge at exactly the pinned version — refusing by name (package-incomplete / carrier-drift / sdk-absent / sdk-mismatch, exit 3), then exec()s node under the carrier 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. Nothing outside the package is located, installed or repaired
|
|
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
|
|
@@ -276,13 +282,13 @@ Usage:
|
|
|
276
282
|
./run.sh peer-facts # #116 M2-a owner-normalized READ-ONLY peer projection: the same listEntwurfFacts provider through the same renderer entwurf_peers uses, emitted as deterministic JSON {schemaVersion:1, storeDir, peers, diagnostics} so a consumer never re-implements the placement join (whose pi half is a measured vendor floor). NOT the same bytes as an entwurf_peers call and deliberately so: that surface is a human listing (observationLimit, text over the wire), this is a machine projection with UNBOUNDED observation, because a rationed row says `unobserved` and a machine cannot tell that from "nobody could look". `placement` stays a STRUCTURED tagged union ({kind:"herdr-pane",paneId} / unobserved / none / ambiguous) — never the human `herdr <pane>` string. Publishes NO socket coordinate (ENTWURF_DIR selects what is PROBED, #50 C4) and NO herdr agent_status / interactive_ready / screen text. One placement read per listing, no watcher. EXIT: 0 readable (diagnostics in-band; missing store = empty), 2 usage, 3 unreadable ([dir])
|
|
277
283
|
./run.sh meta-bridge-managed-keys # 0.10.0 meta-bridge: print the SSOT of settings keys entwurf OWNS (consumers read this to stay disjoint — keyset-owner invariant)
|
|
278
284
|
./run.sh check-keyset-overlap <fragment.json...> # 0.10.0 meta-bridge: PREVENTIVE keyset guard — fail if a consumer fragment collides with any pi-owned key (cross-repo; not in pnpm check)
|
|
279
|
-
./run.sh check-dep-versions # local deterministic check that the pi pin agrees across package.json (
|
|
285
|
+
./run.sh check-dep-versions # local deterministic check that the pi pin agrees across the durable pin's SDK set, package.json (exact production dependencies, no Earendil dev/peer duplicate), run.sh (install pins), the workspace closure and the baseline docs (README/ROADMAP/setup-clean-host/demo/VERIFY/herdr/acp)
|
|
280
286
|
./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
|
|
281
287
|
./run.sh check-pack # publish gate (dry-run): npm pack --dry-run + tarball invariants (runtime-critical present, dev residue absent)
|
|
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
|
|
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 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)
|
|
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)
|
|
@@ -449,17 +455,12 @@ preflight_dep_integrity() {
|
|
|
449
455
|
# still catching a pnpm dir-move that left the symlink store dangling
|
|
450
456
|
# (accessSync follows the link).
|
|
451
457
|
#
|
|
452
|
-
# Probe set = the
|
|
453
|
-
#
|
|
454
|
-
#
|
|
455
|
-
# of
|
|
456
|
-
#
|
|
457
|
-
#
|
|
458
|
-
# neutral npm/pnpm install — nothing supplies it. The optional peer is simply
|
|
459
|
-
# unresolved (entwurf-preflight.ts:51), and the project-trust lane that needs it
|
|
460
|
-
# is dead on that host. Excluding the trio from this probe is still right (a hard
|
|
461
|
-
# require would reject every consumer install), but it is a KNOWN GAP, not proof
|
|
462
|
-
# that the runtime is present by another route.
|
|
458
|
+
# Probe set = the non-Earendil bundled runtime `dependencies` below. The
|
|
459
|
+
# `@earendil-works/*` SDK set has been an exact production dependency since #130
|
|
460
|
+
# (before that it was an optional peer trio this probe could not require), and #130
|
|
461
|
+
# left it out of this probe: its installed integrity — every edge at the exact pin —
|
|
462
|
+
# is the pi-durable carrier verifier's subject (setup's pi-durable row and
|
|
463
|
+
# `entwurf pi-durable`), not a presence probe's.
|
|
463
464
|
# pi runtime presence/version is covered by check-pi-runtime-version /
|
|
464
465
|
# check-pi-import-surface, neither of which runs on a consumer host.
|
|
465
466
|
local probe=(
|
|
@@ -719,7 +720,7 @@ install_user_scope() {
|
|
|
719
720
|
# scripts/herdr-plugin-activate.mjs → `run.sh install-user-scope --plugin-runtime …`) and had no
|
|
720
721
|
# version check at all, so a host pi setup would name as FAIL got its citizen written here in
|
|
721
722
|
# silence. Both doors now tell the same truth, off the SAME derived range — `pi_supported_range`
|
|
722
|
-
# reads the
|
|
723
|
+
# reads the pinned SDK generation, so there is one range in the repo and no second parser.
|
|
723
724
|
#
|
|
724
725
|
# ABSENT pi is deliberately NOT changed here. This verb is called by an activation that has
|
|
725
726
|
# already decided pi is one of its backends; inventing a presence verdict at the writer would put
|
|
@@ -968,6 +969,19 @@ smoke_omp_fresh_live() {
|
|
|
968
969
|
run_ts scripts/smoke-omp-fresh-live.ts
|
|
969
970
|
}
|
|
970
971
|
|
|
972
|
+
smoke_pi_durable_fresh_live() {
|
|
973
|
+
# #129 first-admission acceptance, RELEASE MUST: docs/adding-a-harness.md step 9 clause 7 and
|
|
974
|
+
# the two cross-harness dispatch receipts. A collector fixture opens a REAL visible Pi through
|
|
975
|
+
# the public entwurf_fresh_call; Pi's live turn opens pi-durable through the managed verb; pd's
|
|
976
|
+
# first action calls back into Pi by exact nonce; Pi's live turn → pd (mailbox, drained by pd's
|
|
977
|
+
# live turn) and pd's live turn → Pi (control socket), relayed to the fixture. pd's actual
|
|
978
|
+
# offering and model are read from its native store via an online node:sqlite backup. Private
|
|
979
|
+
# tmux server; REAL meta roots (one Pi + one pd minted, asserted); records/transcripts/DB kept.
|
|
980
|
+
# pi-durable absent from FRESH_CALL_BACKENDS or without a mailbox rail, or LIVE!=1, is a
|
|
981
|
+
# protocol SKIP; with LIVE=1 every missing prerequisite (a verified carrier, pi, tmux, bridge) FAILs.
|
|
982
|
+
run_ts scripts/smoke-pi-durable-fresh-live.ts
|
|
983
|
+
}
|
|
984
|
+
|
|
971
985
|
smoke_codex_fresh_live() {
|
|
972
986
|
# #95 first-admission RELEASE MUST. The explicit operator-owned app-server PID is the
|
|
973
987
|
# seat authority: validate its Codex app-server argv and /proc TMUX/TMUX_PANE, then use
|
|
@@ -1283,8 +1297,9 @@ check_entwurf_bridge_boot() {
|
|
|
1283
1297
|
|
|
1284
1298
|
check_entwurf_bridge_pi_free() {
|
|
1285
1299
|
# 0.12.1 A-gate (static half): the entwurf-bridge MCP server must boot WITHOUT any
|
|
1286
|
-
# pi package. entwurf is a harness-neutral npm package; pi is one
|
|
1287
|
-
#
|
|
1300
|
+
# pi package. entwurf is a harness-neutral npm package; pi is one adapter lane, not a
|
|
1301
|
+
# boot dependency — since #130 the pi SDK is installed (the pi-durable carrier's), but
|
|
1302
|
+
# the bridge must still not import it. Walks the EAGER static value-import closure of
|
|
1288
1303
|
# mcp/entwurf-bridge/src/index.ts and fails if any reachable module statically
|
|
1289
1304
|
# value-imports @earendil-works/pi-*. Type-only imports and dynamic `await import()`
|
|
1290
1305
|
# (the intended lazy preflight boundary) are excluded — the runtime boot smoke is the
|
|
@@ -1492,7 +1507,22 @@ check_mux_fresh_call() {
|
|
|
1492
1507
|
# codex-caller-seat rides here for the same reason as the two preflights: the leaf exists
|
|
1493
1508
|
# only as freshCall's Codex placement input (#95 lane B), so certifying it apart from the
|
|
1494
1509
|
# composition it feeds would let the two halves of one seat decision drift.
|
|
1495
|
-
|
|
1510
|
+
# pi-durable-fresh-live-receipts rides here for tmux-coordinate-row's reason: it is the
|
|
1511
|
+
# deterministic half of a LIVE sibling (smoke-pi-durable-fresh-live, #129) — the oracles that judge
|
|
1512
|
+
# that gate's receipts, proven on inline rows, so no model turn and no durable host is needed.
|
|
1513
|
+
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
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
check_pi_durable_beside() {
|
|
1517
|
+
# #129: the pi-durable qualification lane's execution coordinate (VERIFY 「Four rules」 rule 3). It is
|
|
1518
|
+
# not a discovery path — check-tests-beside-behavior already finds these files. It exists because the
|
|
1519
|
+
# qualification runner re-invokes a gate argv once per mutant, and the narrow filter keeps one mutant
|
|
1520
|
+
# from re-running every beside test. The six files are one contact: the adapter and its packaged
|
|
1521
|
+
# bootstrap, the installed carrier/SDK-set verifier (which also reads the three checkout gates as
|
|
1522
|
+
# SOURCE), the fresh preflight, the owner join that admits the durable host, the carrier's
|
|
1523
|
+
# relocation grammar, resolver and maintainer emitter (#130), and the bootstrap's explicit
|
|
1524
|
+
# --native-module ingress (#130 P2).
|
|
1525
|
+
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 pi-extensions/lib/pi-durable-carrier.test.ts pi-extensions/lib/pi-durable-native-module.test.ts
|
|
1496
1526
|
}
|
|
1497
1527
|
|
|
1498
1528
|
smoke_mux_fresh_call_live() {
|
|
@@ -2168,67 +2198,57 @@ assert.equal(geminiBundled, undefined, 'package.json must not bundle @google/gem
|
|
|
2168
2198
|
// heredoc (which is indented, so won't match `^...`).
|
|
2169
2199
|
const runSh = readFileSync('run.sh', 'utf8');
|
|
2170
2200
|
|
|
2171
|
-
// pi
|
|
2172
|
-
//
|
|
2173
|
-
//
|
|
2174
|
-
//
|
|
2175
|
-
//
|
|
2176
|
-
//
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
const
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2201
|
+
// pi SDK alignment (#26 / 0.8.0 dep-alignment gate; #130 production set). Since 0.32 the pi SDK is
|
|
2202
|
+
// a PRODUCTION dependency set — the pi-durable carrier runs against it — declared once, exactly, in
|
|
2203
|
+
// pi/pi-durable/overlay/upstream-pin.json (`sdk.members` + `sdk.direct`). package.json must carry
|
|
2204
|
+
// exactly that set in `dependencies`, and no @earendil-works package as a dev, peer or optional
|
|
2205
|
+
// dependency beside it: one name in two dependency classes is install behaviour nothing here has
|
|
2206
|
+
// measured. The members are one SDK generation, and that version is the FLOOR every other surface
|
|
2207
|
+
// below names (the check-pack-install constellation, the workspace closure, the docs) — the host
|
|
2208
|
+
// range `>=<floor> <<major>.<minor+1>` is derived from it, never retyped.
|
|
2209
|
+
const durablePin = JSON.parse(readFileSync('pi/pi-durable/overlay/upstream-pin.json', 'utf8'));
|
|
2210
|
+
const sdkMembers = durablePin.sdk?.members ?? {};
|
|
2211
|
+
const sdkDirect = durablePin.sdk?.direct ?? {};
|
|
2212
|
+
const piAi = sdkMembers['@earendil-works/pi-ai'];
|
|
2213
|
+
assert.ok(/^\d+\.\d+\.\d+$/.test(piAi ?? ''), 'pi/pi-durable/overlay/upstream-pin.json sdk.members must pin @earendil-works/pi-ai exactly');
|
|
2214
|
+
// #120 P2: pi-agent-core is DIRECTLY imported by test/pi-queue.oracle.test.ts, which proves what the
|
|
2215
|
+
// control-socket receipt says about a receiver's queues. An oracle pinned to a DIFFERENT pi than the
|
|
2216
|
+
// one this repo targets would answer honestly about a pi nobody runs, so its exact pin moves with
|
|
2217
|
+
// the floor or the floor is not a floor.
|
|
2218
|
+
const piCore = pkg.dependencies?.['@earendil-works/pi-agent-core'];
|
|
2186
2219
|
assert.equal(piCore, piAi,
|
|
2187
2220
|
`[QK:PI-CORE-PIN-TRACKS-FLOOR] @earendil-works/pi-agent-core (${piCore}) must match @earendil-works/pi-ai (${piAi}) — the queue oracle imports it directly to prove what the control-socket receipt claims about a receiver's queues, so a pin that drifts off the floor would answer honestly about a pi nobody here runs`);
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2221
|
+
for (const [name, ver] of Object.entries(sdkMembers)) {
|
|
2222
|
+
assert.equal(ver, piAi, `pi/pi-durable/overlay/upstream-pin.json: sdk member ${name}@${ver} is not the one SDK generation ${piAi}`);
|
|
2223
|
+
}
|
|
2224
|
+
for (const [name, ver] of Object.entries({ ...sdkMembers, ...sdkDirect })) {
|
|
2225
|
+
assert.equal(pkg.dependencies?.[name], ver,
|
|
2226
|
+
`[QK:PI-SDK-PRODUCTION-SET] package.json dependencies ${name} (${pkg.dependencies?.[name]}) must be exactly the pinned ${ver} (pi/pi-durable/overlay/upstream-pin.json sdk) — the carrier runs against the package's own production set, not whatever a host provides`);
|
|
2227
|
+
}
|
|
2228
|
+
for (const name of Object.keys(pkg.dependencies ?? {}).filter((n) => n.startsWith('@earendil-works/'))) {
|
|
2229
|
+
assert.ok(name in sdkMembers, `package.json dependencies ${name} is not a declared sdk member — declare it in the pin or remove it`);
|
|
2230
|
+
}
|
|
2231
|
+
for (const field of ['devDependencies', 'peerDependencies', 'optionalDependencies', 'peerDependenciesMeta']) {
|
|
2232
|
+
const dual = Object.keys(pkg[field] ?? {}).filter((n) => n.startsWith('@earendil-works/'));
|
|
2233
|
+
assert.deepEqual(dual, [], `[QK:PI-SDK-NO-DUAL-DECLARATION] package.json ${field} must carry no @earendil-works package beside the production set (found ${dual.join(', ')}) — one name in two dependency classes is install behaviour nothing here has measured`);
|
|
2234
|
+
}
|
|
2192
2235
|
|
|
2193
|
-
// check-pack-install
|
|
2236
|
+
// check-pack-install install pins (quoted `@earendil-works/pi-*@<ver>`
|
|
2194
2237
|
// args; the `\d`-anchored version avoids matching this regex literal itself).
|
|
2195
2238
|
const peerAi = runSh.match(/"@earendil-works\/pi-ai@(\d[\d.]*)"/)?.[1];
|
|
2196
2239
|
const peerCoding = runSh.match(/"@earendil-works\/pi-coding-agent@(\d[\d.]*)"/)?.[1];
|
|
2197
2240
|
const peerTui = runSh.match(/"@earendil-works\/pi-tui@(\d[\d.]*)"/)?.[1];
|
|
2198
2241
|
assert.equal(peerAi, piAi,
|
|
2199
|
-
`run.sh check-pack-install pi-ai
|
|
2242
|
+
`run.sh check-pack-install pi-ai install pin (${peerAi}) must match the pinned SDK (${piAi})`);
|
|
2200
2243
|
assert.equal(peerCoding, piAi,
|
|
2201
|
-
`run.sh check-pack-install pi-coding-agent
|
|
2244
|
+
`run.sh check-pack-install pi-coding-agent install pin (${peerCoding}) must match (${piAi})`);
|
|
2202
2245
|
assert.equal(peerTui, piAi,
|
|
2203
|
-
`run.sh check-pack-install pi-tui
|
|
2204
|
-
|
|
2205
|
-
//
|
|
2206
|
-
//
|
|
2207
|
-
//
|
|
2208
|
-
// bound at the next minor stops a fresh install from silently pulling a future
|
|
2209
|
-
// pi (past the declared ceiling — 0.87+ at the current 0.86.0 pin) whose
|
|
2210
|
-
// internal export surface has drifted from the one we typecheck against.
|
|
2211
|
-
// pi moves its public surface every minor (the 0.79→0.80 getModels→provider-
|
|
2212
|
-
// factory churn is exactly this, and 0.86 is the same shape again: a custom
|
|
2213
|
-
// provider's streamSimple now receives a branded TranscriptContext instead of a
|
|
2214
|
-
// raw Context, with systemPrompt/tools folded into a leading system message), so
|
|
2215
|
-
// an open `>=` floor is exactly how the next installer re-acquires the drift.
|
|
2216
|
-
// The floor is also the HARD MINIMUM a consumer install resolves: at `>=0.86.0`
|
|
2217
|
-
// an existing 0.85.1 host is upgraded, not kept.
|
|
2218
|
-
// Expected shape: `>=<devDep> <<major>.<minor+1>` (e.g. `>=0.86.0 <0.87`, `>=1.0.0 <1.1`).
|
|
2219
|
-
// Pi 1.0 did not change the rule: an unverified minor is still unverified, so the ceiling
|
|
2220
|
-
// stays the next minor of the pin's own major.
|
|
2246
|
+
`run.sh check-pack-install pi-tui install pin (${peerTui}) must match (${piAi})`);
|
|
2247
|
+
|
|
2248
|
+
// The supported HOST range a doc may declare is derived, closed: `>=<floor> <<major>.<minor+1>`.
|
|
2249
|
+
// pi moves its public surface every minor (the 0.79→0.80 getModels churn, the 0.86 TranscriptContext
|
|
2250
|
+
// change), so an unverified minor is still unverified; the ceiling stays the next minor of the floor.
|
|
2221
2251
|
const [piMaj, piMin] = piAi.split('.').map(Number);
|
|
2222
|
-
const expectedPeer = `>=${piAi} <${piMaj}.${piMin + 1}`;
|
|
2223
|
-
const peerDepAi = pkg.peerDependencies?.['@earendil-works/pi-ai'];
|
|
2224
|
-
const peerDepCoding = pkg.peerDependencies?.['@earendil-works/pi-coding-agent'];
|
|
2225
|
-
const peerDepTui = pkg.peerDependencies?.['@earendil-works/pi-tui'];
|
|
2226
|
-
assert.equal(peerDepAi, expectedPeer,
|
|
2227
|
-
`package.json peerDependencies @earendil-works/pi-ai (${peerDepAi}) must be "${expectedPeer}" (devDep floor + next-minor ceiling)`);
|
|
2228
|
-
assert.equal(peerDepCoding, expectedPeer,
|
|
2229
|
-
`package.json peerDependencies @earendil-works/pi-coding-agent (${peerDepCoding}) must be "${expectedPeer}"`);
|
|
2230
|
-
assert.equal(peerDepTui, expectedPeer,
|
|
2231
|
-
`package.json peerDependencies @earendil-works/pi-tui (${peerDepTui}) must be "${expectedPeer}"`);
|
|
2232
2252
|
|
|
2233
2253
|
// BASELINE DOCS (0.12.8). This gate was BORN reading a doc: 362becd added it
|
|
2234
2254
|
// after the 21de0f9 drift and asserted README.md's codex-acp install pin against
|
|
@@ -2292,14 +2312,14 @@ for (const file of BASELINE_DOCS) {
|
|
|
2292
2312
|
for (const [decl, floor, ceilMajor, ceilMinor] of text.matchAll(/>=\s?(\d+\.\d+\.\d+)\s?<\s?(\d+)\.(\d+)/g)) {
|
|
2293
2313
|
rangeDecls++;
|
|
2294
2314
|
assert.equal(floor, piAi,
|
|
2295
|
-
`${file}: declared pi floor in "${decl}" is ${floor}, but the
|
|
2315
|
+
`${file}: declared pi floor in "${decl}" is ${floor}, but the pinned SDK is ${piAi} — a baseline doc may not advertise a version no gate drives`);
|
|
2296
2316
|
assert.equal(`${ceilMajor}.${ceilMinor}`, `${piMaj}.${piMin + 1}`,
|
|
2297
2317
|
`${file}: declared pi ceiling in "${decl}" must be the next minor (${piMaj}.${piMin + 1})`);
|
|
2298
2318
|
}
|
|
2299
2319
|
// Exact install pins: `@earendil-works/pi-<pkg>@<version>`.
|
|
2300
2320
|
for (const [decl, ver] of text.matchAll(/@earendil-works\/pi-(?:ai|coding-agent|tui)@(\d+\.\d+\.\d+)/g)) {
|
|
2301
2321
|
exactDecls++;
|
|
2302
|
-
assert.equal(ver, piAi, `${file}: install example "${decl}" pins ${ver}, but the
|
|
2322
|
+
assert.equal(ver, piAi, `${file}: install example "${decl}" pins ${ver}, but the pinned SDK is ${piAi}`);
|
|
2303
2323
|
}
|
|
2304
2324
|
}
|
|
2305
2325
|
// Prose declarations carry the pin in sentences the two patterns above cannot
|
|
@@ -2313,7 +2333,7 @@ const PROSE_DECLS = [
|
|
|
2313
2333
|
for (const [file, re, shape] of PROSE_DECLS) {
|
|
2314
2334
|
const m = readFileSync(file, 'utf8').match(re);
|
|
2315
2335
|
assert.ok(m, `${file}: the baseline sentence "${shape}" is gone — restore it or update check-dep-versions; a doc reword must not silently drop the pin from the gate`);
|
|
2316
|
-
assert.equal(m[1], piAi, `${file}: "${shape}" declares ${m[1]}, but the
|
|
2336
|
+
assert.equal(m[1], piAi, `${file}: "${shape}" declares ${m[1]}, but the pinned SDK is ${piAi}`);
|
|
2317
2337
|
}
|
|
2318
2338
|
// ── #120 P4: ONE FLOOR, FOUR MECHANICAL SURFACES ──────────────────────────
|
|
2319
2339
|
// The bump is mostly a number substitution, and that is exactly how a bump goes
|
|
@@ -2333,6 +2353,8 @@ const PI_CONSTELLATION = [
|
|
|
2333
2353
|
'@earendil-works/pi-telemetry', '@earendil-works/chord',
|
|
2334
2354
|
// `[측정 2026-09-30]` 0.99.1: pi-coding-agent's runtime deps gained the built-in extensions' packages.
|
|
2335
2355
|
'@earendil-works/pi-codemode', '@earendil-works/pi-mcp',
|
|
2356
|
+
// #130: the durable library the pi-durable carrier imports — a production dependency since 0.32.
|
|
2357
|
+
'@earendil-works/pi-durable',
|
|
2336
2358
|
];
|
|
2337
2359
|
const sameSet = (got, want, where) => {
|
|
2338
2360
|
const missing = want.filter((n) => !got.includes(n));
|
|
@@ -2377,7 +2399,7 @@ if (!matcherPin) {
|
|
|
2377
2399
|
const CURRENT_CONTRACT_DECLS = [
|
|
2378
2400
|
['plugins/herdr/README.md', /npm install -g "@earendil-works\/pi-coding-agent@(>=[\d.]+ <\d+\.\d+)"/, 'the herdr install line'],
|
|
2379
2401
|
['docs/mux-launch-rail.md', /현 supported range 는 `(>=[\d.]+ <\d+\.\d+)`/, "mux-launch-rail's current supported range"],
|
|
2380
|
-
['docs/acp-backend-rail.md', /
|
|
2402
|
+
['docs/acp-backend-rail.md', /SDK exact `(\d+\.\d+\.\d+)`, host `(>=[\d.]+ <\d+\.\d+)`/, "the ACP support row's exact+range pair"],
|
|
2381
2403
|
];
|
|
2382
2404
|
for (const [file, re, shape] of CURRENT_CONTRACT_DECLS) {
|
|
2383
2405
|
const m = readFileSync(file, 'utf8').match(re);
|
|
@@ -2388,14 +2410,14 @@ for (const [file, re, shape] of CURRENT_CONTRACT_DECLS) {
|
|
|
2388
2410
|
}
|
|
2389
2411
|
}
|
|
2390
2412
|
assert.deepEqual(floorGaps, [],
|
|
2391
|
-
`[QK:PI-FLOOR-SINGLE-SOURCE] the pi floor is declared on four mechanical surfaces — the
|
|
2413
|
+
`[QK:PI-FLOOR-SINGLE-SOURCE] the pi floor is declared on four mechanical surfaces — the durable pin's SDK set with package.json's exact production dependencies, run.sh's install constellation, the pnpm workspace closure, and the current-contract sentences in the docs — and every one of them must name the SAME floor. A bump that moves three of the four is the ordinary way this breaks, and the surface left behind is the one nothing else reads`);
|
|
2392
2414
|
|
|
2393
2415
|
// Guard the guard: if the patterns ever stop matching, the loops above pass
|
|
2394
2416
|
// vacuously and the docs fall back OUT of the gate without a word.
|
|
2395
2417
|
assert.ok(rangeDecls >= 5, `expected at least 5 pi range declarations across the baseline docs, found ${rangeDecls} — the doc scan matched (almost) nothing and would pass vacuously`);
|
|
2396
2418
|
assert.ok(exactDecls >= 1, `expected at least 1 exact pi install pin in the baseline docs, found ${exactDecls}`);
|
|
2397
2419
|
|
|
2398
|
-
console.log(`[check-dep-versions] ok — pi ${piAi} is coherent across package.json (
|
|
2420
|
+
console.log(`[check-dep-versions] ok — pi ${piAi} is coherent across the durable pin's SDK set, package.json (exact production dependencies, no dev/peer duplicate), run.sh (install pins), and ${BASELINE_DOCS.length} baseline docs (${rangeDecls} range + ${exactDecls} exact + ${PROSE_DECLS.length} prose declarations)`);
|
|
2399
2421
|
EOF
|
|
2400
2422
|
)
|
|
2401
2423
|
}
|
|
@@ -2806,6 +2828,18 @@ check_pi_import_surface() {
|
|
|
2806
2828
|
# `/providers/*`, and any deeper path stay FORBIDDEN (we use only `/compat`).
|
|
2807
2829
|
# Do NOT widen this to a `providers/*` subpath — it typechecks but CANNOT
|
|
2808
2830
|
# resolve under the extension loader.
|
|
2831
|
+
#
|
|
2832
|
+
# #130 CARRIER FENCE (replaces the #129 bootstrap exception, retired with the source runtime):
|
|
2833
|
+
# the pi-durable carrier (pi/pi-durable/carrier/*.js, upstream app files emitted by
|
|
2834
|
+
# scripts/emit-pi-durable-carrier.ts) is the ONE place pi-coding-agent internals are reached,
|
|
2835
|
+
# and only through the reserved `entwurf-pi-dist:` scheme its resolver answers. The fence is
|
|
2836
|
+
# declared and asserted here, not inferred from the root-only scan staying quiet:
|
|
2837
|
+
# - the scheme is quoted ONLY in carrier files and at its one definition
|
|
2838
|
+
# (pi/pi-durable/carrier-relocation.mjs);
|
|
2839
|
+
# - a carrier file names no pi-coding-agent specifier at all, and every bare specifier it does
|
|
2840
|
+
# name is `node:*`, a declared sdk member (root or subpath) or a declared direct dependency
|
|
2841
|
+
# (pi/pi-durable/overlay/upstream-pin.json `sdk`).
|
|
2842
|
+
# Proved on an external fixture repo below, like the work-surface denominator.
|
|
2809
2843
|
# ONE corpus, TWO callers. The real scan and the denominator fixture must run the
|
|
2810
2844
|
# SAME two lines, or the fixture would prove a command the gate does not use.
|
|
2811
2845
|
pi_import_work_surface() {
|
|
@@ -2816,8 +2850,33 @@ check_pi_import_surface() {
|
|
|
2816
2850
|
| xargs -0r grep -HnE "[\"'\`]@earendil-works/pi-(ai|coding-agent|tui)/" 2>/dev/null \
|
|
2817
2851
|
| grep -vE "[\"'\`]@earendil-works/pi-ai/compat[\"'\`]" 2>/dev/null || true
|
|
2818
2852
|
}
|
|
2853
|
+
# The carrier fence over the same work surface: prints one line per violation. $1 = the pin.
|
|
2854
|
+
pi_carrier_fence_scan() {
|
|
2855
|
+
pi_import_work_surface | node -e '
|
|
2856
|
+
const fs = require("node:fs");
|
|
2857
|
+
const pin = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
|
|
2858
|
+
const declared = new Set([...Object.keys(pin.sdk?.members ?? {}), ...Object.keys(pin.sdk?.direct ?? {})]);
|
|
2859
|
+
const files = fs.readFileSync(0, "utf8").split("\0").filter(Boolean);
|
|
2860
|
+
const carrier = (f) => /^pi\/pi-durable\/carrier\/[^/]+\.js$/.test(f);
|
|
2861
|
+
const scheme = /["\x27`]entwurf-pi-dist:/g;
|
|
2862
|
+
for (const f of files) {
|
|
2863
|
+
const text = fs.readFileSync(f, "utf8");
|
|
2864
|
+
const quoted = (text.match(scheme) ?? []).length;
|
|
2865
|
+
if (!carrier(f) && quoted > (f === "pi/pi-durable/carrier-relocation.mjs" ? 1 : 0)) {
|
|
2866
|
+
console.log(`${f}: quotes the reserved entwurf-pi-dist: scheme outside the carrier`);
|
|
2867
|
+
}
|
|
2868
|
+
if (!carrier(f)) continue;
|
|
2869
|
+
for (const m of text.matchAll(/^\s*(?:import|export|\})[^;]*?\bfrom\s*"([^"]+)";|^\s*import\s*"([^"]+)";/gm)) {
|
|
2870
|
+
const spec = m[1] ?? m[2];
|
|
2871
|
+
if (spec.startsWith("./") || spec.startsWith("entwurf-pi-dist:") || spec.startsWith("node:")) continue;
|
|
2872
|
+
const name = spec.startsWith("@") ? spec.split("/").slice(0, 2).join("/") : spec.split("/")[0];
|
|
2873
|
+
if (name === "@earendil-works/pi-coding-agent" || !declared.has(name)) console.log(`${f}: imports ${spec}, outside the declared carrier set`);
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
' "$1"
|
|
2877
|
+
}
|
|
2819
2878
|
|
|
2820
|
-
local probe_dir probe_hits hits
|
|
2879
|
+
local probe_dir probe_hits probe_fence hits fence
|
|
2821
2880
|
probe_dir=$(mktemp -d "${TMPDIR:-/tmp}/entwurf-pi-import-denominator.XXXXXX")
|
|
2822
2881
|
(
|
|
2823
2882
|
cd "$probe_dir" || exit 1
|
|
@@ -2825,8 +2884,19 @@ check_pi_import_surface() {
|
|
|
2825
2884
|
printf '%s\n' 'import { getModels } from "@earendil-works/pi-ai/compat";' > tracked-allowed.ts
|
|
2826
2885
|
git add tracked-allowed.ts
|
|
2827
2886
|
printf '%s\n' 'import "@earendil-works/pi-ai/private-probe";' > untracked-forbidden.ts
|
|
2887
|
+
mkdir -p pi/pi-durable/carrier pi/pi-durable/overlay
|
|
2888
|
+
printf '%s\n' '{"sdk":{"members":{"@earendil-works/pi-ai":"1.0.4","@earendil-works/pi-durable":"1.0.4"},"direct":{"proper-lockfile":"4.1.2"}}}' \
|
|
2889
|
+
> pi/pi-durable/overlay/upstream-pin.json
|
|
2890
|
+
printf '%s\n' 'import { a } from "@earendil-works/pi-ai";' 'import { b } from "@earendil-works/pi-durable/tools";' \
|
|
2891
|
+
'import lock from "proper-lockfile";' 'import { c } from "entwurf-pi-dist:core/x.js";' 'import { d } from "./sibling.js";' \
|
|
2892
|
+
> pi/pi-durable/carrier/allowed.js
|
|
2893
|
+
printf '%s\n' 'import { e } from "@earendil-works/pi-coding-agent";' > pi/pi-durable/carrier/coding-agent.js
|
|
2894
|
+
printf '%s\n' 'import { f } from "left-pad";' > pi/pi-durable/carrier/undeclared.js
|
|
2895
|
+
printf '%s\n' 'import { g } from "entwurf-pi-dist:core/x.js";' > elsewhere.mjs
|
|
2896
|
+
git add pi/pi-durable elsewhere.mjs
|
|
2828
2897
|
) || { fail "[check-pi-import-surface] could not build the denominator fixture in $probe_dir"; rm -rf "$probe_dir"; return 1; }
|
|
2829
2898
|
probe_hits=$(cd "$probe_dir" && pi_import_scan)
|
|
2899
|
+
probe_fence=$(cd "$probe_dir" && pi_carrier_fence_scan pi/pi-durable/overlay/upstream-pin.json)
|
|
2830
2900
|
rm -rf "$probe_dir"
|
|
2831
2901
|
# ONE assertion, ONE claim token: the manifest contract requires the signature to
|
|
2832
2902
|
# occur exactly once in this file, so both fixture conditions are folded into a
|
|
@@ -2838,6 +2908,15 @@ check_pi_import_surface() {
|
|
|
2838
2908
|
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
2909
|
return 1
|
|
2840
2910
|
fi
|
|
2911
|
+
local fence_allowed="no" fence_coding_agent="no" fence_undeclared="no" fence_elsewhere="no"
|
|
2912
|
+
grep -qF 'carrier/allowed.js' <<<"$probe_fence" && fence_allowed="yes"
|
|
2913
|
+
grep -qF 'carrier/coding-agent.js' <<<"$probe_fence" && fence_coding_agent="yes"
|
|
2914
|
+
grep -qF 'carrier/undeclared.js' <<<"$probe_fence" && fence_undeclared="yes"
|
|
2915
|
+
grep -qF 'elsewhere.mjs' <<<"$probe_fence" && fence_elsewhere="yes"
|
|
2916
|
+
if [ "$fence_allowed" != "no" ] || [ "$fence_coding_agent" != "yes" ] || [ "$fence_undeclared" != "yes" ] || [ "$fence_elsewhere" != "yes" ]; then
|
|
2917
|
+
fail "[QK:PIIMPORT-CARRIER-FENCE] the #130 carrier fence is not exact — declared imports in a carrier file flagged=$fence_allowed (want no), a pi-coding-agent specifier in a carrier file reached=$fence_coding_agent (want yes), an undeclared package in a carrier file reached=$fence_undeclared (want yes), the reserved scheme outside the carrier reached=$fence_elsewhere (want yes)"
|
|
2918
|
+
return 1
|
|
2919
|
+
fi
|
|
2841
2920
|
|
|
2842
2921
|
hits=$(cd "$REPO_DIR" && pi_import_scan)
|
|
2843
2922
|
if [ -n "$hits" ]; then
|
|
@@ -2845,7 +2924,13 @@ check_pi_import_surface() {
|
|
|
2845
2924
|
echo "$hits"
|
|
2846
2925
|
exit 1
|
|
2847
2926
|
fi
|
|
2848
|
-
|
|
2927
|
+
fence=$(cd "$REPO_DIR" && pi_carrier_fence_scan pi/pi-durable/overlay/upstream-pin.json)
|
|
2928
|
+
if [ -n "$fence" ]; then
|
|
2929
|
+
echo "[check-pi-import-surface] FAIL: the #130 pi-durable carrier fence is broken:"
|
|
2930
|
+
echo "$fence"
|
|
2931
|
+
exit 1
|
|
2932
|
+
fi
|
|
2933
|
+
ok "[check-pi-import-surface] pi references are root/compat-only across tracked + untracked-non-ignored ts/js, and the pi-durable carrier reaches pi-coding-agent only through its reserved scheme with declared bare imports; the untracked denominator and the carrier fence are proved on an external fixture repo"
|
|
2849
2934
|
}
|
|
2850
2935
|
|
|
2851
2936
|
check_env_namespace() {
|
|
@@ -2872,65 +2957,26 @@ check_env_namespace() {
|
|
|
2872
2957
|
}
|
|
2873
2958
|
|
|
2874
2959
|
check_pi_runtime_version() {
|
|
2875
|
-
# 0.11 Stage 0 (동결결정 9, runtime half)
|
|
2876
|
-
#
|
|
2877
|
-
#
|
|
2878
|
-
#
|
|
2879
|
-
#
|
|
2880
|
-
#
|
|
2881
|
-
#
|
|
2960
|
+
# 0.11 Stage 0 (동결결정 9, runtime half), re-scoped by #130. tsc catches a missing export at
|
|
2961
|
+
# dev time, but an environment can still resolve a pi other than the one this repo pins at
|
|
2962
|
+
# runtime. Since 0.32 the pi SDK is a production dependency set (the pi-durable carrier runs
|
|
2963
|
+
# against it), so the runtime this checkout's tests, bridge and carrier import IS that set, and
|
|
2964
|
+
# the question is no longer "inside a range" but "exactly the pinned generation, edge by edge".
|
|
2965
|
+
# It is answered by the ONE native-set verifier the launcher, setup and the fresh preflight use
|
|
2966
|
+
# (pi-extensions/lib/pi-durable-runtime.ts): every declared member and direct dependency resolves
|
|
2967
|
+
# from the carrier, and every member's own member edge, at exactly the pinned version. A stale
|
|
2968
|
+
# development node_modules (an older pi left behind a bump) is a named sdk-absent/sdk-mismatch.
|
|
2882
2969
|
#
|
|
2883
|
-
#
|
|
2884
|
-
#
|
|
2885
|
-
#
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
const pkg = JSON.parse(readFileSync('package.json', 'utf8'));
|
|
2894
|
-
const FLOOR = pkg.devDependencies?.['@earendil-works/pi-coding-agent'];
|
|
2895
|
-
if (typeof FLOOR !== 'string' || !/^\d+\.\d+\.\d+$/.test(FLOOR)) {
|
|
2896
|
-
console.error(`[check-pi-runtime-version] FAIL: package.json devDependencies['@earendil-works/pi-coding-agent'] must be an EXACT x.y.z pin to serve as the runtime floor (got ${FLOOR ?? 'nothing'})`);
|
|
2897
|
-
process.exit(1);
|
|
2898
|
-
}
|
|
2899
|
-
// The declared contract is a CLOSED range (`>=<devDep> <<major>.<minor+1>`, enforced on
|
|
2900
|
-
// package.json by check-dep-versions), so the runtime check must be closed too.
|
|
2901
|
-
// A floor-only comparison would bless a resolved pi ABOVE the ceiling — and an
|
|
2902
|
-
// out-of-range pi is exactly the drift this cut exists to stop: 0.80.6 landed on
|
|
2903
|
-
// the dev box while the repo still declared 0.80.3, and every gate stayed green.
|
|
2904
|
-
// Verifying only half of a declared range is the same lie in the other direction.
|
|
2905
|
-
const [FLOOR_MAJOR, FLOOR_MINOR] = FLOOR.split('.').map(Number);
|
|
2906
|
-
const CEILING = `${FLOOR_MAJOR}.${FLOOR_MINOR + 1}.0`;
|
|
2907
|
-
const cmp = (a, b) => {
|
|
2908
|
-
const pa = a.split('.').map(Number), pb = b.split('.').map(Number);
|
|
2909
|
-
for (let i = 0; i < 3; i++) { if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0); }
|
|
2910
|
-
return 0;
|
|
2911
|
-
};
|
|
2912
|
-
let VERSION;
|
|
2913
|
-
try {
|
|
2914
|
-
({ VERSION } = await import('@earendil-works/pi-coding-agent'));
|
|
2915
|
-
} catch (e) {
|
|
2916
|
-
console.error(`[check-pi-runtime-version] FAIL: cannot import @earendil-works/pi-coding-agent root — ${e?.message ?? e}`);
|
|
2917
|
-
process.exit(1);
|
|
2918
|
-
}
|
|
2919
|
-
if (typeof VERSION !== 'string') {
|
|
2920
|
-
console.error('[check-pi-runtime-version] FAIL: pi root export VERSION is not a string');
|
|
2921
|
-
process.exit(1);
|
|
2922
|
-
}
|
|
2923
|
-
if (cmp(VERSION, FLOOR) < 0) {
|
|
2924
|
-
console.error(`[check-pi-runtime-version] FAIL: pi VERSION ${VERSION} < ${FLOOR} — the bridge is built and tested against the ${FLOOR} public/runtime surface (trust exports hasTrustRequiringProjectResources + ProjectTrustStore nearest-ancestor get, the 0.80 model-catalog API getModels reached via the deprecated /compat entrypoint — 0.80 moved the standalone root getModels there, and the extension loader resolves only /compat, NOT the providers/* factory subpath — provider registration surface, compaction semantics) that older pi lacks or behaves differently on. Bump @earendil-works/pi-*.`);
|
|
2925
|
-
process.exit(1);
|
|
2926
|
-
}
|
|
2927
|
-
if (cmp(VERSION, CEILING) >= 0) {
|
|
2928
|
-
console.error(`[check-pi-runtime-version] FAIL: pi VERSION ${VERSION} >= ${CEILING} — OUTSIDE the declared range (>=${FLOOR} <${CEILING.slice(0, -2)}). pi moves its public surface every minor (the 0.79→0.80 getModels→/compat churn), so a next-minor runtime is unverified by definition: no gate here has driven it. Either pin the repo to that pi (devDeps + peer range + baseline docs move together) or install the declared one.`);
|
|
2929
|
-
process.exit(1);
|
|
2930
|
-
}
|
|
2931
|
-
console.log(`[check-pi-runtime-version] ok — pi VERSION ${VERSION} within the declared range (>=${FLOOR} <${CEILING.slice(0, -2)})`);
|
|
2932
|
-
EOF
|
|
2933
|
-
)
|
|
2970
|
+
# What this deliberately does NOT answer: the ordinary Pi HOST on PATH. That is a separate
|
|
2971
|
+
# subject with its own range (`pi_supported_range`, enforced by setup and install-user-scope on a
|
|
2972
|
+
# real `pi --version`), and a green here says nothing about it.
|
|
2973
|
+
local out rc=0
|
|
2974
|
+
out=$(cd "$REPO_DIR" && run_ts pi-extensions/lib/pi-durable-runtime.ts resolve 2>&1) || rc=$?
|
|
2975
|
+
if [ "$rc" -ne 0 ]; then
|
|
2976
|
+
fail "[check-pi-runtime-version] the pinned pi SDK set is not what this checkout resolves: ${out##*$'\n'}"
|
|
2977
|
+
return 1
|
|
2978
|
+
fi
|
|
2979
|
+
echo "[check-pi-runtime-version] ok — this checkout resolves exactly the pinned pi SDK set (native set; the PATH host is setup's subject)"
|
|
2934
2980
|
}
|
|
2935
2981
|
|
|
2936
2982
|
check_install_preflight() {
|
|
@@ -2976,8 +3022,8 @@ check_install_preflight() {
|
|
|
2976
3022
|
# (a bare `test -d` would pass) with the other runtime deps symlinked live, but a
|
|
2977
3023
|
# representative dep (@anthropic-ai/sdk) dangles. Asserts the dir-move blind spot
|
|
2978
3024
|
# is closed AND that the failure names the broken dep. Uses a BUNDLED runtime dep
|
|
2979
|
-
# because the @earendil-works
|
|
2980
|
-
#
|
|
3025
|
+
# because the @earendil-works SDK set is not part of the install preflight probe
|
|
3026
|
+
# set (the carrier verifier owns its integrity).
|
|
2981
3027
|
fake=$(mktemp -d); proj=$(mktemp -d)
|
|
2982
3028
|
cp "$REPO_DIR/run.sh" "$fake/run.sh"
|
|
2983
3029
|
mkdir -p "$fake/node_modules/@modelcontextprotocol" "$fake/node_modules/@agentclientprotocol" "$fake/node_modules/@anthropic-ai"
|
|
@@ -3508,19 +3554,25 @@ check_pack() {
|
|
|
3508
3554
|
# Intent + policy live in NEXT.md Phase 2.3.
|
|
3509
3555
|
section "pack invariants (dry-run)"
|
|
3510
3556
|
|
|
3511
|
-
local json
|
|
3512
|
-
# --silent
|
|
3513
|
-
#
|
|
3514
|
-
# payload this parses.
|
|
3515
|
-
#
|
|
3557
|
+
local json pack_err
|
|
3558
|
+
# --silent keeps npm's own lifecycle banner off stdout, and prepack sends its build
|
|
3559
|
+
# output to stderr (`pnpm run build-bridge 1>&2`); either on stdout would pollute the
|
|
3560
|
+
# --json payload this parses. --foreground-scripts streams that stderr instead of
|
|
3561
|
+
# npm's pipe buffer, and it is CAPTURED, not discarded: a pnpm verify-deps refusal
|
|
3562
|
+
# inside prepack used to fail this gate with no reason anywhere. prepack runs on
|
|
3563
|
+
# dry-run too, which is how dist lands in this gate's file list.
|
|
3516
3564
|
# with-dist-lock wraps the WHOLE pack (prepack build-bridge emit + npm's
|
|
3517
3565
|
# post-build dist read) so a concurrent pack/build can't `rm -rf dist` mid-read
|
|
3518
3566
|
# (the 2026-07-03 phantom "dist missing" race). The nested prepack build-bridge
|
|
3519
3567
|
# is reentrant via ENTWURF_BUILD_LOCK_HELD.
|
|
3520
|
-
|
|
3568
|
+
pack_err=$(mktemp)
|
|
3569
|
+
json=$(cd "$REPO_DIR" && bash scripts/with-dist-lock.sh npm pack --dry-run --json --silent --foreground-scripts 2>"$pack_err") || {
|
|
3521
3570
|
fail "[check-pack] npm pack --dry-run failed"
|
|
3571
|
+
sed 's/^/ /' "$pack_err" >&2
|
|
3572
|
+
rm -f "$pack_err"
|
|
3522
3573
|
return 1
|
|
3523
3574
|
}
|
|
3575
|
+
rm -f "$pack_err"
|
|
3524
3576
|
|
|
3525
3577
|
local file_list
|
|
3526
3578
|
file_list=$(node -e '
|
|
@@ -3615,6 +3667,21 @@ check_pack() {
|
|
|
3615
3667
|
# check before assembling anything. The unit skeleton's package.json below is the other
|
|
3616
3668
|
# half — the installer copies it, and `pi/` ships per-FILE.
|
|
3617
3669
|
"mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js"
|
|
3670
|
+
"mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js"
|
|
3671
|
+
"mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js"
|
|
3672
|
+
"mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js"
|
|
3673
|
+
"pi/pi-durable/bootstrap.mjs"
|
|
3674
|
+
"pi/pi-durable/overlay/upstream-pin.json"
|
|
3675
|
+
"pi/pi-durable/overlay/runtime-contacts.patch"
|
|
3676
|
+
"pi/pi-durable/carrier-resolver.mjs"
|
|
3677
|
+
"pi/pi-durable/carrier-relocation.mjs"
|
|
3678
|
+
"pi/pi-durable/carrier/runtime.js"
|
|
3679
|
+
"pi/pi-durable/carrier/tui.js"
|
|
3680
|
+
"pi/pi-durable/carrier/harness-setup.js"
|
|
3681
|
+
"pi/pi-durable/carrier/prompt.js"
|
|
3682
|
+
"pi/pi-durable/carrier/sessions.js"
|
|
3683
|
+
"pi/pi-durable/carrier/subagent.js"
|
|
3684
|
+
"pi/pi-durable/carrier/LICENSE"
|
|
3618
3685
|
"pi/meta-bridge-omp/entwurf-meta-omp/package.json"
|
|
3619
3686
|
"mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js"
|
|
3620
3687
|
"scripts/postinstall-chmod.cjs"
|
|
@@ -3743,7 +3810,7 @@ check_pack() {
|
|
|
3743
3810
|
# which is exactly what property (1)'s `(_|$)` absorbs; all three shapes are fixtures
|
|
3744
3811
|
# below so a future suffix change cannot pass vacuously.)
|
|
3745
3812
|
pack_install_leaked_pi() {
|
|
3746
|
-
grep '^@earendil-works+' | grep -Ev '@1\.0\.
|
|
3813
|
+
grep '^@earendil-works+' | grep -Ev '@1\.0\.4(_|$)' || true
|
|
3747
3814
|
}
|
|
3748
3815
|
|
|
3749
3816
|
# Matcher self-test on SYNTHETIC lookalikes: a healthy install tree cannot exercise either
|
|
@@ -3763,15 +3830,15 @@ check_pack_pin_matcher() {
|
|
|
3763
3830
|
# None may leak; the two lookalikes must — a PREFIX-EXTENDED version (`0.86.0-beta.1`,
|
|
3764
3831
|
# the prerelease shape that an unanchored match would bless) and an off-pin version.
|
|
3765
3832
|
matcher_probe=$(printf '%s\n' \
|
|
3766
|
-
'@earendil-works+pi-ai@1.0.
|
|
3767
|
-
'@earendil-works+pi-ai@1.0.
|
|
3768
|
-
'@earendil-works+pi-ai@1.0.
|
|
3769
|
-
'@earendil-works+pi-ai@1.0.
|
|
3770
|
-
'@earendil-works+pi-ai@1.0.
|
|
3833
|
+
'@earendil-works+pi-ai@1.0.4' \
|
|
3834
|
+
'@earendil-works+pi-ai@1.0.4_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6' \
|
|
3835
|
+
'@earendil-works+pi-ai@1.0.4_ws@8.21.3' \
|
|
3836
|
+
'@earendil-works+pi-ai@1.0.4_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6__ws@8.21.3_zod@4.3.6' \
|
|
3837
|
+
'@earendil-works+pi-ai@1.0.4-beta.1' \
|
|
3771
3838
|
'@earendil-works+pi-agent-core@0.87.1' | pack_install_leaked_pi)
|
|
3772
|
-
if [ "$matcher_probe" != '@earendil-works+pi-ai@1.0.
|
|
3839
|
+
if [ "$matcher_probe" != '@earendil-works+pi-ai@1.0.4-beta.1
|
|
3773
3840
|
@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.
|
|
3841
|
+
fail "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED] the pin-leak matcher must flag the prefix-extended 1.0.4-beta.1 and the off-pin 0.87.1 lookalikes, and pass 1.0.4 bare or with any measured peer-hash — got: ${matcher_probe:-<nothing leaked>}"
|
|
3775
3842
|
return 1
|
|
3776
3843
|
fi
|
|
3777
3844
|
|
|
@@ -3779,9 +3846,9 @@ check_pack_pin_matcher() {
|
|
|
3779
3846
|
# (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
3847
|
# back to `^@earendil-works+pi-` sees nothing here and dies at this signature.
|
|
3781
3848
|
matcher_probe=$(printf '%s\n' \
|
|
3782
|
-
'@earendil-works+chord@1.0.
|
|
3849
|
+
'@earendil-works+chord@1.0.4' \
|
|
3783
3850
|
'@earendil-works+chord@0.87.1' \
|
|
3784
|
-
'@earendil-works+pi-ai@1.0.
|
|
3851
|
+
'@earendil-works+pi-ai@1.0.4' | pack_install_leaked_pi)
|
|
3785
3852
|
if [ "$matcher_probe" != '@earendil-works+chord@0.87.1' ]; then
|
|
3786
3853
|
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
3854
|
return 1
|
|
@@ -3790,6 +3857,233 @@ check_pack_pin_matcher() {
|
|
|
3790
3857
|
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)"
|
|
3791
3858
|
}
|
|
3792
3859
|
|
|
3860
|
+
# #130 — the pi-durable carrier proved on ACTUAL npm consumers of the candidate tarball.
|
|
3861
|
+
# $1 tarball $2 the gate's npm sandbox $3 an existing Entwurf-only npm consumer root inside it.
|
|
3862
|
+
# Cells: L1s (Entwurf alone, $3), L1 (project-local beside an aligned host pi-coding-agent at the
|
|
3863
|
+
# pinned SDK version), L2 (an isolated `npm -g --prefix`), each through (a) the INSTALLED verifier
|
|
3864
|
+
# (exit 0, prints its own carrier-resolver) and (b) an independent consumer oracle; then the L3
|
|
3865
|
+
# NEGATIVE (an override binds pi-durable's pi-ai edge one patch behind: the installed verifier must
|
|
3866
|
+
# refuse it by name, while a bare import still succeeds); then (c) the public launch on L1s.
|
|
3867
|
+
# The oracle observes RESOLUTIONS (a resolve hook), not every filesystem read; it asserts each
|
|
3868
|
+
# resolved product file lies inside THIS consumer's node_modules — a sandbox ancestor is not the
|
|
3869
|
+
# boundary (#130 P1a review, Defect B) — and proves that predicate on a control: the installed
|
|
3870
|
+
# package of a NEIGHBOUR consumer in the same sandbox, which must be judged outside.
|
|
3871
|
+
# Same-version sharing within the consumer is an ordinary layout; copies are recorded, not refused.
|
|
3872
|
+
pack_install_durable_cells() {
|
|
3873
|
+
local tgz="$1" box="$2" l1s="$3" pin_ver oracle_dir cell root control pkg out rc
|
|
3874
|
+
local host_specs=() host_old_specs=() spec
|
|
3875
|
+
pin_ver=$(cd "$REPO_DIR" && node -p "require('./pi/pi-durable/overlay/upstream-pin.json').sdk.members['@earendil-works/pi-coding-agent']")
|
|
3876
|
+
# The HOST fixture needs a coherent generation too: coding-agent@1.0.4 alone
|
|
3877
|
+
# has ^1.0.4 SDK edges, which can resolve 1.1.0 (CI37694671506: pi-ai tar 404).
|
|
3878
|
+
# These are fixture roots from the owning pin, not product overrides or fallback.
|
|
3879
|
+
out=$(cd "$REPO_DIR" && node -e 'const p = require("./pi/pi-durable/overlay/upstream-pin.json"); console.log(Object.entries(p.sdk.members).map(([n,v]) => `${n}@${v}`).join("\n"));') || return 1
|
|
3880
|
+
mapfile -t host_specs <<<"$out"
|
|
3881
|
+
oracle_dir="$box/pd-oracle"; mkdir -p "$oracle_dir"
|
|
3882
|
+
cat > "$oracle_dir/oracle.mjs" <<'ORACLE'
|
|
3883
|
+
// node --import <pkg>/pi/pi-durable/carrier-resolver.mjs oracle.mjs <pkg> <consumer node_modules> <control file> <receipt>
|
|
3884
|
+
import fs from "node:fs";
|
|
3885
|
+
import { registerHooks } from "node:module";
|
|
3886
|
+
import path from "node:path";
|
|
3887
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
3888
|
+
const [pkg, consumerRoot, control, receiptPath] = process.argv.slice(2);
|
|
3889
|
+
const seen = [];
|
|
3890
|
+
registerHooks({ resolve(specifier, context, next) { const r = next(specifier, context); seen.push({ specifier, parent: context.parentURL ?? null, url: r.url }); return r; } });
|
|
3891
|
+
const root = fs.realpathSync(consumerRoot);
|
|
3892
|
+
const inside = (file) => fs.realpathSync(file).startsWith(root + path.sep);
|
|
3893
|
+
const pin = JSON.parse(fs.readFileSync(path.join(pkg, "pi/pi-durable/overlay/upstream-pin.json"), "utf8"));
|
|
3894
|
+
const members = pin.sdk.members, direct = pin.sdk.direct;
|
|
3895
|
+
const carrier = path.join(pkg, "pi/pi-durable/carrier");
|
|
3896
|
+
const exportsOk = (await import(pathToFileURL(path.join(carrier, "runtime.js")).href)).openDurable instanceof Function
|
|
3897
|
+
&& (await import(pathToFileURL(path.join(carrier, "tui.js")).href)).runDurableTui instanceof Function;
|
|
3898
|
+
const files = [...new Set(seen.filter((s) => s.url.startsWith("file:")).map((s) => fileURLToPath(s.url)))];
|
|
3899
|
+
const outside = files.filter((f) => !inside(f));
|
|
3900
|
+
const owner = (file) => { for (let d = path.dirname(fs.realpathSync(file)); d !== path.dirname(d); d = path.dirname(d)) { const m = path.join(d, "package.json"); if (fs.existsSync(m)) { const j = JSON.parse(fs.readFileSync(m, "utf8")); if (typeof j.name === "string") return { name: j.name, version: j.version, root: d }; } } return null; };
|
|
3901
|
+
const copies = {};
|
|
3902
|
+
for (const f of files) { const o = owner(f); if (o?.name.startsWith("@earendil-works/")) (copies[o.name] ??= new Set()).add(`${o.version}@${path.relative(root, o.root)}`); }
|
|
3903
|
+
const relocated = seen.filter((s) => s.specifier.startsWith("entwurf-pi-dist:"));
|
|
3904
|
+
const codingAgentRoots = new Set(relocated.map((s) => owner(fileURLToPath(s.url))?.root));
|
|
3905
|
+
// Independent of the product's resolver: the package directory a bare import from `fromFile` binds is the
|
|
3906
|
+
// nearest ancestor `node_modules/<name>` of the file's REAL directory, walked here by hand.
|
|
3907
|
+
const lookup = (name, fromFile) => { for (let d = fs.realpathSync(path.dirname(fromFile)); ; d = path.dirname(d)) { const m = path.join(d, "node_modules", name, "package.json"); if (fs.existsSync(m)) return m; if (d === path.dirname(d)) return null; } };
|
|
3908
|
+
const edge = (from, to, want, fromFile) => { const m = lookup(to, fromFile); if (!m) return { from, to, want, got: null, name: null }; const j = JSON.parse(fs.readFileSync(m, "utf8")); return { from, to, want, got: j.version, name: j.name, at: path.relative(root, fs.realpathSync(path.dirname(m))) }; };
|
|
3909
|
+
const base = path.join(carrier, "runtime.js");
|
|
3910
|
+
const edges = Object.entries({ ...members, ...direct }).map(([n, v]) => edge("carrier", n, v, base));
|
|
3911
|
+
// Every reachable member copy, at any depth, once per real directory.
|
|
3912
|
+
const queue = edges.filter((e) => e.to in members && e.at).map((e) => e.at), seenCopies = new Set();
|
|
3913
|
+
while (queue.length > 0) { const at = queue.shift(); if (seenCopies.has(at)) continue; seenCopies.add(at); const m = path.join(root, at, "package.json"); const j = JSON.parse(fs.readFileSync(m, "utf8")); for (const dep of Object.keys(j.dependencies ?? {}).filter((x) => x in members)) { const e = edge(j.name, dep, members[dep], m); edges.push(e); if (e.at) queue.push(e.at); } }
|
|
3914
|
+
const verdict = {
|
|
3915
|
+
exportsOk,
|
|
3916
|
+
resolvedFiles: files.length,
|
|
3917
|
+
outsideConsumer: outside,
|
|
3918
|
+
controlRejected: fs.existsSync(control) && !inside(control),
|
|
3919
|
+
loadedVersionsOk: Object.entries(copies).every(([n, set]) => [...set].every((c) => c.startsWith(`${members[n]}@`))),
|
|
3920
|
+
relocations: relocated.length,
|
|
3921
|
+
relocationOneDistInside: codingAgentRoots.size === 1 && relocated.every((s) => inside(fileURLToPath(s.url))),
|
|
3922
|
+
memberEdges: edges.filter((e) => e.from !== "carrier").length,
|
|
3923
|
+
memberCopiesWalked: seenCopies.size,
|
|
3924
|
+
edgeMismatches: edges.filter((e) => e.got !== e.want || e.name !== e.to),
|
|
3925
|
+
};
|
|
3926
|
+
fs.writeFileSync(receiptPath, JSON.stringify({ consumerRoot: root, verdict, copies: Object.fromEntries(Object.entries(copies).map(([n, s]) => [n, [...s]])), edges, resolutions: seen }, null, 1));
|
|
3927
|
+
const ok = verdict.exportsOk && verdict.outsideConsumer.length === 0 && verdict.controlRejected && verdict.loadedVersionsOk
|
|
3928
|
+
&& verdict.relocations > 0 && verdict.relocationOneDistInside && verdict.memberEdges > 0 && verdict.edgeMismatches.length === 0;
|
|
3929
|
+
console.log(JSON.stringify({ ok, ...verdict, outsideConsumer: verdict.outsideConsumer.length, edgeMismatches: verdict.edgeMismatches.length }));
|
|
3930
|
+
process.exit(ok ? 0 : 1);
|
|
3931
|
+
ORACLE
|
|
3932
|
+
|
|
3933
|
+
mkdir -p "$box/pd-aligned" "$box/pd-global" "$box/pd-mismatch"
|
|
3934
|
+
out=$(cd "$box/pd-aligned" && npm install "$tgz" "${host_specs[@]}" --no-audit --no-fund 2>&1) || {
|
|
3935
|
+
fail "[check-pack-install] pi-durable L1 (aligned host) install failed:"; echo "$out" | tail -10 | sed 's/^/ /' >&2; return 1; }
|
|
3936
|
+
out=$(cd "$box" && npm install -g --prefix "$box/pd-global" "$tgz" --no-audit --no-fund 2>&1) || {
|
|
3937
|
+
fail "[check-pack-install] pi-durable L2 (isolated global) install failed:"; echo "$out" | tail -10 | sed 's/^/ /' >&2; return 1; }
|
|
3938
|
+
|
|
3939
|
+
for cell in L1s L1 L2; do
|
|
3940
|
+
case "$cell" in
|
|
3941
|
+
L1s) root="$l1s/node_modules" control="$box/pd-aligned/node_modules/@junghanacs/entwurf/package.json" ;;
|
|
3942
|
+
L1) root="$box/pd-aligned/node_modules" control="$box/pd-global/lib/node_modules/@junghanacs/entwurf/package.json" ;;
|
|
3943
|
+
L2) root="$box/pd-global/lib/node_modules" control="$l1s/node_modules/@junghanacs/entwurf/package.json" ;;
|
|
3944
|
+
esac
|
|
3945
|
+
pkg="$root/@junghanacs/entwurf"
|
|
3946
|
+
rc=0; out=$(cd "$box" && node "$pkg/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js" resolve 2>&1) || rc=$?
|
|
3947
|
+
if [ "$rc" -ne 0 ] || [ "$(realpath "$out" 2>/dev/null)" != "$(realpath "$pkg/pi/pi-durable/carrier-resolver.mjs")" ]; then
|
|
3948
|
+
fail "[check-pack-install] pi-durable $cell: the INSTALLED verifier did not verify its own carrier (rc=$rc): $out"
|
|
3949
|
+
return 1
|
|
3950
|
+
fi
|
|
3951
|
+
rc=0; out=$(cd "$box" && node --import "$out" "$oracle_dir/oracle.mjs" "$pkg" "$root" "$control" "$box/pd-oracle/$cell.json" 2>&1) || rc=$?
|
|
3952
|
+
if [ "$rc" -ne 0 ]; then
|
|
3953
|
+
fail "[QK:PACK-INSTALL-DURABLE-CONSUMER] [check-pack-install] pi-durable $cell: the carrier is not a coherent, consumer-contained load (receipt $box/pd-oracle/$cell.json): $out"
|
|
3954
|
+
return 1
|
|
3955
|
+
fi
|
|
3956
|
+
echo "[check-pack-install] pi-durable $cell: installed verifier PASS; consumer oracle $out"
|
|
3957
|
+
done
|
|
3958
|
+
|
|
3959
|
+
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"
|
|
3960
|
+
out=$(cd "$box/pd-mismatch" && npm install "$tgz" --no-audit --no-fund 2>&1) || {
|
|
3961
|
+
fail "[check-pack-install] pi-durable L3 (mismatch control) install failed:"; echo "$out" | tail -10 | sed 's/^/ /' >&2; return 1; }
|
|
3962
|
+
pkg="$box/pd-mismatch/node_modules/@junghanacs/entwurf"
|
|
3963
|
+
rc=0; out=$(cd "$box" && node "$pkg/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js" resolve 2>&1) || rc=$?
|
|
3964
|
+
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
|
|
3965
|
+
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"
|
|
3966
|
+
return 1
|
|
3967
|
+
fi
|
|
3968
|
+
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=$?
|
|
3969
|
+
if [ "$rc" -ne 0 ] || [ "$out" != "function" ]; then
|
|
3970
|
+
fail "[check-pack-install] pi-durable L3: the control premise failed — the mixed tree was expected to still IMPORT (rc=$rc): $out"
|
|
3971
|
+
return 1
|
|
3972
|
+
fi
|
|
3973
|
+
echo "[check-pack-install] pi-durable L3: the mixed tree still imports, and the installed verifier refuses it as pi-durable-sdk-mismatch"
|
|
3974
|
+
|
|
3975
|
+
# L4 — a THIRD-depth mismatch control on the same coherence axis. `[측정 2026-10-07]` npm 11 does not
|
|
3976
|
+
# produce this layout from an override (nested strategy included, it dedupes pi-durable's pi-ai to the
|
|
3977
|
+
# shared copy and overrides that), so it is CONSTRUCTED from real package bytes inside the already
|
|
3978
|
+
# proved L2 consumer: pi-durable gets its own same-version copy of the installed pi-ai, whose
|
|
3979
|
+
# pi-telemetry is the real published 1.0.3. Both the installed verifier and the independent oracle
|
|
3980
|
+
# must name the depth-3 edge; a walk that stopped at the carrier's own members would pass it.
|
|
3981
|
+
local l4_root="$box/pd-global/lib/node_modules" l4_tel="$box/pd-tel-103" l4_tel_ver="1.0.3"
|
|
3982
|
+
pkg="$l4_root/@junghanacs/entwurf"
|
|
3983
|
+
mkdir -p "$l4_tel" "$pkg/node_modules/@earendil-works/pi-durable/node_modules/@earendil-works"
|
|
3984
|
+
out=$(cd "$l4_tel" && npm pack "@earendil-works/pi-telemetry@$l4_tel_ver" --silent 2>&1 && tar -xzf ./*.tgz 2>&1) || {
|
|
3985
|
+
fail "[check-pack-install] could not fetch the real pi-telemetry 1.0.3 for the L4 control:"; echo "$out" | tail -5 | sed 's/^/ /' >&2; return 1; }
|
|
3986
|
+
local l4_ai="$pkg/node_modules/@earendil-works/pi-durable/node_modules/@earendil-works/pi-ai"
|
|
3987
|
+
cp -a "$(realpath "$pkg/node_modules/@earendil-works/pi-ai")" "$l4_ai"
|
|
3988
|
+
mkdir -p "$l4_ai/node_modules/@earendil-works"
|
|
3989
|
+
cp -a "$l4_tel/package" "$l4_ai/node_modules/@earendil-works/pi-telemetry"
|
|
3990
|
+
rc=0; out=$(cd "$box" && node "$pkg/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js" resolve 2>&1) || rc=$?
|
|
3991
|
+
if [ "$rc" -ne 3 ] || ! grep -q "pi-durable-sdk-mismatch: @earendil-works/pi-ai → @earendil-works/pi-telemetry wants $pin_ver, binds 1.0.3 at $(realpath "$l4_ai")/node_modules/@earendil-works/pi-telemetry" <<<"$out"; then
|
|
3992
|
+
fail "[QK:PACK-INSTALL-DURABLE-DEPTH3-REFUSED] [check-pack-install] pi-durable L4: a depth-3 pi-telemetry 1.0.3 under pi-durable's own pi-ai copy must be refused by name (rc=$rc): $out"
|
|
3993
|
+
return 1
|
|
3994
|
+
fi
|
|
3995
|
+
rc=0; out=$(cd "$box" && node --import "$pkg/pi/pi-durable/carrier-resolver.mjs" "$oracle_dir/oracle.mjs" "$pkg" "$l4_root" "$l1s/node_modules/@junghanacs/entwurf/package.json" "$box/pd-oracle/L4.json" 2>&1) || rc=$?
|
|
3996
|
+
if [ "$rc" -eq 0 ] || ! grep -q '"edgeMismatches":1' <<<"$out"; then
|
|
3997
|
+
fail "[check-pack-install] pi-durable L4: the independent oracle did not see exactly the one depth-3 mismatch (rc=$rc): $out"
|
|
3998
|
+
return 1
|
|
3999
|
+
fi
|
|
4000
|
+
echo "[check-pack-install] pi-durable L4: a constructed depth-3 pi-telemetry 1.0.3 is refused by the installed verifier and seen by the independent oracle"
|
|
4001
|
+
|
|
4002
|
+
# (c) The public launch on L1s, through the installed bin: verifier → resolver → bootstrap → TUI
|
|
4003
|
+
# module → the carrier's real openDurable on the pinned SDK, whose own model resolution
|
|
4004
|
+
# (`findInitialAgentModel`) refuses a provider no catalog registers — `[측정 2026-10-07]` the
|
|
4005
|
+
# upstream message, before the overlay's exact-model check is reached. Offline (PI_OFFLINE),
|
|
4006
|
+
# sandbox HOME/agent dir, no credential; the contact's bridge
|
|
4007
|
+
# is spawned only at the birth, which this refusal precedes, so no citizen may exist after it.
|
|
4008
|
+
local launch_home="$box/pd-launch-home" launch_cwd="$box/pd-launch-cwd" launch_env=()
|
|
4009
|
+
mkdir -p "$launch_home/.pi/agent" "$launch_cwd"
|
|
4010
|
+
mapfile -t launch_env < <(pack_install_consumer_env "$launch_home")
|
|
4011
|
+
rc=0; out=$(cd "$launch_cwd" && env "${launch_env[@]}" PI_OFFLINE=1 "$l1s/node_modules/.bin/entwurf" pi-durable --provider entwurf-pack-probe --model no-such-model --width task-wide 2>&1 </dev/null) || rc=$?
|
|
4012
|
+
if [ "$rc" -eq 0 ] || ! grep -q 'Could not resolve model: Unknown provider "entwurf-pack-probe"' <<<"$out" || ! grep -q "at openDurable (file://.*/pi/pi-durable/carrier/runtime.js" <<<"$out" || grep -qE "pi-durable-(package-incomplete|carrier-drift|sdk-absent|sdk-mismatch)|ERR_MODULE_NOT_FOUND|ERR_UNSUPPORTED_ESM_URL_SCHEME" <<<"$out"; then
|
|
4013
|
+
fail "[QK:PACK-INSTALL-DURABLE-PUBLIC-LAUNCH] [check-pack-install] the installed 'entwurf pi-durable' did not reach the carrier openDurable's own model refusal on the pinned SDK (rc=$rc):"
|
|
4014
|
+
echo "$out" | tail -15 | sed 's/^/ /' >&2
|
|
4015
|
+
return 1
|
|
4016
|
+
fi
|
|
4017
|
+
if ls "$launch_home/.pi/agent/meta-sessions" 2>/dev/null | grep -q .; then
|
|
4018
|
+
fail "[check-pack-install] the refused durable launch minted a meta record — a refusal before birth must leave no citizen"
|
|
4019
|
+
return 1
|
|
4020
|
+
fi
|
|
4021
|
+
echo "[check-pack-install] pi-durable public launch: the installed verb ran the carrier's openDurable on the pinned SDK, whose own model resolution refused the unregistered provider, before any birth"
|
|
4022
|
+
|
|
4023
|
+
# (c2) #130 P2: ONE explicit operator module on the INSTALLED package. The synthetic fixture is
|
|
4024
|
+
# copied from this checkout into the sandbox — it never ships (test/ is outside the package files).
|
|
4025
|
+
# Its initialization appends `init` to the receipt this cell names; the same public launch must then
|
|
4026
|
+
# reach the same carrier model refusal, so the installed bootstrap loaded the module before the
|
|
4027
|
+
# runtime and the native registry installed its extension (carrier/runtime.js 146) before that
|
|
4028
|
+
# refusal (runtime.js 162). A module claiming an extension name the app installs itself is refused by
|
|
4029
|
+
# name before anything opens: no durable session directory, no citizen, in a fresh sandbox home.
|
|
4030
|
+
# Native SDK file opens here are ordinary (the module runs in the native process); none is judged.
|
|
4031
|
+
local mod_dir="$box/pd-module" mod_home="$box/pd-module-home" mod_env=()
|
|
4032
|
+
mkdir -p "$mod_dir" "$mod_home/.pi/agent"
|
|
4033
|
+
cp "$REPO_DIR/test/fixtures/pi-durable-native-module/mock.mjs" "$mod_dir/mock.mjs"
|
|
4034
|
+
printf 'export default { name: "subagent" };\n' > "$mod_dir/reserved.mjs"
|
|
4035
|
+
rc=0; out=$(cd "$launch_cwd" && env "${launch_env[@]}" PI_OFFLINE=1 MOCK_NATIVE_RECEIPT="$mod_dir/receipt.log" "$l1s/node_modules/.bin/entwurf" pi-durable --provider entwurf-pack-probe --model no-such-model --width task-wide --native-module "$mod_dir/mock.mjs" 2>&1 </dev/null) || rc=$?
|
|
4036
|
+
if [ "$rc" -eq 0 ] || [ "$(cat "$mod_dir/receipt.log" 2>/dev/null)" != "init" ] || ! grep -q 'Could not resolve model: Unknown provider "entwurf-pack-probe"' <<<"$out" || ! grep -q "at openDurable (file://.*/pi/pi-durable/carrier/runtime.js" <<<"$out" || grep -q "native-module-" <<<"$out"; then
|
|
4037
|
+
fail "[QK:PACK-INSTALL-DURABLE-NATIVE-MODULE] [check-pack-install] the installed 'entwurf pi-durable --native-module' did not initialize the module once and then reach the carrier openDurable's own model refusal (rc=$rc, receipt $(cat "$mod_dir/receipt.log" 2>/dev/null | tr '\n' ' ')):"
|
|
4038
|
+
echo "$out" | tail -15 | sed 's/^/ /' >&2
|
|
4039
|
+
return 1
|
|
4040
|
+
fi
|
|
4041
|
+
mapfile -t mod_env < <(pack_install_consumer_env "$mod_home")
|
|
4042
|
+
rc=0; out=$(cd "$launch_cwd" && env "${mod_env[@]}" PI_OFFLINE=1 "$l1s/node_modules/.bin/entwurf" pi-durable --provider entwurf-pack-probe --model no-such-model --width task-wide --native-module "$mod_dir/reserved.mjs" 2>&1 </dev/null) || rc=$?
|
|
4043
|
+
if [ "$rc" -eq 0 ] || ! grep -q "native-module-name-reserved: .*subagent" <<<"$out" || grep -q "Unknown provider" <<<"$out" || [ -e "$mod_home/.pi/agent/experimental" ] || [ -e "$mod_home/.pi/agent/meta-sessions" ]; then
|
|
4044
|
+
fail "[QK:PACK-INSTALL-DURABLE-NATIVE-MODULE] [check-pack-install] the installed launch did not refuse a reserved-name module by name before anything opened (rc=$rc):"
|
|
4045
|
+
echo "$out" | tail -15 | sed 's/^/ /' >&2
|
|
4046
|
+
return 1
|
|
4047
|
+
fi
|
|
4048
|
+
echo "[check-pack-install] pi-durable native module: the installed bootstrap initialized one explicit module before the carrier's runtime (then the same model refusal), and refused a reserved extension name before anything opened"
|
|
4049
|
+
|
|
4050
|
+
# (d) Host vs native are two subjects. The installed package's native SDK set is the pin (above);
|
|
4051
|
+
# the ordinary Pi HOST on PATH is judged by its OWN real `pi --version` against the derived range.
|
|
4052
|
+
# Real host binaries, no forged version: a sandbox install of the previous pi (1.0.2) must make the
|
|
4053
|
+
# installed setup's pi row a named FAIL with ZERO Pi wiring while pi-durable still PASSes, and the
|
|
4054
|
+
# aligned host binary (the pinned version, installed above for L1) must be inside the range.
|
|
4055
|
+
# The previous host version is a variable, not a quoted `@pkg@x.y.z` spec: check-dep-versions reads
|
|
4056
|
+
# those specs as this repo's install pins, and this one is deliberately off the floor.
|
|
4057
|
+
local host_home host_proj host_old="$box/pd-host-old" host_env=() host_old_ver="1.0.2"
|
|
4058
|
+
mkdir -p "$host_old"
|
|
4059
|
+
for spec in "${host_specs[@]}"; do host_old_specs+=("${spec%@*}@$host_old_ver"); done
|
|
4060
|
+
out=$(cd "$host_old" && npm install "${host_old_specs[@]}" --no-audit --no-fund 2>&1) || {
|
|
4061
|
+
fail "[check-pack-install] could not install the previous-host pi fixture (1.0.2):"; echo "$out" | tail -10 | sed 's/^/ /' >&2; return 1; }
|
|
4062
|
+
for cell in old aligned; do
|
|
4063
|
+
host_home="$box/pd-host-$cell-home"; host_proj="$box/pd-host-$cell-proj"
|
|
4064
|
+
mkdir -p "$host_home/.pi/agent" "$host_proj"
|
|
4065
|
+
mapfile -t host_env < <(pack_install_consumer_env "$host_home")
|
|
4066
|
+
local host_bin="$host_old/node_modules/.bin/pi"
|
|
4067
|
+
[ "$cell" = aligned ] && host_bin="$box/pd-aligned/node_modules/.bin/pi"
|
|
4068
|
+
rc=0; out=$(cd "$box" && env "${host_env[@]}" PI_BIN="$host_bin" CLAUDE_BIN="$box/definitely-absent" AGY_BIN="$box/definitely-absent" \
|
|
4069
|
+
COPILOT_BIN="$box/definitely-absent" OMP_BIN="$box/definitely-absent" CODEX_BIN="$box/definitely-absent" \
|
|
4070
|
+
"$l1s/node_modules/.bin/entwurf" setup "$host_proj" 2>&1) || rc=$?
|
|
4071
|
+
if ! grep -q "pi-durable: PASS" <<<"$out"; then
|
|
4072
|
+
fail "[check-pack-install] host $cell: the native pi-durable row must stay PASS whatever the PATH host is:"; echo "$out" | tail -20 | sed 's/^/ /' >&2; return 1
|
|
4073
|
+
fi
|
|
4074
|
+
if [ "$cell" = old ]; then
|
|
4075
|
+
if [ "$rc" -eq 0 ] || ! grep -q "pi: FAIL — detected pi 1.0.2 is outside the supported range >=$pin_ver <1.1" <<<"$out" || [ -e "$host_proj/.pi" ] || [ -e "$host_home/.pi/agent/settings.json" ]; then
|
|
4076
|
+
fail "[QK:PACK-INSTALL-HOST-RANGE-SEPARATE] [check-pack-install] a real pi 1.0.2 host beside the $pin_ver native set must be a named pi FAIL with zero Pi wiring and a non-green setup (rc=$rc):"
|
|
4077
|
+
echo "$out" | tail -20 | sed 's/^/ /' >&2
|
|
4078
|
+
return 1
|
|
4079
|
+
fi
|
|
4080
|
+
elif grep -q "outside the supported range" <<<"$out"; then
|
|
4081
|
+
fail "[check-pack-install] host aligned: the pinned-version host binary was judged outside the range:"; echo "$out" | tail -20 | sed 's/^/ /' >&2; return 1
|
|
4082
|
+
fi
|
|
4083
|
+
done
|
|
4084
|
+
echo "[check-pack-install] host vs native: a real pi 1.0.2 host is a named pi FAIL with no wiring while pi-durable PASSes; the $pin_ver host binary is inside the range"
|
|
4085
|
+
}
|
|
4086
|
+
|
|
3793
4087
|
# The writable roots of ONE consumer sandbox, shared by every consumer subcall of
|
|
3794
4088
|
# check-pack-install that drives an installed `run.sh` writer. The writers resolve their agent
|
|
3795
4089
|
# dir as `${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}` (`resolve`/`install` below), so swapping HOME
|
|
@@ -3981,6 +4275,21 @@ _check_pack_install_impl() {
|
|
|
3981
4275
|
# check before assembling anything. The unit skeleton's package.json below is the other
|
|
3982
4276
|
# half — the installer copies it, and `pi/` ships per-FILE.
|
|
3983
4277
|
"mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js"
|
|
4278
|
+
"mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js"
|
|
4279
|
+
"mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js"
|
|
4280
|
+
"mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js"
|
|
4281
|
+
"pi/pi-durable/bootstrap.mjs"
|
|
4282
|
+
"pi/pi-durable/overlay/upstream-pin.json"
|
|
4283
|
+
"pi/pi-durable/overlay/runtime-contacts.patch"
|
|
4284
|
+
"pi/pi-durable/carrier-resolver.mjs"
|
|
4285
|
+
"pi/pi-durable/carrier-relocation.mjs"
|
|
4286
|
+
"pi/pi-durable/carrier/runtime.js"
|
|
4287
|
+
"pi/pi-durable/carrier/tui.js"
|
|
4288
|
+
"pi/pi-durable/carrier/harness-setup.js"
|
|
4289
|
+
"pi/pi-durable/carrier/prompt.js"
|
|
4290
|
+
"pi/pi-durable/carrier/sessions.js"
|
|
4291
|
+
"pi/pi-durable/carrier/subagent.js"
|
|
4292
|
+
"pi/pi-durable/carrier/LICENSE"
|
|
3984
4293
|
"pi/meta-bridge-omp/entwurf-meta-omp/package.json"
|
|
3985
4294
|
"mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js"
|
|
3986
4295
|
"scripts/postinstall-chmod.cjs"
|
|
@@ -4059,9 +4368,9 @@ _check_pack_install_impl() {
|
|
|
4059
4368
|
# repo packages with; --ignore-workspace stops it from re-attaching
|
|
4060
4369
|
# to our pnpm-workspace.yaml; --ignore-scripts blocks the husky
|
|
4061
4370
|
# prepare hook (and any future install scripts) from running inside
|
|
4062
|
-
# the consumer project.
|
|
4063
|
-
# baseline (the package.json
|
|
4064
|
-
# check-dep-versions binds to
|
|
4371
|
+
# the consumer project. The pi SDK is pinned to the CURRENT pi release
|
|
4372
|
+
# baseline (the package.json exact production dependency — the pins below
|
|
4373
|
+
# are what check-dep-versions binds to it) so the smoke matches the same
|
|
4065
4374
|
# shape an external pi user would have after `pi install`.
|
|
4066
4375
|
local tmp npm_tmp
|
|
4067
4376
|
tmp=$(mktemp -d -t entwurf-install-smoke.XXXXXX)
|
|
@@ -4122,20 +4431,28 @@ _check_pack_install_impl() {
|
|
|
4122
4431
|
# @earendil-works/pi-codemode and @earendil-works/pi-mcp as runtime dependencies (the built-in
|
|
4123
4432
|
# codemode and MCP extensions), so both are pinned beside the eight. pi-client and pi-protocol are
|
|
4124
4433
|
# still published at 0.99.1 and keep their pins under the rule above.
|
|
4125
|
-
|
|
4434
|
+
# `[측정 2026-10-05]` at 1.0.2 the constellation is unchanged: `npm view <pkg>@1.0.2 dependencies`
|
|
4435
|
+
# gives pi-coding-agent {chord, pi-agent-core, pi-ai, pi-codemode, pi-mcp, pi-tui}, pi-ai
|
|
4436
|
+
# {pi-telemetry}, pi-client {chord, pi-protocol}, pi-protocol {chord}, all ten published at 1.0.2.
|
|
4437
|
+
# `[측정 2026-10-07]` at 1.0.4 the same edges hold, and #130 adds @earendil-works/pi-durable: the
|
|
4438
|
+
# library the pi-durable carrier imports, a production dependency of this package since 0.32 (its
|
|
4439
|
+
# own deps: chord, pi-ai). All eleven are published at 1.0.4. The tarball now DECLARES the nine-member
|
|
4440
|
+
# SDK set itself, so these pins are an aligned host install beside it, not peers it lacks.
|
|
4441
|
+
echo "[check-pack-install] pnpm add into $tmp (with the aligned 1.0.x SDK + chord + codemode + mcp + durable + typebox)"
|
|
4126
4442
|
local install_log
|
|
4127
4443
|
install_log=$(cd "$tmp" && pnpm add \
|
|
4128
4444
|
"$tgz_path" \
|
|
4129
|
-
"@earendil-works/pi-ai@1.0.
|
|
4130
|
-
"@earendil-works/pi-coding-agent@1.0.
|
|
4131
|
-
"@earendil-works/pi-tui@1.0.
|
|
4132
|
-
"@earendil-works/pi-agent-core@1.0.
|
|
4133
|
-
"@earendil-works/pi-client@1.0.
|
|
4134
|
-
"@earendil-works/pi-protocol@1.0.
|
|
4135
|
-
"@earendil-works/pi-telemetry@1.0.
|
|
4136
|
-
"@earendil-works/chord@1.0.
|
|
4137
|
-
"@earendil-works/pi-codemode@1.0.
|
|
4138
|
-
"@earendil-works/pi-mcp@1.0.
|
|
4445
|
+
"@earendil-works/pi-ai@1.0.4" \
|
|
4446
|
+
"@earendil-works/pi-coding-agent@1.0.4" \
|
|
4447
|
+
"@earendil-works/pi-tui@1.0.4" \
|
|
4448
|
+
"@earendil-works/pi-agent-core@1.0.4" \
|
|
4449
|
+
"@earendil-works/pi-client@1.0.4" \
|
|
4450
|
+
"@earendil-works/pi-protocol@1.0.4" \
|
|
4451
|
+
"@earendil-works/pi-telemetry@1.0.4" \
|
|
4452
|
+
"@earendil-works/chord@1.0.4" \
|
|
4453
|
+
"@earendil-works/pi-codemode@1.0.4" \
|
|
4454
|
+
"@earendil-works/pi-mcp@1.0.4" \
|
|
4455
|
+
"@earendil-works/pi-durable@1.0.4" \
|
|
4139
4456
|
"typebox@latest" \
|
|
4140
4457
|
--ignore-workspace --ignore-scripts 2>&1) || {
|
|
4141
4458
|
fail "[check-pack-install] pnpm add failed:"
|
|
@@ -4151,11 +4468,11 @@ _check_pack_install_impl() {
|
|
|
4151
4468
|
local leaked_pi
|
|
4152
4469
|
leaked_pi=$(ls "$tmp/node_modules/.pnpm" 2>/dev/null | pack_install_leaked_pi)
|
|
4153
4470
|
if [ -n "$leaked_pi" ]; then
|
|
4154
|
-
fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 1.0.
|
|
4471
|
+
fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 1.0.4):"
|
|
4155
4472
|
printf '%s\n' "$leaked_pi" | sed 's/^/ /' >&2
|
|
4156
4473
|
return 1
|
|
4157
4474
|
fi
|
|
4158
|
-
echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works package is 1.0.
|
|
4475
|
+
echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works package is 1.0.4 (chord and pi-durable included)"
|
|
4159
4476
|
|
|
4160
4477
|
# Resolve the installed package.json and confirm pi.extensions
|
|
4161
4478
|
# arrived intact. If pi.extensions is empty or missing, the
|
|
@@ -4174,6 +4491,34 @@ _check_pack_install_impl() {
|
|
|
4174
4491
|
}
|
|
4175
4492
|
echo "[check-pack-install] installed: $probe"
|
|
4176
4493
|
|
|
4494
|
+
# #129 — the packaged durable bootstrap, imported FROM the installed tree. The tarball listing
|
|
4495
|
+
# above proves the file ships, not that its static imports reach the shipped compiled closure
|
|
4496
|
+
# under node_modules (where a checkout .ts import would be refused by the strip-types fence), nor
|
|
4497
|
+
# that the bridge entry it hands the adapter is this package's own dist. Import only: main() runs
|
|
4498
|
+
# solely when the file is the process entry, and the runtime/TUI are dynamic imports inside it, so
|
|
4499
|
+
# no runtime, TUI, birth or bridge starts here.
|
|
4500
|
+
local durable_proof
|
|
4501
|
+
durable_proof=$(cd "$tmp" && node --input-type=module -e "
|
|
4502
|
+
const { realpathSync } = await import('node:fs');
|
|
4503
|
+
const pkg = realpathSync(process.cwd() + '/node_modules/@junghanacs/entwurf');
|
|
4504
|
+
const m = await import(pkg + '/pi/pi-durable/bootstrap.mjs');
|
|
4505
|
+
for (const name of ['main', 'openDurableCitizen']) {
|
|
4506
|
+
if (typeof m[name] !== 'function') { console.error(name + ' is not an exported function'); process.exit(1); }
|
|
4507
|
+
}
|
|
4508
|
+
const want = pkg + '/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js';
|
|
4509
|
+
const entry = m.PACKAGED_BRIDGE_ENTRY;
|
|
4510
|
+
if (typeof entry !== 'string' || realpathSync(entry) !== want) {
|
|
4511
|
+
console.error('PACKAGED_BRIDGE_ENTRY ' + entry + ' is not the dist entry of the installed package (' + want + ')');
|
|
4512
|
+
process.exit(1);
|
|
4513
|
+
}
|
|
4514
|
+
console.log('exports main/openDurableCitizen; PACKAGED_BRIDGE_ENTRY is the installed dist entry');
|
|
4515
|
+
" 2>&1) || {
|
|
4516
|
+
fail "[check-pack-install] installed durable bootstrap import failed:"
|
|
4517
|
+
echo "$durable_proof" | sed 's/^/ /' >&2
|
|
4518
|
+
return 1
|
|
4519
|
+
}
|
|
4520
|
+
echo "[check-pack-install] installed durable bootstrap: $durable_proof"
|
|
4521
|
+
|
|
4177
4522
|
# #116 M3-b1/b2 — THE ACTUAL-PACKAGE RUNTIME PROOF, THROUGH THE SHIPPED OWNER.
|
|
4178
4523
|
# The Herdr plugin's runtime verifier decides whether a tree npm placed is a runtime the scoped
|
|
4179
4524
|
# wiring may name: exact name@version, the compiled entry, all three required bins present AND
|
|
@@ -4236,8 +4581,8 @@ _check_pack_install_impl() {
|
|
|
4236
4581
|
# and safe to run in CI. Output goes to stderr; capture both
|
|
4237
4582
|
# streams with 2>&1.
|
|
4238
4583
|
#
|
|
4239
|
-
# The pi that loads the tarball is the PINNED
|
|
4240
|
-
# it ($tmp/node_modules/.bin/pi = the floor of the supported
|
|
4584
|
+
# The pi that loads the tarball is the PINNED SDK pi this smoke just installed next to
|
|
4585
|
+
# it ($tmp/node_modules/.bin/pi = the floor of the supported host range), NEVER
|
|
4241
4586
|
# whatever `pi` the host happens to have on PATH. A gate may not READ the operator's
|
|
4242
4587
|
# global install any more than it may WRITE it: PATH resolution made this gate green
|
|
4243
4588
|
# on a dev box carrying a newer global pi and RED in CI, which carries no global pi
|
|
@@ -4260,14 +4605,14 @@ _check_pack_install_impl() {
|
|
|
4260
4605
|
)
|
|
4261
4606
|
|
|
4262
4607
|
# Assert the version: a gate that cannot name which pi it proved has proved nothing.
|
|
4263
|
-
#
|
|
4264
|
-
#
|
|
4608
|
+
# The pinned SDK generation is the pin SSOT (package.json's exact production dependency, which
|
|
4609
|
+
# check-dep-versions binds to the durable pin and to the install literals above).
|
|
4265
4610
|
local pi_bin="$tmp/node_modules/.bin/pi" pi_pin pi_ver
|
|
4266
4611
|
if [ ! -x "$pi_bin" ]; then
|
|
4267
4612
|
fail "[check-pack-install] pinned pi missing from the install-smoke tree ($pi_bin) — cannot run loader smoke"
|
|
4268
4613
|
return 1
|
|
4269
4614
|
fi
|
|
4270
|
-
pi_pin=$(cd "$REPO_DIR" && node -p "require('./package.json').
|
|
4615
|
+
pi_pin=$(cd "$REPO_DIR" && node -p "require('./package.json').dependencies['@earendil-works/pi-coding-agent']")
|
|
4271
4616
|
pi_ver=$(cd "$tmp" && env "${pi_env[@]}" "$pi_bin" --version 2>&1 | head -1 | tr -d '[:space:]')
|
|
4272
4617
|
if [ "$pi_ver" != "$pi_pin" ]; then
|
|
4273
4618
|
fail "[check-pack-install] install-smoke pi is '$pi_ver', expected the pinned '$pi_pin' — the loader smoke would prove the wrong runtime"
|
|
@@ -4334,8 +4679,9 @@ _check_pack_install_impl() {
|
|
|
4334
4679
|
# npm-managed neutral install regression — the README's PRIMARY install path is
|
|
4335
4680
|
# now `npm install @junghanacs/entwurf` (NOT `pi install npm:...`). This layout
|
|
4336
4681
|
# lands the package under node_modules, hoists runtime deps to the sibling
|
|
4337
|
-
# node_modules, has no package-local node_modules, and
|
|
4338
|
-
#
|
|
4682
|
+
# node_modules, has no package-local node_modules, and (since #130) carries the
|
|
4683
|
+
# pi SDK as a production dependency the bridge must still not import — the
|
|
4684
|
+
# pi-free boot below proves that under a deny hook. The old cwd-relative
|
|
4339
4685
|
# preflight_dep_integrity rejected every hoisted-dep npm install; 0.12.0 then
|
|
4340
4686
|
# additionally died because start.sh tried strip-types under node_modules.
|
|
4341
4687
|
# Prove `entwurf`/`entwurf-bridge` bins exist, `run.sh install` writes settings
|
|
@@ -4425,6 +4771,10 @@ sys.exit(0 if any(isinstance(s,str) and s.endswith('/node_modules/@junghanacs/en
|
|
|
4425
4771
|
fi
|
|
4426
4772
|
echo "[check-pack-install] user-scope citizen regression pass (npm consumer: --entwurf-control loads from a foreign cwd)"
|
|
4427
4773
|
|
|
4774
|
+
# #130: the pi-durable carrier on actual npm consumers (L1s = this Entwurf-only root, L1 aligned
|
|
4775
|
+
# host, L2 isolated global, L3 mismatch control) and the public launch.
|
|
4776
|
+
pack_install_durable_cells "$tgz_path" "$npm_tmp" "$npmroot" || return 1
|
|
4777
|
+
|
|
4428
4778
|
# Two-root ownership row (#86 C2, L1b extension): a SECOND npm root under the
|
|
4429
4779
|
# SAME consumer HOME must not silently steal the user-scope registration the
|
|
4430
4780
|
# first root owns — normal install refuses (naming takeover-user-scope) and the
|
|
@@ -4566,6 +4916,13 @@ sys.exit(0 if any(isinstance(s,str) and s.rstrip('/')== '$npm2_pkg' for s in src
|
|
|
4566
4916
|
return 1
|
|
4567
4917
|
fi
|
|
4568
4918
|
done
|
|
4919
|
+
# #130: pi-durable is not a detected harness — its carrier and pi SDK set ship IN this package, so
|
|
4920
|
+
# the installed row must verify them from node_modules (PASS), never fall back to a zero-state SKIP.
|
|
4921
|
+
if ! grep -q "pi-durable: PASS" <<<"$setup_out"; then
|
|
4922
|
+
fail "[check-pack-install] installed all-absent setup did not verify the packaged pi-durable carrier and its SDK set (want 'pi-durable: PASS'):"
|
|
4923
|
+
echo "$setup_out" | tail -25 | sed 's/^/ /' >&2
|
|
4924
|
+
return 1
|
|
4925
|
+
fi
|
|
4569
4926
|
if ! grep -q "bins: PASS — provided by npm bin linking" <<<"$setup_out"; then
|
|
4570
4927
|
fail "[check-pack-install] installed setup must report stable bins PASS as npm-provided (capability present, not absent):"
|
|
4571
4928
|
echo "$setup_out" | tail -25 | sed 's/^/ /' >&2
|
|
@@ -4920,29 +5277,55 @@ PY
|
|
|
4920
5277
|
# surface. Two proofs in one: (1) the prepack dist JS boots under node_modules
|
|
4921
5278
|
# with plain node — a strip-types fallback would crash with the exact error
|
|
4922
5279
|
# above, so a parseable tools/list IS proof the dist path was taken; (2) the
|
|
4923
|
-
# boot is pi-free
|
|
4924
|
-
#
|
|
5280
|
+
# boot is pi-free. Until #130 that was proved by ABSENCE (the pi peers were optional and the
|
|
5281
|
+
# neutral npm path did not install them). Since 0.32 the pi SDK is a production dependency, so
|
|
5282
|
+
# it is always installed and absence can no longer be the premise; the proof is now adversarial
|
|
5283
|
+
# in the other direction: the bridge boots under a preloaded resolve hook that REFUSES every
|
|
5284
|
+
# `@earendil-works/*` specifier, so an eager graph that imported pi would die at boot.
|
|
4925
5285
|
local installed_start="$npmroot/node_modules/.bin/entwurf-bridge"
|
|
4926
5286
|
local installed_dist="$npm_pkg/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js"
|
|
4927
5287
|
if [ ! -f "$installed_dist" ]; then
|
|
4928
5288
|
fail "[check-pack-install] installed bridge missing prebuilt dist (prepack did not emit into the tarball?): $installed_dist"
|
|
4929
5289
|
return 1
|
|
4930
5290
|
fi
|
|
4931
|
-
if [ -d "$npmroot/node_modules/@earendil-works" ]; then
|
|
4932
|
-
fail "[check-pack-install]
|
|
5291
|
+
if [ ! -d "$npmroot/node_modules/@earendil-works/pi-coding-agent" ]; then
|
|
5292
|
+
fail "[check-pack-install] the npm-managed install lacks @earendil-works/pi-coding-agent — the pi-free boot proof below needs pi PRESENT to deny it (#130 production SDK)"
|
|
4933
5293
|
return 1
|
|
4934
5294
|
fi
|
|
5295
|
+
local deny_pi="$npm_tmp/deny-pi/deny-pi.mjs"
|
|
5296
|
+
mkdir -p "$(dirname "$deny_pi")"
|
|
5297
|
+
cat > "$deny_pi" <<'DENY'
|
|
5298
|
+
import { registerHooks } from "node:module";
|
|
5299
|
+
registerHooks({
|
|
5300
|
+
resolve(specifier, context, next) {
|
|
5301
|
+
if (specifier.startsWith("@earendil-works/")) throw new Error(`pi-free boot violated: the bridge imported ${specifier}`);
|
|
5302
|
+
return next(specifier, context);
|
|
5303
|
+
},
|
|
5304
|
+
});
|
|
5305
|
+
DENY
|
|
5306
|
+
# Control: the hook really refuses an @earendil-works import from this tree (else the proof is vacuous).
|
|
5307
|
+
local deny_ctl
|
|
5308
|
+
deny_ctl=$(cd "$npmroot" && node --import "$deny_pi" --input-type=module -e "await import('@earendil-works/pi-ai')" 2>&1) && {
|
|
5309
|
+
fail "[check-pack-install] the pi-deny hook let @earendil-works/pi-ai load — the pi-free boot proof would be vacuous"
|
|
5310
|
+
return 1
|
|
5311
|
+
}
|
|
5312
|
+
if ! grep -q "pi-free boot violated: the bridge imported @earendil-works/pi-ai" <<<"$deny_ctl"; then
|
|
5313
|
+
fail "[check-pack-install] the pi-deny control failed for another reason:"; echo "$deny_ctl" | tail -5 | sed 's/^/ /' >&2; return 1
|
|
5314
|
+
fi
|
|
4935
5315
|
local boot_out
|
|
4936
|
-
if ! boot_out=$(START_SH="$installed_start" node --input-type=module <<'JS'
|
|
5316
|
+
if ! boot_out=$(START_SH="$installed_start" DENY_PI="$deny_pi" node --input-type=module <<'JS'
|
|
4937
5317
|
import { spawn } from 'node:child_process';
|
|
4938
5318
|
const start = process.env.START_SH;
|
|
4939
5319
|
// Sanitize the child env so the pi-free proof cannot be masked by a leaked
|
|
4940
5320
|
// module-resolution path: if NODE_PATH (or a stray pi env) pointed at a tree
|
|
4941
5321
|
// holding @earendil-works, a statically pi-importing eager graph could resolve
|
|
4942
5322
|
// and boot anyway, turning this gate falsely green. Strip it so "boots with
|
|
4943
|
-
// @earendil
|
|
5323
|
+
// every @earendil-works resolution refused" stays an honest adversarial proof.
|
|
4944
5324
|
const env = { ...process.env };
|
|
4945
5325
|
delete env.NODE_PATH;
|
|
5326
|
+
// #130: pi is installed beside the bridge now; every @earendil-works resolution is refused instead.
|
|
5327
|
+
env.NODE_OPTIONS = `${env.NODE_OPTIONS ?? ''} --import ${process.env.DENY_PI}`.trim();
|
|
5328
|
+
delete env.DENY_PI;
|
|
4946
5329
|
const child = spawn(start, { stdio: ['pipe', 'pipe', 'pipe'], env });
|
|
4947
5330
|
let stdout = '', stderr = '', done = false;
|
|
4948
5331
|
const timer = setTimeout(() => {
|
|
@@ -5666,13 +6049,15 @@ setup_mode() {
|
|
|
5666
6049
|
esac
|
|
5667
6050
|
}
|
|
5668
6051
|
|
|
5669
|
-
# Supported pi range, derived at runtime from the package.json
|
|
5670
|
-
#
|
|
5671
|
-
# Never retyped here as a second literal.
|
|
6052
|
+
# Supported HOST pi range, derived at runtime from the pinned SDK generation — package.json's exact
|
|
6053
|
+
# production dependency on pi-coding-agent, which check-dep-versions binds to the durable pin's SDK
|
|
6054
|
+
# set: `>=<pin> <<major>.<minor+1>`. Never retyped here as a second literal. This is the ordinary Pi
|
|
6055
|
+
# on PATH that loads Entwurf's extensions; the carrier's own SDK set is a different subject
|
|
6056
|
+
# (pi-durable-runtime.ts), and a host inside this range is not required to be the same copy.
|
|
5672
6057
|
pi_supported_range() {
|
|
5673
6058
|
node -e '
|
|
5674
6059
|
const pkg = require(process.argv[1]);
|
|
5675
|
-
const pin = pkg.
|
|
6060
|
+
const pin = pkg.dependencies?.["@earendil-works/pi-coding-agent"];
|
|
5676
6061
|
if (!/^\d+\.\d+\.\d+$/.test(pin ?? "")) { console.error(`unparseable pi pin: ${pin}`); process.exit(1); }
|
|
5677
6062
|
const [maj, min] = pin.split(".").map(Number);
|
|
5678
6063
|
console.log(`>=${pin} <${maj}.${min + 1}`);
|
|
@@ -6024,6 +6409,19 @@ setup_all() {
|
|
|
6024
6409
|
setup_result codex-terminal-title FAIL "detected codex, but the terminal-title setting did not complete (see above)"
|
|
6025
6410
|
fi
|
|
6026
6411
|
fi
|
|
6412
|
+
# ── pi-durable ── #129, #130: the durable app is this package's own carrier and its pi SDK set
|
|
6413
|
+
# is a production dependency, so there is nothing to detect and setup writes NOTHING for it —
|
|
6414
|
+
# the row reports whether the installed carrier can run. There is no SKIP: a missing piece is an
|
|
6415
|
+
# incomplete installation, a named FAIL repaired by reinstalling Entwurf. The verdict is the
|
|
6416
|
+
# same leaf the managed launch and the fresh preflight ask — 0 verified, anything else refused.
|
|
6417
|
+
local pd_rc pd_out
|
|
6418
|
+
pd_rc=0; pd_out=$(run_ts pi-extensions/lib/pi-durable-runtime.ts resolve 2>&1) || pd_rc=$?
|
|
6419
|
+
if [ "$pd_rc" -eq 0 ]; then
|
|
6420
|
+
setup_result pi-durable PASS "carrier and its pi SDK set verify — launch with 'entwurf pi-durable --provider <p> --model <id> --width task-wide'"
|
|
6421
|
+
else
|
|
6422
|
+
setup_result pi-durable FAIL "the installed pi-durable carrier is refused — ${pd_out##*$'\n'}"
|
|
6423
|
+
fi
|
|
6424
|
+
|
|
6027
6425
|
# ── core bridge boundary ── deterministic preflight lives in `pnpm run
|
|
6028
6426
|
# check:full`; live substrate acceptance lives in `LIVE=1 ./run.sh
|
|
6029
6427
|
# release-gate <scratch> --cut`. Setup is the install path, so it verifies the
|
|
@@ -6536,6 +6934,8 @@ release_gate() {
|
|
|
6536
6934
|
# Codex then opens the outbound Pi and correlates that callback. Actor coordinates travel,
|
|
6537
6935
|
# and fresh Codex == app-server/MCP inherited session == outbound Pi is a hard check.
|
|
6538
6936
|
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
|
|
6937
|
+
# #129 first-admission: step 9 clause 7 + the two cross-harness receipts for pi-durable.
|
|
6938
|
+
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
6939
|
|
|
6540
6940
|
# 4. BEHAVIOR lane (advisory, non-blocking). Model-in-loop gates that probe
|
|
6541
6941
|
# whether the model AUTONOMOUSLY drives the MCP entwurf surface. These never
|
|
@@ -6660,6 +7060,9 @@ case "$cmd" in
|
|
|
6660
7060
|
smoke-omp-fresh-live)
|
|
6661
7061
|
smoke_omp_fresh_live
|
|
6662
7062
|
;;
|
|
7063
|
+
smoke-pi-durable-fresh-live)
|
|
7064
|
+
smoke_pi_durable_fresh_live
|
|
7065
|
+
;;
|
|
6663
7066
|
check-capability-bundle-reach)
|
|
6664
7067
|
check_capability_bundle_reach
|
|
6665
7068
|
;;
|
|
@@ -6699,6 +7102,26 @@ case "$cmd" in
|
|
|
6699
7102
|
# on the status line; and still zero mailbox/receiver marker. Hermetic; no omp, no model turn
|
|
6700
7103
|
run_ts scripts/check-omp-birth-hook.ts
|
|
6701
7104
|
;;
|
|
7105
|
+
check-pi-durable-contact)
|
|
7106
|
+
# #129 L-identity G-contact, checkout-only. Spawning gate (VERIFY: a vitest lane cannot hold
|
|
7107
|
+
# it): the real overlay durable app as a child host, the branch-emitted bridge as ITS child,
|
|
7108
|
+
# /proc as the PPID oracle. Both runtime inputs are operator-provisioned; absent → SKIP 97,
|
|
7109
|
+
# never pass. Installed: run_ts refuses it (no compiled twin) before any input is read.
|
|
7110
|
+
run_ts scripts/check-pi-durable-contact.ts
|
|
7111
|
+
;;
|
|
7112
|
+
check-pi-durable-send)
|
|
7113
|
+
# #129 L-send first cell, checkout-only spawning gate: one native durable turn against a
|
|
7114
|
+
# gate-owned scripted endpoint (S) through the native ToolTask (H) to a seeded target. No
|
|
7115
|
+
# vendor turn (V). Inputs as check-pi-durable-contact; absent → SKIP 97.
|
|
7116
|
+
run_ts scripts/check-pi-durable-send.ts
|
|
7117
|
+
;;
|
|
7118
|
+
check-pi-durable-receive)
|
|
7119
|
+
# #129 L-receive first cell, checkout-only spawning gate: no controller input; a gate-owned
|
|
7120
|
+
# sending bridge enqueues, the host's doorbell admits to the root, the scripted endpoint (S)
|
|
7121
|
+
# holds request #1 until the enqueue is captured, the native turn (H) drains it. No vendor (V).
|
|
7122
|
+
# Inputs as check-pi-durable-contact; absent → SKIP 97.
|
|
7123
|
+
run_ts scripts/check-pi-durable-receive.ts
|
|
7124
|
+
;;
|
|
6702
7125
|
check-copilot-statusline)
|
|
6703
7126
|
check_copilot_statusline
|
|
6704
7127
|
;;
|
|
@@ -6777,6 +7200,9 @@ case "$cmd" in
|
|
|
6777
7200
|
check-mux-fresh-call)
|
|
6778
7201
|
check_mux_fresh_call
|
|
6779
7202
|
;;
|
|
7203
|
+
check-pi-durable-beside)
|
|
7204
|
+
check_pi_durable_beside
|
|
7205
|
+
;;
|
|
6780
7206
|
check-mux-resume-call)
|
|
6781
7207
|
check_mux_resume_call
|
|
6782
7208
|
;;
|
|
@@ -7415,6 +7841,20 @@ case "$cmd" in
|
|
|
7415
7841
|
shift || true
|
|
7416
7842
|
exec bash "$REPO_DIR/scripts/copilot-launch.sh" "$@"
|
|
7417
7843
|
;;
|
|
7844
|
+
pi-durable)
|
|
7845
|
+
# #129 W (L3), #130: the MANAGED durable launch. Same shape and reasons as `copilot` above:
|
|
7846
|
+
# `exec`, no subshell and no cd — the durable TUI must own this terminal, the caller's cwd, this
|
|
7847
|
+
# pid and its exit status — and `shift`, because everything after the verb is the bootstrap's argv.
|
|
7848
|
+
#
|
|
7849
|
+
# The durable app is this package's CARRIER (pi/pi-durable/carrier/), and it is verified with its
|
|
7850
|
+
# declared pi SDK set BEFORE any of it runs: the resolver it prints is the first code this launch
|
|
7851
|
+
# loads. An incomplete package, a drifted carrier or an absent/mismatched SDK edge is a named
|
|
7852
|
+
# refusal (exit 3) from the same leaf the fresh preflight and setup ask. Nothing outside the
|
|
7853
|
+
# package is located, installed or repaired.
|
|
7854
|
+
shift || true
|
|
7855
|
+
pd_resolver=$(run_ts pi-extensions/lib/pi-durable-runtime.ts resolve) || exit $?
|
|
7856
|
+
exec node --import "$pd_resolver" "$REPO_DIR/pi/pi-durable/bootstrap.mjs" "$@"
|
|
7857
|
+
;;
|
|
7418
7858
|
pi)
|
|
7419
7859
|
# #118 홉 2: the managed pi launch. Same shape and same reasons as `copilot` above —
|
|
7420
7860
|
# `exec`, no subshell and no cd, because this operates on the operator's session; and
|