@junghanacs/entwurf 0.25.0 → 0.30.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 +1 -1
- package/BASELINE.md +1 -1
- package/CHANGELOG.md +182 -0
- package/CONTRIBUTING.md +1 -1
- package/DELIVERY.md +1 -1
- package/README.md +178 -674
- package/VERIFY.md +17 -16
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +6 -2
- package/docs/external-mcp-host.md +1 -1
- package/docs/setup-clean-host.md +346 -10
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +27 -4
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +12 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-peers-render.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-decider.js +2 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-mailbox.js +2 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-production.js +2 -2
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-surface.js +3 -5
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +3 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +13 -6
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-mailbox-body.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +3 -3
- package/mcp/entwurf-bridge/src/index.ts +30 -3
- package/package.json +11 -11
- package/pi-extensions/entwurf-control.ts +50 -637
- package/pi-extensions/lib/acp/backend-adapter.ts +13 -1
- package/pi-extensions/lib/entwurf-fact-provider.ts +2 -2
- package/pi-extensions/lib/entwurf-mcp-server.ts +74 -0
- package/pi-extensions/lib/entwurf-peers-render.ts +1 -1
- package/pi-extensions/lib/entwurf-v2-decider.ts +2 -2
- package/pi-extensions/lib/entwurf-v2-mailbox.ts +2 -2
- package/pi-extensions/lib/entwurf-v2-production.ts +3 -3
- package/pi-extensions/lib/entwurf-v2-surface.ts +3 -5
- package/pi-extensions/lib/entwurf-v2-visible-resume.ts +3 -3
- package/pi-extensions/lib/fresh-call-composition.ts +13 -6
- package/pi-extensions/lib/meta-mailbox-body.ts +1 -1
- package/pi-extensions/lib/mux-fresh-call.ts +3 -3
- package/run.sh +168 -54
- package/scripts/check-entwurf-peers-surface.ts +8 -15
- package/scripts/check-entwurf-self-address.ts +7 -11
- package/scripts/check-entwurf-v2-surface.ts +20 -112
- package/scripts/check-fresh-call-dispatch.ts +12 -13
- package/scripts/check-gate-qualification.ts +2741 -157
- package/scripts/check-install-surface.ts +3 -2
- package/scripts/check-mux-resume-call.ts +24 -35
- package/scripts/check-pi-bridge-sender.ts +130 -0
- package/scripts/check-pi-mcp-bridge.ts +251 -0
- package/scripts/lib/codex-fresh-live-protocol.ts +17 -5
- package/scripts/lib/codex-fresh-source-receipts.ts +21 -2
- package/scripts/lib/mutation-qualify.ts +269 -44
- package/scripts/lib/pi-mcp-bridge-probe.mjs +170 -0
- package/scripts/lib/qualification-acceptance.ts +146 -0
- package/scripts/lib/qualification-artifact.ts +359 -0
- package/scripts/lib/qualification-collection.ts +384 -0
- package/scripts/lib/qualification-gh.ts +231 -0
- package/scripts/lib/qualification-ledger.ts +669 -0
- package/scripts/lib/qualification-oracle-core.ts +279 -0
- package/scripts/lib/qualification-receipt.ts +561 -0
- package/scripts/lib/qualification-release.ts +271 -0
- package/scripts/meta-bridge-doctor.sh +16 -5
- package/scripts/mutants/acp-foreground-only.json +17 -0
- package/scripts/mutants/codex-native.json +11 -11
- package/scripts/mutants/control-send-receipt.json +6 -4
- package/scripts/mutants/copilot-birth.json +2 -1
- package/scripts/mutants/fresh-call-dispatch.json +3 -14
- package/scripts/mutants/gate-qualification.json +59 -0
- package/scripts/mutants/mux-fresh-call.json +43 -45
- package/scripts/mutants/mux-resume-call.json +11 -35
- package/scripts/mutants/pack-install.json +15 -4
- package/scripts/mutants/pi-bridge-sender.json +54 -0
- package/scripts/mutants/pi-floor.json +6 -2
- package/scripts/mutants/pi-mcp-bridge.json +36 -0
- package/scripts/mutants/pi-mcp-register.json +51 -0
- package/scripts/mutants/qualification-ledger.json +489 -0
- package/scripts/mutants/v2-surface.json +6 -6
- package/scripts/qualification-oracle.ts +124 -0
- package/scripts/resident-rpc-drive.ts +5 -1
- package/scripts/smoke-acp-bundled-mcp-live.ts +3 -1
- package/scripts/smoke-acp-cortex-live.ts +3 -1
- package/scripts/smoke-acp-session-reuse-live.ts +29 -12
- package/scripts/smoke-acp-socket-citizen-live.ts +3 -1
- package/scripts/smoke-acp-v2-send-live.ts +3 -1
- package/scripts/smoke-codex-fresh-live.ts +20 -1
- package/scripts/smoke-entwurf-chain-live.ts +6 -2
- package/scripts/smoke-entwurf-v2-matrix-live.ts +3 -1
- package/scripts/smoke-herdr-raw-install-live.sh +8 -3
- package/scripts/smoke-mux-lifecycle-live.ts +1 -1
- package/scripts/smoke-resident-garden-guard.sh +3 -1
package/run.sh
CHANGED
|
@@ -140,6 +140,9 @@ Usage:
|
|
|
140
140
|
./run.sh check-omp-fresh-preflight # #87 C: the OMP fresh preflight reproduces omp_agent_dir and the tools.xdev read in TS (it runs from two emit depths and cannot call a sibling script). This drives the SHIPPED shell/python leaves over the same inputs — refusals included — and requires the TS half to agree, so the reproduction cannot silently drift from the installer's own oracle
|
|
141
141
|
./run.sh check-codex-sender-identity # Codex tools/call _meta triple agreement + addressable record join + cross-carrier conflict refusal
|
|
142
142
|
./run.sh check-codex-bridge-identity # real MCP bridge request identity for entwurf_self/v2/fresh; malformed or conflicting _meta fails loud
|
|
143
|
+
./run.sh check-pi-mcp-register # #125: mutant execution coordinate for pi-extensions/entwurf-control.test.ts — the real extension births through Pi's real loader and registers the compiled bridge after birth, before UI, direct with two hidden. Needs build-bridge
|
|
144
|
+
./run.sh check-pi-mcp-bridge # #125: a real Pi runtime (builtin MCP, default stdio, faux model only) spawns the COMPILED bridge as the born citizen — six prefixed verbs declared, hidden two absent, child env = record = entwurf_self, cwd = session cwd; newSession vendor conformance observed (old child gone, new record+child, old record kept). Needs build-bridge
|
|
145
|
+
./run.sh check-pi-bridge-sender # #125: real bridge pi-session carrier is served only when it names a readable pi RECORD; missing / foreign-backend / unreadable each refuse with their own cause; no socket = replyable:false, not refusal
|
|
143
146
|
./run.sh check-codex-native-push # app-server WebSocket-over-UDS probe + measured one-shot codex queue delivery; no replay
|
|
144
147
|
./run.sh check-codex-birth-hook # sandboxed payload + declaration: first-turn V3 birth, one record per thread, top-level event predicate, no markers
|
|
145
148
|
./run.sh check-codex-fresh-preflight # pre-mutation birth/MCP/thread-title/default-app-server readiness
|
|
@@ -199,7 +202,7 @@ Usage:
|
|
|
199
202
|
./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
|
|
200
203
|
./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
|
|
201
204
|
./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
|
|
202
|
-
./run.sh check-gate-manifests # the HEAD of that qualification, on its own: runner self-test + the committed manifests validated against the origin index + the declared lane inventory. Executes ZERO mutants and never snapshots this repo (~
|
|
205
|
+
./run.sh check-gate-manifests # the HEAD of that qualification, on its own: runner self-test + the committed manifests validated against the origin index + the declared lane inventory. Executes ZERO committed mutants (only the self-test's synthetic fixture mutants, in tmp fixture repos) and never snapshots this repo (~20s), which is why it lives in check:hermetic while the body stays on CI/release-gate — three of the five reds qualification ever produced in CI died right here, before a mutant gate ran (#99 B-3)
|
|
203
206
|
./run.sh check-gate-qualification # kill-proof qualification (the gate-of-gates): runner self-test (classifier truth table + synthetic negatives incl. wrong-reason/hang/control-red/impurity) + committed mutant manifests (scripts/mutants/*.json) run in an isolated snapshot repo under control→mutant→restore→control; the real checkout is never written. Evidence = claim IDs + killed mutant IDs, never assertion counts
|
|
204
207
|
./run.sh check-probe-ordering # §11-7 ordering-probe deterministic gate: raw-client SAMENESS pinned to backend.ts (sequence/args/timeouts/permission policy — the probe may never measure a lookalike), phase attribution incl. set-model, probe-mode fixture wire markers (delay honored, probeRunId REQUIRED, smoke-acp-mcp-live legacy compat), event-log door integrity (reserved keys refused at write; unknown marker name, broken sort axis, or a payload the classifier cannot judge on is MALFORMED, never a quiet event), and the §11-7 paired-verdict truth table (P0/I0 outside the space, phase-qualified D, B promotion ladder, C, A two-delay rule). Offline/deterministic; one product claim (no production prompt cutoff) is replant-qualified via scripts/mutants/probe-ordering.json, while the other assertions are direct [CHECK:*] contracts
|
|
205
208
|
./run.sh check-probe-cli-shim # §11-7-c B-name-snapshot PRODUCER gate: the CLI shim driven as a REAL process against fake CLIs (no API, no cost). Proves what a defect would buy — FABRICATED evidence (a malformed init is never reported as an empty name set; the boot report carries the true target path+sha256 the classifier verifies against the roster), a DESTROYED turn (byte transparency across mid-UTF8/CRLF/oversized/unterminated framing, exit-code fidelity, signal re-raise, inbound signal forwarding, stderr passthrough, stdout backpressure), and LEAKED operator state (exact-allowlist env scrub, no argv/env/prompt body in the log). Offline/deterministic; 20 direct [CHECK:*] contracts, deliberately no longer replant-qualified after #70 subtraction
|
|
@@ -277,6 +280,7 @@ Usage:
|
|
|
277
280
|
./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
|
|
278
281
|
./run.sh check-pack # publish gate (dry-run): npm pack --dry-run + tarball invariants (runtime-critical present, dev residue absent)
|
|
279
282
|
./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
|
+
./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
|
|
280
284
|
./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
|
|
281
285
|
./run.sh check-pack-install # heavy publish gate (prepublishOnly): actual npm pack + tar -tf + fresh-temp install smoke + the #116 M3-b1 herdr-plugin RUNTIME VERIFIER run against that actual installed tarball (the focused check-herdr-runtime-bootstrap drives a fixture; THIS is where the real package is proven) with the pinned pi peers (pins derived from the package.json devDep; check-dep-versions binds them) + the npm-installed bridge BOOTS (tools/list) and DELIVERS (tools/call entwurf_v2 → .msg lands) + the installed all-absent and copilot-present (four-unit fake-vendor) `entwurf setup` rows + the INSTALLED generation lifecycle on a seeded previous-generation host (REFUSE before activation writes / zero Claude invocations → installed fresh-cut archives + opens empty → install-meta-bridge PASSES) + the INSTALLED-PACKAGE branch of the Copilot and OMP birth installers actually RUN (compiled entry selected, no raw .ts, and a real birth edge mints a citizen — the half a required-artifact list can never stand in for)
|
|
282
286
|
./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)
|
|
@@ -1336,6 +1340,24 @@ check_entwurf_resume_args() {
|
|
|
1336
1340
|
run_vitest pi-extensions/lib/entwurf-resume-args.test.ts
|
|
1337
1341
|
}
|
|
1338
1342
|
|
|
1343
|
+
check_acp_foreground_only() {
|
|
1344
|
+
# NOT a discovery path — check-tests-beside-behavior finds the test by glob. This case is the
|
|
1345
|
+
# MUTANT EXECUTION COORDINATE for scripts/mutants/acp-foreground-only.json (same reasoning as
|
|
1346
|
+
# check_entwurf_resume_args above), with a file filter narrow on purpose.
|
|
1347
|
+
section "claude ACP foreground-only launch env (mutant execution coordinate)"
|
|
1348
|
+
run_vitest pi-extensions/lib/acp/backend-adapter.test.ts
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
check_pi_mcp_register() {
|
|
1352
|
+
# NOT a discovery path — check-tests-beside-behavior finds the test by glob. This case is the
|
|
1353
|
+
# MUTANT EXECUTION COORDINATE for scripts/mutants/pi-mcp-register.json (same reasoning as
|
|
1354
|
+
# check_entwurf_resume_args above): run_vitest is what emits the structured failed-test titles a
|
|
1355
|
+
# kill is attributed from. It sits in check:hermetic, not check:contracts, because the subject
|
|
1356
|
+
# registers the COMPILED bridge and refuses by name when `build-bridge` has not run.
|
|
1357
|
+
section "pi MCP registration after birth (mutant execution coordinate)"
|
|
1358
|
+
run_vitest pi-extensions/entwurf-control.test.ts
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1339
1361
|
check_mux_launcher_fence() {
|
|
1340
1362
|
# Deterministic gate for the shared operator-launcher fence (issue #67):
|
|
1341
1363
|
# scripts/lib/claude-launcher-fence.ts plus its wiring into BOTH mux LIVE smokes. Replants the
|
|
@@ -2185,11 +2207,11 @@ assert.equal(peerTui, piAi,
|
|
|
2185
2207
|
// an open `>=` floor is exactly how the next installer re-acquires the drift.
|
|
2186
2208
|
// The floor is also the HARD MINIMUM a consumer install resolves: at `>=0.86.0`
|
|
2187
2209
|
// an existing 0.85.1 host is upgraded, not kept.
|
|
2188
|
-
// Expected shape: `>=<devDep>
|
|
2210
|
+
// Expected shape: `>=<devDep> <<major>.<minor+1>` (e.g. `>=0.86.0 <0.87`, `>=1.0.0 <1.1`).
|
|
2211
|
+
// Pi 1.0 did not change the rule: an unverified minor is still unverified, so the ceiling
|
|
2212
|
+
// stays the next minor of the pin's own major.
|
|
2189
2213
|
const [piMaj, piMin] = piAi.split('.').map(Number);
|
|
2190
|
-
|
|
2191
|
-
`pi pin major must stay 0 for the next-minor ceiling rule (got ${piAi}); revisit check-dep-versions when pi reaches 1.x`);
|
|
2192
|
-
const expectedPeer = `>=${piAi} <0.${piMin + 1}`;
|
|
2214
|
+
const expectedPeer = `>=${piAi} <${piMaj}.${piMin + 1}`;
|
|
2193
2215
|
const peerDepAi = pkg.peerDependencies?.['@earendil-works/pi-ai'];
|
|
2194
2216
|
const peerDepCoding = pkg.peerDependencies?.['@earendil-works/pi-coding-agent'];
|
|
2195
2217
|
const peerDepTui = pkg.peerDependencies?.['@earendil-works/pi-tui'];
|
|
@@ -2258,13 +2280,13 @@ function stripBumpLedger(file, text) {
|
|
|
2258
2280
|
let rangeDecls = 0, exactDecls = 0;
|
|
2259
2281
|
for (const file of BASELINE_DOCS) {
|
|
2260
2282
|
const text = stripBumpLedger(file, readFileSync(file, 'utf8'));
|
|
2261
|
-
// Closed-range declarations: `>=<floor>
|
|
2262
|
-
for (const [decl, floor, ceilMinor] of text.matchAll(/>=\s?(\d+\.\d+\.\d+)\s?<\s?
|
|
2283
|
+
// Closed-range declarations: `>=<floor> <<major>.<ceiling>` (spaces optional).
|
|
2284
|
+
for (const [decl, floor, ceilMajor, ceilMinor] of text.matchAll(/>=\s?(\d+\.\d+\.\d+)\s?<\s?(\d+)\.(\d+)/g)) {
|
|
2263
2285
|
rangeDecls++;
|
|
2264
2286
|
assert.equal(floor, piAi,
|
|
2265
2287
|
`${file}: declared pi floor in "${decl}" is ${floor}, but the devDep pin is ${piAi} — a baseline doc may not advertise a version no gate drives`);
|
|
2266
|
-
assert.equal(
|
|
2267
|
-
`${file}: declared pi ceiling in "${decl}" must be the next minor (
|
|
2288
|
+
assert.equal(`${ceilMajor}.${ceilMinor}`, `${piMaj}.${piMin + 1}`,
|
|
2289
|
+
`${file}: declared pi ceiling in "${decl}" must be the next minor (${piMaj}.${piMin + 1})`);
|
|
2268
2290
|
}
|
|
2269
2291
|
// Exact install pins: `@earendil-works/pi-<pkg>@<version>`.
|
|
2270
2292
|
for (const [decl, ver] of text.matchAll(/@earendil-works\/pi-(?:ai|coding-agent|tui)@(\d+\.\d+\.\d+)/g)) {
|
|
@@ -2295,12 +2317,14 @@ for (const [file, re, shape] of PROSE_DECLS) {
|
|
|
2295
2317
|
// ONE assertion, because the claim token may appear exactly once in a gate source.
|
|
2296
2318
|
// Every way the floor can fragment is collected first and named individually in the
|
|
2297
2319
|
// failure, so "which surface" is never lost to the single-token rule.
|
|
2298
|
-
const nextFloorRange = `>=${piAi}
|
|
2320
|
+
const nextFloorRange = `>=${piAi} <${piMaj}.${piMin + 1}`;
|
|
2299
2321
|
const floorGaps = [];
|
|
2300
2322
|
const PI_CONSTELLATION = [
|
|
2301
2323
|
'@earendil-works/pi-ai', '@earendil-works/pi-coding-agent', '@earendil-works/pi-tui',
|
|
2302
2324
|
'@earendil-works/pi-agent-core', '@earendil-works/pi-client', '@earendil-works/pi-protocol',
|
|
2303
2325
|
'@earendil-works/pi-telemetry', '@earendil-works/chord',
|
|
2326
|
+
// `[측정 2026-09-30]` 0.99.1: pi-coding-agent's runtime deps gained the built-in extensions' packages.
|
|
2327
|
+
'@earendil-works/pi-codemode', '@earendil-works/pi-mcp',
|
|
2304
2328
|
];
|
|
2305
2329
|
const sameSet = (got, want, where) => {
|
|
2306
2330
|
const missing = want.filter((n) => !got.includes(n));
|
|
@@ -2343,9 +2367,9 @@ if (!matcherPin) {
|
|
|
2343
2367
|
// measurement in docs/mux-launch-rail.md, for one) are deliberately NOT scanned:
|
|
2344
2368
|
// they record what was true then, and rewriting them would falsify evidence.
|
|
2345
2369
|
const CURRENT_CONTRACT_DECLS = [
|
|
2346
|
-
['plugins/herdr/README.md', /npm install -g "@earendil-works\/pi-coding-agent@(>=[\d.]+
|
|
2347
|
-
['docs/mux-launch-rail.md', /현 supported range 는 `(>=[\d.]+
|
|
2348
|
-
['docs/acp-backend-rail.md', /devDep exact `(\d+\.\d+\.\d+)`, peer `(>=[\d.]+
|
|
2370
|
+
['plugins/herdr/README.md', /npm install -g "@earendil-works\/pi-coding-agent@(>=[\d.]+ <\d+\.\d+)"/, 'the herdr install line'],
|
|
2371
|
+
['docs/mux-launch-rail.md', /현 supported range 는 `(>=[\d.]+ <\d+\.\d+)`/, "mux-launch-rail's current supported range"],
|
|
2372
|
+
['docs/acp-backend-rail.md', /devDep exact `(\d+\.\d+\.\d+)`, peer `(>=[\d.]+ <\d+\.\d+)`/, "the ACP support row's exact+range pair"],
|
|
2349
2373
|
];
|
|
2350
2374
|
for (const [file, re, shape] of CURRENT_CONTRACT_DECLS) {
|
|
2351
2375
|
const m = readFileSync(file, 'utf8').match(re);
|
|
@@ -2864,13 +2888,14 @@ if (typeof FLOOR !== 'string' || !/^\d+\.\d+\.\d+$/.test(FLOOR)) {
|
|
|
2864
2888
|
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'})`);
|
|
2865
2889
|
process.exit(1);
|
|
2866
2890
|
}
|
|
2867
|
-
// The declared contract is a CLOSED range (`>=<devDep>
|
|
2891
|
+
// The declared contract is a CLOSED range (`>=<devDep> <<major>.<minor+1>`, enforced on
|
|
2868
2892
|
// package.json by check-dep-versions), so the runtime check must be closed too.
|
|
2869
2893
|
// A floor-only comparison would bless a resolved pi ABOVE the ceiling — and an
|
|
2870
2894
|
// out-of-range pi is exactly the drift this cut exists to stop: 0.80.6 landed on
|
|
2871
2895
|
// the dev box while the repo still declared 0.80.3, and every gate stayed green.
|
|
2872
2896
|
// Verifying only half of a declared range is the same lie in the other direction.
|
|
2873
|
-
const
|
|
2897
|
+
const [FLOOR_MAJOR, FLOOR_MINOR] = FLOOR.split('.').map(Number);
|
|
2898
|
+
const CEILING = `${FLOOR_MAJOR}.${FLOOR_MINOR + 1}.0`;
|
|
2874
2899
|
const cmp = (a, b) => {
|
|
2875
2900
|
const pa = a.split('.').map(Number), pb = b.split('.').map(Number);
|
|
2876
2901
|
for (let i = 0; i < 3; i++) { if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0); }
|
|
@@ -3710,7 +3735,7 @@ check_pack() {
|
|
|
3710
3735
|
# which is exactly what property (1)'s `(_|$)` absorbs; all three shapes are fixtures
|
|
3711
3736
|
# below so a future suffix change cannot pass vacuously.)
|
|
3712
3737
|
pack_install_leaked_pi() {
|
|
3713
|
-
grep '^@earendil-works+' | grep -Ev '@0\.
|
|
3738
|
+
grep '^@earendil-works+' | grep -Ev '@1\.0\.0(_|$)' || true
|
|
3714
3739
|
}
|
|
3715
3740
|
|
|
3716
3741
|
# Matcher self-test on SYNTHETIC lookalikes: a healthy install tree cannot exercise either
|
|
@@ -3730,35 +3755,88 @@ check_pack_pin_matcher() {
|
|
|
3730
3755
|
# None may leak; the two lookalikes must — a PREFIX-EXTENDED version (`0.86.0-beta.1`,
|
|
3731
3756
|
# the prerelease shape that an unanchored match would bless) and an off-pin version.
|
|
3732
3757
|
matcher_probe=$(printf '%s\n' \
|
|
3733
|
-
'@earendil-works+pi-ai@0.
|
|
3734
|
-
'@earendil-works+pi-ai@0.
|
|
3735
|
-
'@earendil-works+pi-ai@0.
|
|
3736
|
-
'@earendil-works+pi-ai@0.
|
|
3737
|
-
'@earendil-works+pi-ai@0.
|
|
3738
|
-
'@earendil-works+pi-agent-core@0.87.
|
|
3739
|
-
if [ "$matcher_probe" != '@earendil-works+pi-ai@0.
|
|
3740
|
-
@earendil-works+pi-agent-core@0.87.
|
|
3741
|
-
fail "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED] the pin-leak matcher must flag the prefix-extended 0.
|
|
3758
|
+
'@earendil-works+pi-ai@1.0.0' \
|
|
3759
|
+
'@earendil-works+pi-ai@1.0.0_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6' \
|
|
3760
|
+
'@earendil-works+pi-ai@1.0.0_ws@8.21.3' \
|
|
3761
|
+
'@earendil-works+pi-ai@1.0.0_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6__ws@8.21.3_zod@4.3.6' \
|
|
3762
|
+
'@earendil-works+pi-ai@1.0.0-beta.1' \
|
|
3763
|
+
'@earendil-works+pi-agent-core@0.87.1' | pack_install_leaked_pi)
|
|
3764
|
+
if [ "$matcher_probe" != '@earendil-works+pi-ai@1.0.0-beta.1
|
|
3765
|
+
@earendil-works+pi-agent-core@0.87.1' ]; then
|
|
3766
|
+
fail "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED] the pin-leak matcher must flag the prefix-extended 1.0.0-beta.1 and the off-pin 0.87.1 lookalikes, and pass 1.0.0 bare or with any measured peer-hash — got: ${matcher_probe:-<nothing leaked>}"
|
|
3742
3767
|
return 1
|
|
3743
3768
|
fi
|
|
3744
3769
|
|
|
3745
3770
|
# Cell 2 — the closure prefix. chord is a non-`pi-` member of the same runtime closure
|
|
3746
|
-
# (0.85.0 onward, still true at 0.
|
|
3771
|
+
# (0.85.0 onward, still true at 0.99.1). An off-pin chord MUST leak; the pinned one must not. A matcher narrowed
|
|
3747
3772
|
# back to `^@earendil-works+pi-` sees nothing here and dies at this signature.
|
|
3748
3773
|
matcher_probe=$(printf '%s\n' \
|
|
3774
|
+
'@earendil-works+chord@1.0.0' \
|
|
3749
3775
|
'@earendil-works+chord@0.87.1' \
|
|
3750
|
-
'@earendil-works+
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
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 and `[측정 2026-09-23]` at 0.87.1: the same four @earendil-works direct deps) — got: ${matcher_probe:-<nothing leaked>}"
|
|
3776
|
+
'@earendil-works+pi-ai@1.0.0' | pack_install_leaked_pi)
|
|
3777
|
+
if [ "$matcher_probe" != '@earendil-works+chord@0.87.1' ]; then
|
|
3778
|
+
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>}"
|
|
3754
3779
|
return 1
|
|
3755
3780
|
fi
|
|
3756
3781
|
|
|
3757
3782
|
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)"
|
|
3758
3783
|
}
|
|
3759
3784
|
|
|
3785
|
+
# The writable roots of ONE consumer sandbox, shared by every consumer subcall of
|
|
3786
|
+
# check-pack-install that drives an installed `run.sh` writer. The writers resolve their agent
|
|
3787
|
+
# dir as `${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}` (`resolve`/`install` below), so swapping HOME
|
|
3788
|
+
# alone let an INHERITED PI_CODING_AGENT_DIR win and the consumer's user-scope registration land
|
|
3789
|
+
# outside the sandbox (#125, measured 2026-10-01: an inherited agent dir received the npm
|
|
3790
|
+
# consumer's settings.json while the gate looked under the temp HOME). Every root is named here,
|
|
3791
|
+
# none is left to the caller's environment. One NAME=value per line; read with mapfile.
|
|
3792
|
+
pack_install_consumer_env() {
|
|
3793
|
+
local h="$1"
|
|
3794
|
+
printf '%s\n' \
|
|
3795
|
+
"HOME=$h" \
|
|
3796
|
+
"XDG_CONFIG_HOME=$h/.config" \
|
|
3797
|
+
"XDG_DATA_HOME=$h/.local/share" \
|
|
3798
|
+
"XDG_STATE_HOME=$h/.local/state" \
|
|
3799
|
+
"XDG_CACHE_HOME=$h/.cache" \
|
|
3800
|
+
"PI_CODING_AGENT_DIR=$h/.pi/agent"
|
|
3801
|
+
}
|
|
3802
|
+
|
|
3803
|
+
# Self-test of the consumer roots, snapshot-safe like the pin matcher: under a POISONED inherited
|
|
3804
|
+
# environment every root a consumer subcall sees must sit inside the consumer home. Runs first
|
|
3805
|
+
# inside check-pack-install too, so the heavy gate cannot proceed on a leaking root set.
|
|
3806
|
+
check_pack_consumer_roots() {
|
|
3807
|
+
local h="/pack-consumer-home" roots=() seen line
|
|
3808
|
+
mapfile -t roots < <(pack_install_consumer_env "$h")
|
|
3809
|
+
seen=$(env HOME=/poisoned/home XDG_CONFIG_HOME=/poisoned/config XDG_DATA_HOME=/poisoned/data \
|
|
3810
|
+
XDG_STATE_HOME=/poisoned/state XDG_CACHE_HOME=/poisoned/cache PI_CODING_AGENT_DIR=/poisoned/agent \
|
|
3811
|
+
"${roots[@]}" sh -c 'printf "%s\n" "$HOME" "$XDG_CONFIG_HOME" "$XDG_DATA_HOME" "$XDG_STATE_HOME" "$XDG_CACHE_HOME" "$PI_CODING_AGENT_DIR"')
|
|
3812
|
+
while IFS= read -r line; do
|
|
3813
|
+
case "$line" in
|
|
3814
|
+
"$h" | "$h"/*) ;;
|
|
3815
|
+
*)
|
|
3816
|
+
fail "[QK:PACK-INSTALL-CONSUMER-ROOTS-CONTAINED] every writable root of a consumer subcall must be inside its own consumer home ($h) whatever the caller inherited — got: $(printf '%s' "$seen" | tr '\n' ' ')"
|
|
3817
|
+
return 1
|
|
3818
|
+
;;
|
|
3819
|
+
esac
|
|
3820
|
+
done <<<"$seen"
|
|
3821
|
+
echo "[check-pack-consumer-roots] ok — HOME, the four XDG roots and PI_CODING_AGENT_DIR of a consumer subcall all resolve inside the consumer home under a poisoned inherited environment"
|
|
3822
|
+
}
|
|
3823
|
+
|
|
3824
|
+
# Hand-built oracle for the REAL consumer run: check-pack-install exports a sentinel agent dir for
|
|
3825
|
+
# its whole body, standing in for whatever a caller inherits; a subcall that falls back to it
|
|
3826
|
+
# writes there, and this names the stage that did.
|
|
3827
|
+
pack_install_sentinel_untouched() {
|
|
3828
|
+
local sentinel="$1" stage="$2" found
|
|
3829
|
+
found=$(find "$sentinel" -mindepth 1 2>/dev/null | head -5)
|
|
3830
|
+
if [ -n "$found" ]; then
|
|
3831
|
+
fail "[QK:PACK-INSTALL-INHERITED-AGENTDIR-UNTOUCHED] $stage wrote into the inherited PI_CODING_AGENT_DIR instead of its own consumer sandbox:"
|
|
3832
|
+
printf '%s\n' "$found" | sed 's/^/ /' >&2
|
|
3833
|
+
return 1
|
|
3834
|
+
fi
|
|
3835
|
+
}
|
|
3836
|
+
|
|
3760
3837
|
_check_pack_install_impl() {
|
|
3761
3838
|
check_pack_pin_matcher || return 1
|
|
3839
|
+
check_pack_consumer_roots || return 1
|
|
3762
3840
|
|
|
3763
3841
|
# Heavy publish gate. Runs the remaining three checks in #13's
|
|
3764
3842
|
# publish checklist that check_pack (dry-run only) does not cover:
|
|
@@ -3797,6 +3875,11 @@ _check_pack_install_impl() {
|
|
|
3797
3875
|
local pack_tmp
|
|
3798
3876
|
pack_tmp=$(mktemp -d -t entwurf-pack.XXXXXX)
|
|
3799
3877
|
tgz_path="${pack_tmp}/${tgz_name}"
|
|
3878
|
+
# Stand-in for an inherited agent dir, scoped to this function: no consumer subcall may fall
|
|
3879
|
+
# back to it (pack_install_sentinel_untouched). It also keeps the caller's own one out of reach.
|
|
3880
|
+
local agentdir_sentinel="${pack_tmp}/inherited-agent-dir"
|
|
3881
|
+
mkdir -p "$agentdir_sentinel"
|
|
3882
|
+
local -x PI_CODING_AGENT_DIR="$agentdir_sentinel"
|
|
3800
3883
|
|
|
3801
3884
|
# 0.12.1 C — stale-dist guard. `tsc` emit does NOT prune orphaned files from
|
|
3802
3885
|
# outDir, and `files: ["mcp/"]` would carry any leftover dist file into the
|
|
@@ -4027,18 +4110,24 @@ _check_pack_install_impl() {
|
|
|
4027
4110
|
# pi-agent-core incident. The list moves WITH the floor and is re-measured at
|
|
4028
4111
|
# each bump, never pruned on a single quiet minor.
|
|
4029
4112
|
# The verified floor is 0.86.0 as of 2026-09-20.
|
|
4030
|
-
|
|
4113
|
+
# `[측정 2026-09-30]` at 0.99.1 the constellation GREW: pi-coding-agent now declares
|
|
4114
|
+
# @earendil-works/pi-codemode and @earendil-works/pi-mcp as runtime dependencies (the built-in
|
|
4115
|
+
# codemode and MCP extensions), so both are pinned beside the eight. pi-client and pi-protocol are
|
|
4116
|
+
# still published at 0.99.1 and keep their pins under the rule above.
|
|
4117
|
+
echo "[check-pack-install] pnpm add into $tmp (with 1.0.x peers + chord + codemode + mcp + typebox)"
|
|
4031
4118
|
local install_log
|
|
4032
4119
|
install_log=$(cd "$tmp" && pnpm add \
|
|
4033
4120
|
"$tgz_path" \
|
|
4034
|
-
"@earendil-works/pi-ai@0.
|
|
4035
|
-
"@earendil-works/pi-coding-agent@0.
|
|
4036
|
-
"@earendil-works/pi-tui@0.
|
|
4037
|
-
"@earendil-works/pi-agent-core@0.
|
|
4038
|
-
"@earendil-works/pi-client@0.
|
|
4039
|
-
"@earendil-works/pi-protocol@0.
|
|
4040
|
-
"@earendil-works/pi-telemetry@0.
|
|
4041
|
-
"@earendil-works/chord@0.
|
|
4121
|
+
"@earendil-works/pi-ai@1.0.0" \
|
|
4122
|
+
"@earendil-works/pi-coding-agent@1.0.0" \
|
|
4123
|
+
"@earendil-works/pi-tui@1.0.0" \
|
|
4124
|
+
"@earendil-works/pi-agent-core@1.0.0" \
|
|
4125
|
+
"@earendil-works/pi-client@1.0.0" \
|
|
4126
|
+
"@earendil-works/pi-protocol@1.0.0" \
|
|
4127
|
+
"@earendil-works/pi-telemetry@1.0.0" \
|
|
4128
|
+
"@earendil-works/chord@1.0.0" \
|
|
4129
|
+
"@earendil-works/pi-codemode@1.0.0" \
|
|
4130
|
+
"@earendil-works/pi-mcp@1.0.0" \
|
|
4042
4131
|
"typebox@latest" \
|
|
4043
4132
|
--ignore-workspace --ignore-scripts 2>&1) || {
|
|
4044
4133
|
fail "[check-pack-install] pnpm add failed:"
|
|
@@ -4054,11 +4143,11 @@ _check_pack_install_impl() {
|
|
|
4054
4143
|
local leaked_pi
|
|
4055
4144
|
leaked_pi=$(ls "$tmp/node_modules/.pnpm" 2>/dev/null | pack_install_leaked_pi)
|
|
4056
4145
|
if [ -n "$leaked_pi" ]; then
|
|
4057
|
-
fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 0.
|
|
4146
|
+
fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 1.0.0):"
|
|
4058
4147
|
printf '%s\n' "$leaked_pi" | sed 's/^/ /' >&2
|
|
4059
4148
|
return 1
|
|
4060
4149
|
fi
|
|
4061
|
-
echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works package is 0.
|
|
4150
|
+
echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works package is 1.0.0 (chord included)"
|
|
4062
4151
|
|
|
4063
4152
|
# Resolve the installed package.json and confirm pi.extensions
|
|
4064
4153
|
# arrived intact. If pi.extensions is empty or missing, the
|
|
@@ -4271,11 +4360,14 @@ _check_pack_install_impl() {
|
|
|
4271
4360
|
ls -l "$npmroot/node_modules/.bin" 2>/dev/null | sed 's/^/ /' >&2 || true
|
|
4272
4361
|
return 1
|
|
4273
4362
|
fi
|
|
4274
|
-
|
|
4363
|
+
local consumer_env=()
|
|
4364
|
+
mapfile -t consumer_env < <(pack_install_consumer_env "$npmhome")
|
|
4365
|
+
wire_log=$(env "${consumer_env[@]}" "$npm_pkg/run.sh" install "$npmproj" 2>&1) || {
|
|
4275
4366
|
fail "[check-pack-install] npm-managed run.sh install failed (preflight rejected hoisted deps?):"
|
|
4276
4367
|
echo "$wire_log" | tail -15 | sed 's/^/ /' >&2
|
|
4277
4368
|
return 1
|
|
4278
4369
|
}
|
|
4370
|
+
pack_install_sentinel_untouched "$agentdir_sentinel" "npm-managed run.sh install" || return 1
|
|
4279
4371
|
if [ ! -f "$npmproj/.pi/settings.json" ]; then
|
|
4280
4372
|
fail "[check-pack-install] npm-managed run.sh install did not write settings.json:"
|
|
4281
4373
|
echo "$wire_log" | tail -15 | sed 's/^/ /' >&2
|
|
@@ -4313,7 +4405,7 @@ sys.exit(0 if any(isinstance(s,str) and s.endswith('/node_modules/@junghanacs/en
|
|
|
4313
4405
|
# a KNOWN flag and the entwurf provider must load, sourced only from user scope.
|
|
4314
4406
|
# Before the fix this printed "Unknown options: --entwurf-control".
|
|
4315
4407
|
local foreign_out
|
|
4316
|
-
foreign_out=$(cd "$tmp" &&
|
|
4408
|
+
foreign_out=$(cd "$tmp" && env "${consumer_env[@]}" "$pi_bin" --entwurf-control --list-models entwurf 2>&1) || {
|
|
4317
4409
|
fail "[check-pack-install] foreign-cwd --entwurf-control smoke failed (user-scope citizen not loading?):"
|
|
4318
4410
|
echo "$foreign_out" | tail -10 | sed 's/^/ /' >&2
|
|
4319
4411
|
return 1
|
|
@@ -4338,7 +4430,7 @@ sys.exit(0 if any(isinstance(s,str) and s.endswith('/node_modules/@junghanacs/en
|
|
|
4338
4430
|
}
|
|
4339
4431
|
npm2_pkg="$npmroot2/node_modules/@junghanacs/entwurf"
|
|
4340
4432
|
set +e
|
|
4341
|
-
two_root_out=$(
|
|
4433
|
+
two_root_out=$(env "${consumer_env[@]}" "$npm2_pkg/run.sh" install "$npmproj2" 2>&1)
|
|
4342
4434
|
two_root_rc=$?
|
|
4343
4435
|
set -e
|
|
4344
4436
|
if [ "$two_root_rc" -eq 0 ] || ! grep -q "takeover-user-scope" <<<"$two_root_out"; then
|
|
@@ -4359,7 +4451,7 @@ sys.exit(0 if ok_a and not bad_b else 1)
|
|
|
4359
4451
|
return 1
|
|
4360
4452
|
fi
|
|
4361
4453
|
set +e
|
|
4362
|
-
two_root_out=$(
|
|
4454
|
+
two_root_out=$(env "${consumer_env[@]}" "$npm2_pkg/run.sh" takeover-user-scope 2>&1)
|
|
4363
4455
|
two_root_rc=$?
|
|
4364
4456
|
set -e
|
|
4365
4457
|
if [ "$two_root_rc" -ne 0 ] || ! grep -q "takeover: user-scope entwurf registration moved" <<<"$two_root_out"; then
|
|
@@ -4391,7 +4483,7 @@ sys.exit(0 if st.get('installerRoot','').rstrip('/')== '$npm2_pkg' else 1)
|
|
|
4391
4483
|
return 1
|
|
4392
4484
|
fi
|
|
4393
4485
|
set +e
|
|
4394
|
-
two_root_out=$(
|
|
4486
|
+
two_root_out=$(env "${consumer_env[@]}" "$npm_pkg/run.sh" remove-user-scope 2>&1)
|
|
4395
4487
|
two_root_rc=$?
|
|
4396
4488
|
set -e
|
|
4397
4489
|
if [ "$two_root_rc" -eq 0 ] || ! python3 -c "
|
|
@@ -4404,6 +4496,7 @@ sys.exit(0 if any(isinstance(s,str) and s.rstrip('/')== '$npm2_pkg' for s in src
|
|
|
4404
4496
|
echo "$two_root_out" | tail -8 | sed 's/^/ /' >&2
|
|
4405
4497
|
return 1
|
|
4406
4498
|
fi
|
|
4499
|
+
pack_install_sentinel_untouched "$agentdir_sentinel" "two-root ownership row (install / takeover-user-scope / remove-user-scope)" || return 1
|
|
4407
4500
|
echo "[check-pack-install] two-root ownership row pass (second root: normal install refused naming takeover-user-scope; explicit takeover moved entry + provider installerRoot old->new; old root's inverse refused)"
|
|
4408
4501
|
|
|
4409
4502
|
# Installed-package aggregate `setup` row (#86 C1, review blocker 2026-08-26):
|
|
@@ -5153,7 +5246,9 @@ process.stdout.write(resolveCodexDefaultSocketPath(process.env));
|
|
|
5153
5246
|
JSON
|
|
5154
5247
|
local pc_seed_sha
|
|
5155
5248
|
pc_seed_sha=$(sha256sum "$pc_store/$pc_record" | cut -d' ' -f1)
|
|
5156
|
-
|
|
5249
|
+
# `-u`: the default resolution is only proven if no inherited agent dir can pre-empt it
|
|
5250
|
+
# (check-pack-install exports a sentinel one for its whole body).
|
|
5251
|
+
local pc_env=(-u PI_CODING_AGENT_DIR HOME="$pc_home" XDG_DATA_HOME="$pc_home/.local/share" XDG_STATE_HOME="$pc_home/.local/state" XDG_CACHE_HOME="$pc_home/.cache" CLAUDE_CONFIG_DIR="$pc_cfg" FAKE_CLAUDE_LOG="$pc_claude_log" PATH="$fake_claude_dir:$npmroot/node_modules/.bin:$PATH")
|
|
5157
5252
|
local pc_before pc_after pc_out pc_rc
|
|
5158
5253
|
pc_before=$(cd "$pc_home" && find . -type f -exec sha256sum {} + 2>/dev/null | sort)
|
|
5159
5254
|
|
|
@@ -5338,6 +5433,7 @@ JSON
|
|
|
5338
5433
|
fi
|
|
5339
5434
|
echo "[check-pack-install] installed doctor dispatch lock pass (store-scan → dist JS, v2-surface deferred)"
|
|
5340
5435
|
|
|
5436
|
+
pack_install_sentinel_untouched "$agentdir_sentinel" "a later installed-consumer cell" || return 1
|
|
5341
5437
|
ok "[check-pack-install] publish install smoke pass"
|
|
5342
5438
|
return 0
|
|
5343
5439
|
}
|
|
@@ -5563,15 +5659,15 @@ setup_mode() {
|
|
|
5563
5659
|
}
|
|
5564
5660
|
|
|
5565
5661
|
# Supported pi range, derived at runtime from the package.json devDependencies
|
|
5566
|
-
# pin (the SSOT check-dep-versions binds: peer range == `>=<pin>
|
|
5662
|
+
# pin (the SSOT check-dep-versions binds: peer range == `>=<pin> <<major>.<minor+1>`).
|
|
5567
5663
|
# Never retyped here as a second literal.
|
|
5568
5664
|
pi_supported_range() {
|
|
5569
5665
|
node -e '
|
|
5570
5666
|
const pkg = require(process.argv[1]);
|
|
5571
5667
|
const pin = pkg.devDependencies?.["@earendil-works/pi-coding-agent"];
|
|
5572
|
-
if (
|
|
5573
|
-
const [, min] = pin.split(".").map(Number);
|
|
5574
|
-
console.log(`>=${pin}
|
|
5668
|
+
if (!/^\d+\.\d+\.\d+$/.test(pin ?? "")) { console.error(`unparseable pi pin: ${pin}`); process.exit(1); }
|
|
5669
|
+
const [maj, min] = pin.split(".").map(Number);
|
|
5670
|
+
console.log(`>=${pin} <${maj}.${min + 1}`);
|
|
5575
5671
|
' "$REPO_DIR/package.json"
|
|
5576
5672
|
}
|
|
5577
5673
|
|
|
@@ -6655,6 +6751,9 @@ case "$cmd" in
|
|
|
6655
6751
|
check-entwurf-resume-args)
|
|
6656
6752
|
check_entwurf_resume_args
|
|
6657
6753
|
;;
|
|
6754
|
+
check-acp-foreground-only)
|
|
6755
|
+
check_acp_foreground_only
|
|
6756
|
+
;;
|
|
6658
6757
|
check-resume-launch-identity)
|
|
6659
6758
|
check_resume_launch_identity
|
|
6660
6759
|
;;
|
|
@@ -6938,9 +7037,10 @@ case "$cmd" in
|
|
|
6938
7037
|
# floor can pay for it: the runner self-test (classifier truth table + synthetic
|
|
6939
7038
|
# negatives) plus the REAL manifests — schema, global claim uniqueness, subjects
|
|
6940
7039
|
# tracked and lstat-regular in the origin index, claim tokens exactly once in their
|
|
6941
|
-
# gate sources — plus the declared lane inventory. It executes ZERO mutants
|
|
6942
|
-
#
|
|
6943
|
-
#
|
|
7040
|
+
# gate sources — plus the declared lane inventory. It executes ZERO committed mutants
|
|
7041
|
+
# (the self-test runs only synthetic fixture mutants in tmp fixture repos) and makes NO
|
|
7042
|
+
# snapshot of this repo; the body (check-gate-qualification) owns the committed set and
|
|
7043
|
+
# is unchanged. Cheap enough for check:hermetic (~20s measured 2026-09-29 on oracle).
|
|
6944
7044
|
run_ts scripts/check-gate-qualification.ts --manifests-only
|
|
6945
7045
|
;;
|
|
6946
7046
|
check-gate-qualification)
|
|
@@ -7661,6 +7761,17 @@ case "$cmd" in
|
|
|
7661
7761
|
shift || true
|
|
7662
7762
|
run_ts scripts/check-codex-bridge-identity.ts "$@"
|
|
7663
7763
|
;;
|
|
7764
|
+
check-pi-bridge-sender)
|
|
7765
|
+
shift || true
|
|
7766
|
+
run_ts scripts/check-pi-bridge-sender.ts "$@"
|
|
7767
|
+
;;
|
|
7768
|
+
check-pi-mcp-register)
|
|
7769
|
+
check_pi_mcp_register
|
|
7770
|
+
;;
|
|
7771
|
+
check-pi-mcp-bridge)
|
|
7772
|
+
shift || true
|
|
7773
|
+
run_ts scripts/check-pi-mcp-bridge.ts "$@"
|
|
7774
|
+
;;
|
|
7664
7775
|
check-codex-native-push)
|
|
7665
7776
|
shift || true
|
|
7666
7777
|
run_ts scripts/check-codex-native-push.ts "$@"
|
|
@@ -7687,6 +7798,9 @@ case "$cmd" in
|
|
|
7687
7798
|
check-pack-pin-matcher)
|
|
7688
7799
|
check_pack_pin_matcher
|
|
7689
7800
|
;;
|
|
7801
|
+
check-pack-consumer-roots)
|
|
7802
|
+
check_pack_consumer_roots
|
|
7803
|
+
;;
|
|
7690
7804
|
check-pack-install)
|
|
7691
7805
|
check_pack_install
|
|
7692
7806
|
;;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* check-entwurf-peers-surface — deterministic gate for the MCP
|
|
2
|
+
* check-entwurf-peers-surface — deterministic gate for the `entwurf_peers` (one MCP surface since #125)
|
|
3
3
|
* RENDER/PAYLOAD layer (0.11 Stage 0 step 4, slice 4c; #50 C4 re-author). Drives the PURE
|
|
4
4
|
* `renderEntwurfPeers` with a fabricated `EntwurfFactsResult` (no IO) and proves
|
|
5
5
|
* the surface contract (GPi + Fable 수렴 + the C4 demotion):
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
* dropped),
|
|
18
18
|
* - diagnostics sharing one (kind + message) AGGREGATE in text (F8) — including
|
|
19
19
|
* record-less-socket groups, which share a message per liveness,
|
|
20
|
-
* - WIRING guard (Fable e②/6): the MCP handler
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* - WIRING guard (Fable e②/6): the MCP handler calls listEntwurfFacts +
|
|
21
|
+
* renderEntwurfPeers; getLiveSessions and the `/entwurf-sessions` socket-scan
|
|
22
|
+
* command stay gone from the bridge AND the pi extension.
|
|
23
23
|
*
|
|
24
24
|
* No IO — the facts are fabricated; only the wiring guard reads the bridge source
|
|
25
25
|
* as text (a static assertion, not an execution).
|
|
@@ -252,31 +252,24 @@ function main(): void {
|
|
|
252
252
|
ok("the aggregated record-less line samples gids", agg.text.includes(gids[0] as string));
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
-
// ── WIRING guard:
|
|
255
|
+
// ── WIRING guard: the bridge calls the provider+render; the socket-scan lane is gone ──
|
|
256
|
+
// #125: the pi-native entwurf_peers copy is retired — a pi session reads the bridge's own
|
|
257
|
+
// surface through Pi's built-in MCP — so its wiring halves left with it. The pi file is still
|
|
258
|
+
// held to the lanes it must NOT grow back.
|
|
256
259
|
{
|
|
257
260
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
258
261
|
const bridgeSrc = readFileSync(path.join(here, "..", "mcp", "entwurf-bridge", "src", "index.ts"), "utf8");
|
|
259
262
|
const nativeSrc = readFileSync(path.join(here, "..", "pi-extensions", "entwurf-control.ts"), "utf8");
|
|
260
263
|
ok("wiring: bridge calls listEntwurfFacts(", bridgeSrc.includes("listEntwurfFacts("));
|
|
261
264
|
ok("wiring: bridge calls renderEntwurfPeers(", bridgeSrc.includes("renderEntwurfPeers("));
|
|
262
|
-
ok("wiring: native pi tool calls listEntwurfFacts(", nativeSrc.includes("listEntwurfFacts("));
|
|
263
|
-
ok("wiring: native pi tool calls renderEntwurfPeers(", nativeSrc.includes("renderEntwurfPeers("));
|
|
264
265
|
ok(
|
|
265
266
|
"#112 wiring: MCP observation uses the render limit",
|
|
266
267
|
bridgeSrc.includes("observationLimit: ENTWURF_PEERS_RENDER_LIMIT"),
|
|
267
268
|
);
|
|
268
|
-
ok(
|
|
269
|
-
"#112 wiring: native pi observation uses the render limit",
|
|
270
|
-
nativeSrc.includes("observationLimit: render.ENTWURF_PEERS_RENDER_LIMIT"),
|
|
271
|
-
);
|
|
272
269
|
ok(
|
|
273
270
|
"wiring: bridge does not paste full JSON payload into human text",
|
|
274
271
|
!bridgeSrc.includes("JSON.stringify(payload)"),
|
|
275
272
|
);
|
|
276
|
-
ok(
|
|
277
|
-
"wiring: native pi tool does not paste full JSON payload into human text",
|
|
278
|
-
!nativeSrc.includes("JSON.stringify(payload)"),
|
|
279
|
-
);
|
|
280
273
|
// `\bname\s*\(` catches a definition OR a call (tolerating a space before the
|
|
281
274
|
// paren, GPi Q4); a bare prose mention in a removal-note comment (no paren) is
|
|
282
275
|
// allowed — the guard targets the second scan, not the word.
|
|
@@ -495,17 +495,13 @@ ok(
|
|
|
495
495
|
/origin:\s*"meta-session"/.test(metaBody) && /replyable:\s*self\.replyable/.test(metaBody),
|
|
496
496
|
);
|
|
497
497
|
|
|
498
|
-
// ──
|
|
499
|
-
//
|
|
500
|
-
//
|
|
501
|
-
//
|
|
498
|
+
// ── pi-session replyability has ONE computation now (#125) ──
|
|
499
|
+
// The pi-native sender decoration (`decoratePiSenderAddressability`) left with the native verbs: a
|
|
500
|
+
// pi session reaches entwurf_v2/entwurf_self through the bridge, whose `buildStrictPiSenderEnvelope`
|
|
501
|
+
// derives replyability from the same computeSelfAddressability + canonical-socket existsSync and is
|
|
502
|
+
// pinned above. What the pi file still takes from this fence is the not-a-citizen notice below,
|
|
503
|
+
// reached through the non-literal dynamic import that keeps the `.ts` fence out of root tsc.
|
|
502
504
|
const nativeSrc = readFileSync(path.join(REPO_DIR, "pi-extensions", "entwurf-control.ts"), "utf8");
|
|
503
|
-
ok(
|
|
504
|
-
"pi-native: decoratePiSenderAddressability derives replyable from computeSelfAddressability + canonical existsSync",
|
|
505
|
-
/function\s+decoratePiSenderAddressability/.test(nativeSrc) &&
|
|
506
|
-
/computeSelfAddressability/.test(nativeSrc) &&
|
|
507
|
-
/existsSync\s*\(\s*getSocketPath/.test(nativeSrc),
|
|
508
|
-
);
|
|
509
505
|
ok(
|
|
510
506
|
"pi-native: reaches the self-address fence via non-literal dynamic import (no static import; TS5097)",
|
|
511
507
|
/const ENTWURF_SELF_ADDRESS_MODULE\s*=/.test(nativeSrc) &&
|
|
@@ -555,7 +551,7 @@ ok(
|
|
|
555
551
|
// a mutant to move it into, and passing a constant would make the decision above unreachable.
|
|
556
552
|
const noticeBody = nativeSrc.slice(
|
|
557
553
|
nativeSrc.indexOf("async function noticeUncitizenedSession"),
|
|
558
|
-
nativeSrc.indexOf("function
|
|
554
|
+
nativeSrc.indexOf("// Extension Export", nativeSrc.indexOf("async function noticeUncitizenedSession")),
|
|
559
555
|
);
|
|
560
556
|
ok(
|
|
561
557
|
"pi-native: the not-a-citizen notice is decided from the REAL flag, on the UI only, never on stderr " +
|