@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
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// check-entwurf-bridge-pi-free (0.12.1 A-gate, static half)
|
|
2
2
|
//
|
|
3
|
-
// INVARIANT: the entwurf-bridge MCP server must boot WITHOUT any pi
|
|
4
|
-
// (`@earendil-works/pi-*`). entwurf is a harness-neutral npm package; pi is
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
3
|
+
// INVARIANT: the entwurf-bridge MCP server must boot WITHOUT importing any pi
|
|
4
|
+
// package (`@earendil-works/pi-*`). entwurf is a harness-neutral npm package; pi is
|
|
5
|
+
// one adapter lane, not a boot dependency. A plain `npm install @junghanacs/entwurf`
|
|
6
|
+
// must still stand `entwurf-bridge` up and answer MCP `tools/list` without touching
|
|
7
|
+
// pi. Since #130 that install carries the pi SDK as a production dependency (the
|
|
8
|
+
// pi-durable carrier's), so the runtime half proves this under a deny hook in
|
|
9
|
+
// check-pack-install rather than by the SDK's absence.
|
|
8
10
|
//
|
|
9
11
|
// This gate is the STATIC half of that contract: it walks the EAGER static
|
|
10
12
|
// value-import closure of the bridge entry (`mcp/entwurf-bridge/src/index.ts`)
|
|
@@ -138,6 +138,24 @@ async function main(): Promise<void> {
|
|
|
138
138
|
!herdrBranch.includes("codexCallerFreshPreflight"),
|
|
139
139
|
);
|
|
140
140
|
|
|
141
|
+
// #129: pi-durable joined the tmux fresh set, NOT the herdr pilot set (GLG, 2026-10-06: no herdr
|
|
142
|
+
// pi-durable support in 0.31). Same axis as the codex cell: refused by the herdr reason, nothing
|
|
143
|
+
// spawned, no tmux fallback, and its installed-carrier preflight is never asked on this branch.
|
|
144
|
+
const durable = recordingSpawn();
|
|
145
|
+
const durableRefused = await dispatchFreshCall(
|
|
146
|
+
{ ...REQUEST, backend: "pi-durable", model: "loopback/scripted" },
|
|
147
|
+
HERDR_ENV,
|
|
148
|
+
durable.spawn,
|
|
149
|
+
);
|
|
150
|
+
ok(
|
|
151
|
+
"[QK:FCD-HERDR-PI-DURABLE-UNSUPPORTED] inside herdr pi-durable is not a pilot backend: it refuses by the herdr reason with nothing spawned, never falls back to a tmux window, and its runtime preflight is never consulted there",
|
|
152
|
+
durableRefused.rail === "herdr" &&
|
|
153
|
+
!durableRefused.result.ok &&
|
|
154
|
+
durableRefused.result.reason === "herdr-backend-unsupported" &&
|
|
155
|
+
durable.calls.length === 0 &&
|
|
156
|
+
!herdrBranch.includes("piDurable"),
|
|
157
|
+
);
|
|
158
|
+
|
|
141
159
|
// ── the tmux rail keeps its pre-existing ordering ────────────────────────────────────
|
|
142
160
|
// The import line names the preflight too, so the body is what gets counted: the claim is
|
|
143
161
|
// "called once, on one path", not "the word appears once".
|
|
@@ -66,9 +66,9 @@ import {
|
|
|
66
66
|
runGateBounded,
|
|
67
67
|
signatureAttributedToFailure,
|
|
68
68
|
signatureOnFailureLine,
|
|
69
|
-
sweepStaleSnapshots,
|
|
70
69
|
validateManifest,
|
|
71
70
|
validateManifestSet,
|
|
71
|
+
withOwnSnapshot,
|
|
72
72
|
} from "./lib/mutation-qualify.ts";
|
|
73
73
|
import { ACCEPTANCE_SCHEMA, checkAcceptanceRecord, RECORD_CONSISTENT } from "./lib/qualification-acceptance.ts";
|
|
74
74
|
import {
|
|
@@ -637,6 +637,32 @@ function fixtureSpec(patch: Partial<MutantSpec>): MutantSpec {
|
|
|
637
637
|
};
|
|
638
638
|
}
|
|
639
639
|
|
|
640
|
+
/**
|
|
641
|
+
* A PRIOR run's snapshot root inside a self-test's private `tmpRoot`, in the exact shape the
|
|
642
|
+
* retired ambient sweep deleted (snapshot prefix, a `runner.json` naming a dead pid). It is
|
|
643
|
+
* that run's evidence: no qualification run may list it, let alone remove it. `state()`
|
|
644
|
+
* names every entry with its bytes, so a removal or a rewrite both read as a change.
|
|
645
|
+
*/
|
|
646
|
+
const PRIOR_RUN_ROOT = "entwurf-qualify-prior1";
|
|
647
|
+
function seedPriorRunRoot(tmpRoot: string): { before: string; state: () => string } {
|
|
648
|
+
const dir = path.join(tmpRoot, PRIOR_RUN_ROOT);
|
|
649
|
+
fs.mkdirSync(dir);
|
|
650
|
+
fs.writeFileSync(path.join(dir, "runner.json"), JSON.stringify({ pid: 999_999_999, startedAt: 0 }));
|
|
651
|
+
fs.writeFileSync(path.join(dir, "evidence.log"), "a prior run's red evidence\n");
|
|
652
|
+
const state = (): string =>
|
|
653
|
+
fs.existsSync(dir)
|
|
654
|
+
? fs
|
|
655
|
+
.readdirSync(dir)
|
|
656
|
+
.sort()
|
|
657
|
+
.map((name) => `${name}\0${fs.readFileSync(path.join(dir, name), "utf8")}`)
|
|
658
|
+
.join("\n")
|
|
659
|
+
: "absent";
|
|
660
|
+
return { before: state(), state };
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/** Each prior-root observation, asserted ONCE under its QK by Phase 1c-s (the token is exactly-once). */
|
|
664
|
+
const priorRunKept: Record<string, boolean> = {};
|
|
665
|
+
|
|
640
666
|
const quiet = (): void => {};
|
|
641
667
|
|
|
642
668
|
{
|
|
@@ -897,21 +923,6 @@ const quiet = (): void => {};
|
|
|
897
923
|
ok(`pgroup: the GRANDCHILD (pid ${grandchild}) is dead after the group SIGKILL`, dead);
|
|
898
924
|
fs.rmSync(invocationDir, { recursive: true, force: true });
|
|
899
925
|
}
|
|
900
|
-
|
|
901
|
-
// Stale-snapshot sweep: dead-pid residue is reclaimed, a live runner's dir survives.
|
|
902
|
-
{
|
|
903
|
-
const tmpRoot = reclaimOnExit(fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-qualify-sweep-")));
|
|
904
|
-
const deadDir = path.join(tmpRoot, "entwurf-qualify-dead1");
|
|
905
|
-
fs.mkdirSync(deadDir);
|
|
906
|
-
fs.writeFileSync(path.join(deadDir, "runner.json"), JSON.stringify({ pid: 999_999_999, startedAt: 0 }));
|
|
907
|
-
const liveDir = path.join(tmpRoot, "entwurf-qualify-live1");
|
|
908
|
-
fs.mkdirSync(liveDir);
|
|
909
|
-
fs.writeFileSync(path.join(liveDir, "runner.json"), JSON.stringify({ pid: process.pid, startedAt: Date.now() }));
|
|
910
|
-
const swept = sweepStaleSnapshots(tmpRoot);
|
|
911
|
-
ok("sweep: a dead-pid snapshot dir is reclaimed", swept.includes(deadDir) && !fs.existsSync(deadDir));
|
|
912
|
-
ok("sweep: a live runner's snapshot dir survives", fs.existsSync(liveDir));
|
|
913
|
-
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
914
|
-
}
|
|
915
926
|
} finally {
|
|
916
927
|
fs.rmSync(fixtureOrigin, { recursive: true, force: true });
|
|
917
928
|
fs.rmSync(path.dirname(externalTarget), { recursive: true, force: true });
|
|
@@ -1262,8 +1273,11 @@ function buildDeclaredOutputFixture(): string {
|
|
|
1262
1273
|
logError: quiet,
|
|
1263
1274
|
tmpRoot,
|
|
1264
1275
|
});
|
|
1265
|
-
|
|
1276
|
+
// This run's own snapshots only: the seeded prior root is not this run's to reclaim.
|
|
1277
|
+
const snapshotsLeft = (): string[] =>
|
|
1278
|
+
fs.readdirSync(tmpRoot).filter((f) => f.startsWith("entwurf-qualify-") && f !== PRIOR_RUN_ROOT);
|
|
1266
1279
|
const readReceipt = (name: string) => JSON.parse(fs.readFileSync(out(name), "utf8"));
|
|
1280
|
+
const prior = seedPriorRunRoot(tmpRoot);
|
|
1267
1281
|
|
|
1268
1282
|
const green = await runFx(["bash", "gates/kill.sh"], "green.json");
|
|
1269
1283
|
const gr = readReceipt("green.json");
|
|
@@ -1279,6 +1293,7 @@ function buildDeclaredOutputFixture(): string {
|
|
|
1279
1293
|
gr.receiptSha256 === green.receiptSha256,
|
|
1280
1294
|
);
|
|
1281
1295
|
ok("group run: the snapshot is reclaimed", snapshotsLeft().length === 0);
|
|
1296
|
+
priorRunKept["group green"] = prior.state() === prior.before;
|
|
1282
1297
|
|
|
1283
1298
|
const red = await runFx(["bash", "gates/survive.sh"], "red.json");
|
|
1284
1299
|
const rr = readReceipt("red.json");
|
|
@@ -1286,6 +1301,7 @@ function buildDeclaredOutputFixture(): string {
|
|
|
1286
1301
|
"group run: a SURVIVED mutant is still written — red receipt, passed false",
|
|
1287
1302
|
red.passed === false && rr.result.passed === false && rr.result.mutants[0].verdict === "SURVIVED",
|
|
1288
1303
|
);
|
|
1304
|
+
priorRunKept["group red"] = prior.state() === prior.before && snapshotsLeft().length === 0;
|
|
1289
1305
|
|
|
1290
1306
|
let refused = "";
|
|
1291
1307
|
try {
|
|
@@ -1405,6 +1421,7 @@ const refuseArgs = (argv: string[], naming: string): void =>
|
|
|
1405
1421
|
const receiptOf = (dir: string, file: string | null) =>
|
|
1406
1422
|
JSON.parse(fs.readFileSync(path.join(tmpRoot, dir, file ?? ""), "utf8"));
|
|
1407
1423
|
|
|
1424
|
+
const prior = seedPriorRunRoot(tmpRoot);
|
|
1408
1425
|
const abc = await collect("abc", [sA, sB, sC]);
|
|
1409
1426
|
ok(
|
|
1410
1427
|
"[QK:COLLECT-RED-DOES-NOT-STOP] collect: green / red / green — the red middle does not stop the tail; collection COMPLETE and intact",
|
|
@@ -1430,8 +1447,9 @@ const refuseArgs = (argv: string[], naming: string): void =>
|
|
|
1430
1447
|
) &&
|
|
1431
1448
|
rB.result.control === "ok" &&
|
|
1432
1449
|
rB.result.mutants[0].verdict === "SURVIVED" &&
|
|
1433
|
-
fs.readdirSync(tmpRoot).every((f) => !f.startsWith("entwurf-qualify-")),
|
|
1450
|
+
fs.readdirSync(tmpRoot).every((f) => f === PRIOR_RUN_ROOT || !f.startsWith("entwurf-qualify-")),
|
|
1434
1451
|
);
|
|
1452
|
+
priorRunKept["collect green/red/green"] = prior.state() === prior.before;
|
|
1435
1453
|
ok(
|
|
1436
1454
|
"collect: runId/runAttempt recorded exactly as the environment gave them; requested order kept",
|
|
1437
1455
|
abc.runId === "123" &&
|
|
@@ -1586,6 +1604,47 @@ const refuseArgs = (argv: string[], naming: string): void =>
|
|
|
1586
1604
|
}
|
|
1587
1605
|
}
|
|
1588
1606
|
|
|
1607
|
+
// ═══ Phase 1c-s — every real run owns exactly its own snapshot (#130 r3) ═════
|
|
1608
|
+
//
|
|
1609
|
+
// The group (1c-g) and collection (1c-m) fixtures above seeded a prior run's root and
|
|
1610
|
+
// recorded whether it survived their green and red runs. That holds for real runs only while
|
|
1611
|
+
// every real run goes THROUGH the seam, so the production callers are read here too: the full
|
|
1612
|
+
// body and the group receipt each make their snapshot only via withOwnSnapshot, the collection
|
|
1613
|
+
// only via the group receipt, and the library exports no sweep and lists no tmp root. The
|
|
1614
|
+
// retired ambient sweep removed a protected prior snapshot during a release-gate run; the
|
|
1615
|
+
// fixtures cannot see a caller that bypasses the seam, this can. All four observations are
|
|
1616
|
+
// asserted once, under the claim's single token.
|
|
1617
|
+
|
|
1618
|
+
{
|
|
1619
|
+
const read = (rel: string): string => fs.readFileSync(path.join(REPO_DIR, rel), "utf8");
|
|
1620
|
+
const qualifier = read("scripts/check-gate-qualification.ts");
|
|
1621
|
+
// The LAST occurrence is the real header: this block's own needle sits above it.
|
|
1622
|
+
const bodyAt = qualifier.lastIndexOf("// ═══ Phase 2m — several exact-argv groups");
|
|
1623
|
+
const body = qualifier.slice(bodyAt);
|
|
1624
|
+
const receipt = read("scripts/lib/qualification-receipt.ts");
|
|
1625
|
+
const collection = read("scripts/lib/qualification-collection.ts");
|
|
1626
|
+
const lib = read("scripts/lib/mutation-qualify.ts");
|
|
1627
|
+
const libExports = Object.keys(await import("./lib/mutation-qualify.ts"));
|
|
1628
|
+
priorRunKept.wiring =
|
|
1629
|
+
bodyAt > qualifier.indexOf("// ═══ Phase 1c-s") &&
|
|
1630
|
+
countOccurrences(body, "withOwnSnapshot(REPO_DIR, undefined,") === 1 &&
|
|
1631
|
+
countOccurrences(body, "createRepoSnapshot(") === 0 &&
|
|
1632
|
+
countOccurrences(receipt, "withOwnSnapshot(repoDir, opts.tmpRoot,") === 1 &&
|
|
1633
|
+
countOccurrences(receipt, "createRepoSnapshot(") === 0 &&
|
|
1634
|
+
countOccurrences(collection, "runGroupReceipt(") >= 1 &&
|
|
1635
|
+
countOccurrences(collection, "createRepoSnapshot(") === 0 &&
|
|
1636
|
+
countOccurrences(collection, "withOwnSnapshot(") === 0 &&
|
|
1637
|
+
countOccurrences(lib, "createRepoSnapshot(") === 2 &&
|
|
1638
|
+
countOccurrences(lib, "const snap = createRepoSnapshot(originDir, tmpRoot);") === 1 &&
|
|
1639
|
+
countOccurrences(lib, "readdirSync(tmpRoot") === 0 &&
|
|
1640
|
+
!libExports.some((name) => /sweep/i.test(name));
|
|
1641
|
+
const failed = Object.keys(priorRunKept).filter((k) => !priorRunKept[k]);
|
|
1642
|
+
ok(
|
|
1643
|
+
`[QK:QUALIFY-PRIOR-RUN-EVIDENCE-KEPT] a prior run's dead-pid snapshot root keeps every name and byte through a green and a red group run and a green/red/green collection, each run's own snapshot is reclaimed, and the full body and group receipt snapshot only through withOwnSnapshot — failed: ${failed.join(", ") || "none"}`,
|
|
1644
|
+
Object.keys(priorRunKept).length === 4 && failed.length === 0,
|
|
1645
|
+
);
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1589
1648
|
// ═══ Phase 1c-l — the composite ledger over REAL fixture receipts (#124 P2) ═
|
|
1590
1649
|
//
|
|
1591
1650
|
// A late failure, end to end: receipts are written by runGroupReceipt on a fixture
|
|
@@ -3342,15 +3401,16 @@ let manifestCount: number;
|
|
|
3342
3401
|
"copilot-birth": 19,
|
|
3343
3402
|
"copilot-launch": 14,
|
|
3344
3403
|
"pi-launch": 9,
|
|
3345
|
-
"pi-floor":
|
|
3404
|
+
"pi-floor": 3,
|
|
3346
3405
|
"pi-bridge-sender": 4,
|
|
3347
3406
|
"pi-mcp-bridge": 2,
|
|
3348
3407
|
"pi-mcp-register": 3,
|
|
3408
|
+
"pi-durable": 47,
|
|
3349
3409
|
"copilot-receive": 20,
|
|
3350
3410
|
"entwurf-peers": 1,
|
|
3351
3411
|
"fresh-call-dispatch": 11,
|
|
3352
3412
|
"fresh-cut": 3,
|
|
3353
|
-
"gate-qualification":
|
|
3413
|
+
"gate-qualification": 8,
|
|
3354
3414
|
"herdr-placement": 13,
|
|
3355
3415
|
"herdr-plugin": 25,
|
|
3356
3416
|
"herdr-plugin-profile": 14,
|
|
@@ -3365,7 +3425,7 @@ let manifestCount: number;
|
|
|
3365
3425
|
"meta-identity": 4,
|
|
3366
3426
|
"meta-retire": 4,
|
|
3367
3427
|
"mux-boundary": 16,
|
|
3368
|
-
"mux-fresh-call":
|
|
3428
|
+
"mux-fresh-call": 65,
|
|
3369
3429
|
"mux-launcher-fence": 8,
|
|
3370
3430
|
"mux-parent-artifact": 3,
|
|
3371
3431
|
"pack-install": 3,
|
|
@@ -3502,7 +3562,6 @@ if (ARGS.mode === "compose") {
|
|
|
3502
3562
|
if (ARGS.mode === "groups") {
|
|
3503
3563
|
const requested = ARGS.groups === "all" ? allGroupArgv(selected) : ARGS.groups;
|
|
3504
3564
|
checkRequestedGroups(requested, selected);
|
|
3505
|
-
sweepStaleSnapshots(os.tmpdir());
|
|
3506
3565
|
const c = await collectGroups({
|
|
3507
3566
|
repoDir: REPO_DIR,
|
|
3508
3567
|
selected,
|
|
@@ -3531,7 +3590,6 @@ if (ARGS.mode === "groups") {
|
|
|
3531
3590
|
// prints `[gate-qualification] ok` and never claims the full set.
|
|
3532
3591
|
|
|
3533
3592
|
if (ARGS.mode === "group") {
|
|
3534
|
-
sweepStaleSnapshots(os.tmpdir());
|
|
3535
3593
|
const { passed: groupPassed } = await runGroupReceipt({
|
|
3536
3594
|
repoDir: REPO_DIR,
|
|
3537
3595
|
selected,
|
|
@@ -3549,17 +3607,12 @@ if (ARGS.mode === "group") {
|
|
|
3549
3607
|
const headBefore = originHead(REPO_DIR);
|
|
3550
3608
|
const workSurfaceBefore = originWorkSurfaceSha(REPO_DIR);
|
|
3551
3609
|
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
try {
|
|
3559
|
-
report = await qualifyMutants(snap, selected, (line) => console.log(line));
|
|
3560
|
-
} finally {
|
|
3561
|
-
fs.rmSync(snap.baseDir, { recursive: true, force: true });
|
|
3562
|
-
}
|
|
3610
|
+
const report = await withOwnSnapshot(REPO_DIR, undefined, (snap) => {
|
|
3611
|
+
console.log(
|
|
3612
|
+
`[gate-qualification] snapshot: ${snap.repoDir} (${snap.fileCount} files, origin HEAD ${headBefore.slice(0, 12)})`,
|
|
3613
|
+
);
|
|
3614
|
+
return qualifyMutants(snap, selected, (line) => console.log(line));
|
|
3615
|
+
});
|
|
3563
3616
|
|
|
3564
3617
|
// Tripwire, not recovery: the runner never writes the origin, so any drift here is
|
|
3565
3618
|
// an outside writer racing the qualification — evidence cannot be attributed.
|
|
@@ -205,12 +205,13 @@ const {
|
|
|
205
205
|
COMPONENT_STATES,
|
|
206
206
|
} = mod;
|
|
207
207
|
|
|
208
|
-
/** The pi pin package.json declares
|
|
208
|
+
/** The pi pin package.json declares (an exact production dependency since #130) — the SSOT
|
|
209
|
+
* `run.sh pi_supported_range` derives its range from. */
|
|
209
210
|
const PI_PIN = (
|
|
210
211
|
JSON.parse(fs.readFileSync(path.join(REPO, "package.json"), "utf8")) as {
|
|
211
|
-
|
|
212
|
+
dependencies: Record<string, string>;
|
|
212
213
|
}
|
|
213
|
-
).
|
|
214
|
+
).dependencies["@earendil-works/pi-coding-agent"];
|
|
214
215
|
|
|
215
216
|
/** A `pi` stand-in that reports `version` and nothing else — enough for a `--version` floor probe. */
|
|
216
217
|
function fakePi(dir: string, name: string, version: string): string {
|
|
@@ -364,7 +365,7 @@ function ledgerWith(
|
|
|
364
365
|
{
|
|
365
366
|
const env = world("pi-floor");
|
|
366
367
|
const bin = path.join(env.HOME as string, "bin");
|
|
367
|
-
// The supported range is DERIVED from the package.json
|
|
368
|
+
// The supported range is DERIVED from the package.json production dependency pin (run.sh
|
|
368
369
|
// `pi_supported_range`), so the fixtures are derived from it too — a hardcoded "0.85.1" here
|
|
369
370
|
// would silently stop being out-of-range the day the pin moves past it.
|
|
370
371
|
const [maj, min] = PI_PIN.split(".").map(Number);
|