@junghanacs/entwurf 0.31.0 → 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 +3 -2
- package/CHANGELOG.md +82 -0
- package/README.md +46 -36
- package/VERIFY.md +35 -16
- package/demo/README.md +1 -1
- package/docs/acp-backend-rail.md +1 -1
- package/docs/setup-clean-host.md +112 -95
- package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-composition.js +1 -1
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +8 -10
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +3 -3
- package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js +153 -205
- package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +6 -4
- package/mcp/entwurf-bridge/src/index.ts +1 -1
- package/package.json +14 -17
- package/pi/pi-durable/bootstrap.mjs +142 -19
- 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/upstream-pin.json +103 -11
- package/pi-extensions/lib/fresh-call-composition.ts +1 -1
- package/pi-extensions/lib/mux-fresh-call.ts +8 -10
- package/pi-extensions/lib/pi-durable-fresh-preflight.ts +3 -3
- package/pi-extensions/lib/pi-durable-runtime.ts +197 -263
- package/pi-extensions/meta-bridge-pi-durable.ts +11 -7
- package/run.sh +508 -235
- package/scripts/check-entwurf-bridge-pi-free.ts +7 -5
- package/scripts/check-fresh-call-dispatch.ts +1 -1
- package/scripts/check-gate-qualification.ts +86 -34
- package/scripts/check-herdr-activation.ts +5 -4
- package/scripts/check-pi-durable-contact.ts +26 -93
- package/scripts/check-pi-durable-receive.ts +22 -59
- package/scripts/check-pi-durable-send.ts +235 -75
- package/scripts/emit-pi-durable-carrier.ts +355 -0
- package/scripts/lib/mutation-qualify.ts +21 -45
- package/scripts/lib/pi-durable-operator-guard.ts +89 -0
- package/scripts/lib/qualification-receipt.ts +7 -11
- package/scripts/mutants/control-send-receipt.json +1 -1
- package/scripts/mutants/gate-qualification.json +13 -0
- package/scripts/mutants/mux-fresh-call.json +13 -13
- package/scripts/mutants/pack-install.json +3 -3
- package/scripts/mutants/pi-durable.json +442 -109
- package/scripts/mutants/pi-floor.json +24 -2
- package/scripts/pi-durable-contact-host.mjs +3 -3
- package/scripts/pi-durable-receive-host.mjs +3 -3
- package/scripts/pi-durable-send-host.mjs +76 -5
- package/scripts/smoke-herdr-raw-install-live.sh +1 -1
- package/scripts/smoke-pi-durable-fresh-live.ts +3 -3
- package/scripts/smoke-setup-verdict.sh +15 -15
- package/scripts/smoke-user-scope-citizen.sh +1 -1
|
@@ -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`)
|
|
@@ -140,7 +140,7 @@ async function main(): Promise<void> {
|
|
|
140
140
|
|
|
141
141
|
// #129: pi-durable joined the tmux fresh set, NOT the herdr pilot set (GLG, 2026-10-06: no herdr
|
|
142
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
|
|
143
|
+
// spawned, no tmux fallback, and its installed-carrier preflight is never asked on this branch.
|
|
144
144
|
const durable = recordingSpawn();
|
|
145
145
|
const durableRefused = await dispatchFreshCall(
|
|
146
146
|
{ ...REQUEST, backend: "pi-durable", model: "loopback/scripted" },
|
|
@@ -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,16 +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,
|
|
3349
|
-
"pi-durable":
|
|
3408
|
+
"pi-durable": 47,
|
|
3350
3409
|
"copilot-receive": 20,
|
|
3351
3410
|
"entwurf-peers": 1,
|
|
3352
3411
|
"fresh-call-dispatch": 11,
|
|
3353
3412
|
"fresh-cut": 3,
|
|
3354
|
-
"gate-qualification":
|
|
3413
|
+
"gate-qualification": 8,
|
|
3355
3414
|
"herdr-placement": 13,
|
|
3356
3415
|
"herdr-plugin": 25,
|
|
3357
3416
|
"herdr-plugin-profile": 14,
|
|
@@ -3503,7 +3562,6 @@ if (ARGS.mode === "compose") {
|
|
|
3503
3562
|
if (ARGS.mode === "groups") {
|
|
3504
3563
|
const requested = ARGS.groups === "all" ? allGroupArgv(selected) : ARGS.groups;
|
|
3505
3564
|
checkRequestedGroups(requested, selected);
|
|
3506
|
-
sweepStaleSnapshots(os.tmpdir());
|
|
3507
3565
|
const c = await collectGroups({
|
|
3508
3566
|
repoDir: REPO_DIR,
|
|
3509
3567
|
selected,
|
|
@@ -3532,7 +3590,6 @@ if (ARGS.mode === "groups") {
|
|
|
3532
3590
|
// prints `[gate-qualification] ok` and never claims the full set.
|
|
3533
3591
|
|
|
3534
3592
|
if (ARGS.mode === "group") {
|
|
3535
|
-
sweepStaleSnapshots(os.tmpdir());
|
|
3536
3593
|
const { passed: groupPassed } = await runGroupReceipt({
|
|
3537
3594
|
repoDir: REPO_DIR,
|
|
3538
3595
|
selected,
|
|
@@ -3550,17 +3607,12 @@ if (ARGS.mode === "group") {
|
|
|
3550
3607
|
const headBefore = originHead(REPO_DIR);
|
|
3551
3608
|
const workSurfaceBefore = originWorkSurfaceSha(REPO_DIR);
|
|
3552
3609
|
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
try {
|
|
3560
|
-
report = await qualifyMutants(snap, selected, (line) => console.log(line));
|
|
3561
|
-
} finally {
|
|
3562
|
-
fs.rmSync(snap.baseDir, { recursive: true, force: true });
|
|
3563
|
-
}
|
|
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
|
+
});
|
|
3564
3616
|
|
|
3565
3617
|
// Tripwire, not recovery: the runner never writes the origin, so any drift here is
|
|
3566
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);
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* CHECKOUT-ONLY dev gate: an installed package refuses it in `run_ts` before it reads an input.
|
|
4
4
|
*
|
|
5
5
|
* LABEL: adapter-direct, harness-bypassed. The durable app is opened through the production
|
|
6
|
-
* bootstrap composition
|
|
6
|
+
* bootstrap composition from THIS checkout's carrier (pi/pi-durable/carrier/, under its resolver,
|
|
7
|
+
* against the checkout's pinned pi SDK set — verified first), the contact is born, and its
|
|
7
8
|
* registered tools are executed DIRECTLY in the host (scripts/pi-durable-contact-host.mjs). No
|
|
8
9
|
* model turn, no ToolTask: this gate does NOT prove that the harness selects, validates or
|
|
9
10
|
* replays these tools, that a model accepts their schema, or anything about send / delivery /
|
|
@@ -11,10 +12,9 @@
|
|
|
11
12
|
* the contact's own `receiver()` state and the bridge's replyability verdict — never a doorbell, an
|
|
12
13
|
* admission or a drain; those are check-pi-durable-receive's.
|
|
13
14
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* and the pin's git-ignored model data in place
|
|
15
|
+
* Input, required, not provisioned here (missing → SKIP, never a pass). The durable app itself is
|
|
16
|
+
* no longer an input: it is this checkout's carrier, refused by name before anything spawns when it
|
|
17
|
+
* or the SDK set it resolves is not the pin (#130).
|
|
18
18
|
* ENTWURF_PI_DURABLE_BRIDGE_ENTRY <bundle>/mcp/entwurf-bridge/src/index.js emitted from THIS
|
|
19
19
|
* checkout into a private bundle (a shared/installed bridge
|
|
20
20
|
* carries the old backend list and is refused as stale) — the
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
* `malformed-report` makes the gate's host driver send a non-JSON report line, which arrives while
|
|
45
45
|
* the gate is already waiting for it (the pending-line path of `Host.next`).
|
|
46
46
|
*/
|
|
47
|
-
import { type ChildProcess, spawn
|
|
47
|
+
import { type ChildProcess, spawn } from "node:child_process";
|
|
48
48
|
import { createHash } from "node:crypto";
|
|
49
49
|
import * as fs from "node:fs";
|
|
50
50
|
import * as os from "node:os";
|
|
@@ -52,16 +52,16 @@ import * as path from "node:path";
|
|
|
52
52
|
import { createInterface } from "node:readline";
|
|
53
53
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
54
54
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
55
|
-
import {
|
|
56
|
-
inspectPiDurableRuntime,
|
|
57
|
-
PI_DURABLE_RESOLVER_RELATIVE,
|
|
58
|
-
readPiDurablePin,
|
|
59
|
-
} from "../pi-extensions/lib/pi-durable-runtime.ts";
|
|
55
|
+
import { checkPiDurableRuntime, piDurableLayoutAt } from "../pi-extensions/lib/pi-durable-runtime.ts";
|
|
60
56
|
import { skipLive } from "./lib/live-skip.ts";
|
|
57
|
+
import {
|
|
58
|
+
describeOperatorSnapshot,
|
|
59
|
+
operatorPiDurableSnapshot,
|
|
60
|
+
operatorSnapshotUnchanged,
|
|
61
|
+
} from "./lib/pi-durable-operator-guard.ts";
|
|
61
62
|
|
|
62
63
|
const LABEL = "check-pi-durable-contact";
|
|
63
64
|
const REPO = path.resolve(import.meta.dirname, "..");
|
|
64
|
-
const OVERLAY = path.join(REPO, "pi", "pi-durable", "overlay");
|
|
65
65
|
const HOST_DRIVER = path.join(REPO, "scripts", "pi-durable-contact-host.mjs");
|
|
66
66
|
const REPORT_TIMEOUT_MS = 90_000;
|
|
67
67
|
const CLOSE_TIMEOUT_MS = 30_000;
|
|
@@ -74,17 +74,14 @@ if (FAULT !== undefined && FAULT !== "after-birth" && FAULT !== "malformed-repor
|
|
|
74
74
|
process.exit(2);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
const runtimeInput = process.env.ENTWURF_PI_DURABLE_RUNTIME?.trim();
|
|
78
77
|
const bridgeInput = process.env.ENTWURF_PI_DURABLE_BRIDGE_ENTRY?.trim();
|
|
79
|
-
if (!
|
|
78
|
+
if (!bridgeInput) {
|
|
80
79
|
skipLive(
|
|
81
80
|
LABEL,
|
|
82
|
-
"set
|
|
83
|
-
"
|
|
84
|
-
"this gate provisions neither",
|
|
81
|
+
"set ENTWURF_PI_DURABLE_BRIDGE_ENTRY=<bridge index.js emitted from this checkout into a private bundle>; " +
|
|
82
|
+
"this gate does not provision it",
|
|
85
83
|
);
|
|
86
84
|
}
|
|
87
|
-
const runtime = path.resolve(runtimeInput);
|
|
88
85
|
const bridgeEntry = path.resolve(bridgeInput);
|
|
89
86
|
|
|
90
87
|
let passed = 0;
|
|
@@ -104,36 +101,18 @@ function fatal(message: string): never {
|
|
|
104
101
|
process.exit(1);
|
|
105
102
|
}
|
|
106
103
|
|
|
107
|
-
const sha256 = (data: string | Buffer): string => createHash("sha256").update(data).digest("hex");
|
|
108
|
-
|
|
109
104
|
// ---------------------------------------------------------------------------
|
|
110
105
|
// 0. Inputs. A provisioned-but-wrong input is broken, not absent: these FAIL, they do not skip.
|
|
111
106
|
// ---------------------------------------------------------------------------
|
|
112
|
-
// The
|
|
113
|
-
// preflight use (pi-extensions/lib/pi-durable-runtime.ts)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const patchFile = path.join(OVERLAY, pin.patches[0] as string);
|
|
117
|
-
const facts = inspectPiDurableRuntime(runtime, OVERLAY);
|
|
118
|
-
const reverse = spawnSync("git", ["-C", runtime, "apply", "-R", "--check", patchFile]);
|
|
119
|
-
console.log("0. inputs: tracked overlay, ignored model data, bridge bundle registry");
|
|
120
|
-
ok(
|
|
121
|
-
`[QK:PDC-PIN] runtime HEAD is the overlay pin ${pin.commit} (got ${facts.head || "no answer"})`,
|
|
122
|
-
facts.gitAnswered && facts.head === pin.commit,
|
|
123
|
-
);
|
|
124
|
-
ok(
|
|
125
|
-
`[QK:PDC-TRACKED-OVERLAY-EXACT] the TRACKED tree is exactly pin + overlay patch and no non-ignored file is untracked (index ${facts.indexMatchesHead ? "==" : "!="} HEAD, diff ${facts.diffEqualsPatch ? "==" : "!="} patch ${sha256(patchBytes).slice(0, 12)}, untracked ${facts.untracked || "none"}) — git-ignored runtime inputs are outside this assertion`,
|
|
126
|
-
facts.gitAnswered && facts.indexMatchesHead && facts.diffEqualsPatch && facts.untracked === "",
|
|
127
|
-
);
|
|
128
|
-
ok("[QK:PDC-OVERLAY-REVERSE] the patch reverse-applies cleanly", reverse.status === 0);
|
|
107
|
+
// The carrier and its SDK set are verified by the ONE verifier the managed launch, setup and the
|
|
108
|
+
// fresh preflight use (pi-extensions/lib/pi-durable-runtime.ts), from this checkout's layout.
|
|
109
|
+
const carrier = checkPiDurableRuntime(piDurableLayoutAt(REPO));
|
|
110
|
+
console.log("0. inputs: the checkout carrier and its pi SDK set, bridge bundle registry");
|
|
129
111
|
ok(
|
|
130
|
-
`[QK:PDC-
|
|
131
|
-
|
|
132
|
-
facts.modelDataAggregate === pin.modelData.aggregateSha256 &&
|
|
133
|
-
facts.manifestSha256 === pin.modelData.manifestSha256,
|
|
112
|
+
`[QK:PDC-CARRIER-VERIFIED] this checkout's pi-durable carrier and the pi SDK set it resolves are the pin (${carrier.ok ? "verified" : `${carrier.reason}: ${carrier.detail}`})`,
|
|
113
|
+
carrier.ok,
|
|
134
114
|
);
|
|
135
|
-
const resolver =
|
|
136
|
-
ok("[QK:PDC-RESOLVER] the checkout carries the experimental source resolver", facts.resolverPresent);
|
|
115
|
+
const resolver = carrier.ok ? carrier.resolver : "";
|
|
137
116
|
const bundleRoot = path.resolve(bridgeEntry, "..", "..", "..", "..");
|
|
138
117
|
const bundleRegistry = path.join(bundleRoot, "pi-extensions", "entwurf-capabilities.json");
|
|
139
118
|
const repoRegistry = path.join(REPO, "pi", "entwurf-capabilities.json");
|
|
@@ -154,52 +133,14 @@ ok(
|
|
|
154
133
|
`[QK:PDC-BUNDLE-OWNER-JOIN] the bridge bundle's compiled owner join admits pi-durable-host (${joinLine || "not found"})`,
|
|
155
134
|
joinLine.includes('"pi-durable-host"'),
|
|
156
135
|
);
|
|
157
|
-
if (failed > 0)
|
|
158
|
-
fatal("inputs are not the pinned overlay, its model data, this checkout's registry and owner join — refusing to run");
|
|
136
|
+
if (failed > 0) fatal("inputs are not the verified carrier, this checkout's registry and owner join — refusing to run");
|
|
159
137
|
|
|
160
138
|
// ---------------------------------------------------------------------------
|
|
161
139
|
// Operator guard — COARSE and pi-durable-specific: the operator's own roots gain no pi-durable
|
|
162
140
|
// record, sender dir or durable session tree. Not a proof about any other operator byte.
|
|
163
141
|
// ---------------------------------------------------------------------------
|
|
164
142
|
const operatorAgent = path.join(os.homedir(), ".pi", "agent");
|
|
165
|
-
|
|
166
|
-
const sessions = path.join(operatorAgent, "meta-sessions");
|
|
167
|
-
const durableRecords = fs.existsSync(sessions)
|
|
168
|
-
? fs
|
|
169
|
-
.readdirSync(sessions)
|
|
170
|
-
.filter((n) => n.endsWith(".meta.json"))
|
|
171
|
-
.filter((n) => {
|
|
172
|
-
try {
|
|
173
|
-
return (
|
|
174
|
-
(JSON.parse(fs.readFileSync(path.join(sessions, n), "utf8")) as { backend?: unknown }).backend ===
|
|
175
|
-
"pi-durable"
|
|
176
|
-
);
|
|
177
|
-
} catch {
|
|
178
|
-
return false;
|
|
179
|
-
}
|
|
180
|
-
})
|
|
181
|
-
: [];
|
|
182
|
-
const senders = fs.existsSync(path.join(operatorAgent, "meta-senders", "pi-durable"));
|
|
183
|
-
const durableSessions = fs.existsSync(path.join(operatorAgent, "experimental", "durable-sessions"));
|
|
184
|
-
const receiversDir = path.join(operatorAgent, "meta-receivers");
|
|
185
|
-
const durableReceivers = fs.existsSync(receiversDir)
|
|
186
|
-
? fs
|
|
187
|
-
.readdirSync(receiversDir)
|
|
188
|
-
.filter((n) => n.endsWith(".json"))
|
|
189
|
-
.filter((n) => {
|
|
190
|
-
try {
|
|
191
|
-
return (
|
|
192
|
-
(JSON.parse(fs.readFileSync(path.join(receiversDir, n), "utf8")) as { backend?: unknown }).backend ===
|
|
193
|
-
"pi-durable"
|
|
194
|
-
);
|
|
195
|
-
} catch {
|
|
196
|
-
return false;
|
|
197
|
-
}
|
|
198
|
-
})
|
|
199
|
-
: [];
|
|
200
|
-
return JSON.stringify({ durableRecords, senders, durableSessions, durableReceivers });
|
|
201
|
-
}
|
|
202
|
-
const operatorBefore = operatorPiDurableFacts();
|
|
143
|
+
const operatorBefore = operatorPiDurableSnapshot(operatorAgent);
|
|
203
144
|
|
|
204
145
|
// ---------------------------------------------------------------------------
|
|
205
146
|
// Sandbox: private root, env built from nothing, children at umask 022.
|
|
@@ -810,17 +751,9 @@ ok(
|
|
|
810
751
|
`[QK:PDC-OWNED-REAPED] every host this gate spawned and every bridge observed under it is gone (${ownedSummary || "none spawned"}; alive ${stillAlive.join(",") || "none"})`,
|
|
811
752
|
stillAlive.length === 0 && cleanupProblems.length === 0,
|
|
812
753
|
);
|
|
813
|
-
const before = JSON.parse(operatorBefore) as {
|
|
814
|
-
durableRecords: string[];
|
|
815
|
-
senders: boolean;
|
|
816
|
-
durableReceivers: string[];
|
|
817
|
-
};
|
|
818
754
|
ok(
|
|
819
|
-
`[QK:PDC-OPERATOR-UNTOUCHED] coarse pi-durable guard: the operator's
|
|
820
|
-
|
|
821
|
-
before.durableRecords.length === 0 &&
|
|
822
|
-
!before.senders &&
|
|
823
|
-
before.durableReceivers.length === 0,
|
|
755
|
+
`[QK:PDC-OPERATOR-UNTOUCHED] coarse pi-durable registration guard: this run left the operator's pi-durable registrations exactly as it found them (${describeOperatorSnapshot(operatorBefore)} — same names, same bytes; existing state is not a red; no auth or session content read)`,
|
|
756
|
+
operatorSnapshotUnchanged(operatorBefore, operatorPiDurableSnapshot(operatorAgent)),
|
|
824
757
|
);
|
|
825
758
|
if (stillAlive.length > 0) console.log(`sandbox kept because owned processes are still alive: ${root}`);
|
|
826
759
|
else if (process.env.ENTWURF_PI_DURABLE_KEEP === "1") console.log(`sandbox kept: ${root}`);
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
* Intended single idle cell: controller input 0, doorbell admission 1, S requests 2, V 0.
|
|
41
41
|
*
|
|
42
42
|
* Inputs as check-pi-durable-contact (missing → SKIP 97, never a pass):
|
|
43
|
-
*
|
|
43
|
+
* ENTWURF_PI_DURABLE_BRIDGE_ENTRY (a bundle carrying the owner join; the durable app is this
|
|
44
|
+
* checkout's verified carrier, #130)
|
|
44
45
|
* Optional:
|
|
45
46
|
* ENTWURF_PI_DURABLE_RECEIPTS a NEW absolute directory (refused if it exists) for the cell's raw bytes.
|
|
46
47
|
*
|
|
@@ -58,12 +59,13 @@ import { createInterface } from "node:readline";
|
|
|
58
59
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
59
60
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
60
61
|
import { metaReceiverMarkerPath, upsertMetaSession, writeMetaSenderMarker } from "../pi-extensions/lib/meta-session.ts";
|
|
61
|
-
import {
|
|
62
|
-
inspectPiDurableRuntime,
|
|
63
|
-
PI_DURABLE_RESOLVER_RELATIVE,
|
|
64
|
-
readPiDurablePin,
|
|
65
|
-
} from "../pi-extensions/lib/pi-durable-runtime.ts";
|
|
62
|
+
import { checkPiDurableRuntime, piDurableLayoutAt } from "../pi-extensions/lib/pi-durable-runtime.ts";
|
|
66
63
|
import { skipLive } from "./lib/live-skip.ts";
|
|
64
|
+
import {
|
|
65
|
+
describeOperatorSnapshot,
|
|
66
|
+
operatorPiDurableSnapshot,
|
|
67
|
+
operatorSnapshotUnchanged,
|
|
68
|
+
} from "./lib/pi-durable-operator-guard.ts";
|
|
67
69
|
import { ownedReapVerdict } from "./lib/pi-durable-reap.ts";
|
|
68
70
|
import {
|
|
69
71
|
encodedRequestIdColumn,
|
|
@@ -75,7 +77,6 @@ import {
|
|
|
75
77
|
const LABEL = "check-pi-durable-receive";
|
|
76
78
|
const QK = "PRX";
|
|
77
79
|
const REPO = path.resolve(import.meta.dirname, "..");
|
|
78
|
-
const OVERLAY = path.join(REPO, "pi", "pi-durable", "overlay");
|
|
79
80
|
const HOST_DRIVER = path.join(REPO, "scripts", "pi-durable-receive-host.mjs");
|
|
80
81
|
const REPORT_TIMEOUT_MS = 120_000;
|
|
81
82
|
/** Validity cutoff for request #1 after the ENQUEUE — a gap bound, never a success inference. */
|
|
@@ -85,17 +86,14 @@ const REAP_TIMEOUT_MS = 5_000;
|
|
|
85
86
|
const CALL_ID = "call_prx_1";
|
|
86
87
|
const FINAL_TEXT = "read.";
|
|
87
88
|
|
|
88
|
-
const runtimeInput = process.env.ENTWURF_PI_DURABLE_RUNTIME?.trim();
|
|
89
89
|
const bridgeInput = process.env.ENTWURF_PI_DURABLE_BRIDGE_ENTRY?.trim();
|
|
90
|
-
if (!
|
|
90
|
+
if (!bridgeInput) {
|
|
91
91
|
skipLive(
|
|
92
92
|
LABEL,
|
|
93
|
-
"set
|
|
94
|
-
"
|
|
95
|
-
"this gate provisions neither",
|
|
93
|
+
"set ENTWURF_PI_DURABLE_BRIDGE_ENTRY=<bridge index.js emitted from this checkout into a private bundle>; " +
|
|
94
|
+
"this gate does not provision it",
|
|
96
95
|
);
|
|
97
96
|
}
|
|
98
|
-
const runtime = path.resolve(runtimeInput);
|
|
99
97
|
const bridgeEntry = path.resolve(bridgeInput);
|
|
100
98
|
const receiptsInput = process.env.ENTWURF_PI_DURABLE_RECEIPTS?.trim() || undefined;
|
|
101
99
|
if (receiptsInput !== undefined) {
|
|
@@ -158,28 +156,19 @@ function messageText(content: unknown): string {
|
|
|
158
156
|
}
|
|
159
157
|
|
|
160
158
|
// ---------------------------------------------------------------------------
|
|
161
|
-
// 0. Inputs — the
|
|
159
|
+
// 0. Inputs — the verified checkout carrier, the bundle registry, and the bundle's owner join.
|
|
162
160
|
// ---------------------------------------------------------------------------
|
|
163
|
-
// The
|
|
164
|
-
// preflight use (pi-extensions/lib/pi-durable-runtime.ts).
|
|
165
|
-
const
|
|
166
|
-
const
|
|
167
|
-
const resolver = path.join(runtime, PI_DURABLE_RESOLVER_RELATIVE);
|
|
161
|
+
// The carrier and its SDK set are verified by the ONE verifier the managed launch, setup and the fresh
|
|
162
|
+
// preflight use (pi-extensions/lib/pi-durable-runtime.ts), from this checkout's layout.
|
|
163
|
+
const carrier = checkPiDurableRuntime(piDurableLayoutAt(REPO));
|
|
164
|
+
const resolver = carrier.ok ? carrier.resolver : "";
|
|
168
165
|
const bundleRoot = path.resolve(bridgeEntry, "..", "..", "..", "..");
|
|
169
166
|
const bundleRegistry = path.join(bundleRoot, "pi-extensions", "entwurf-capabilities.json");
|
|
170
167
|
const bundleDeliverability = path.join(bundleRoot, "pi-extensions", "lib", "entwurf-deliverability.js");
|
|
171
168
|
console.log("0. inputs");
|
|
172
169
|
ok(
|
|
173
|
-
`[QK:${QK}-INPUTS]
|
|
174
|
-
|
|
175
|
-
facts.head === pin.commit &&
|
|
176
|
-
facts.indexMatchesHead &&
|
|
177
|
-
facts.diffEqualsPatch &&
|
|
178
|
-
facts.untracked === "" &&
|
|
179
|
-
facts.modelDataFiles === pin.modelData.files &&
|
|
180
|
-
facts.modelDataAggregate === pin.modelData.aggregateSha256 &&
|
|
181
|
-
facts.manifestSha256 === pin.modelData.manifestSha256 &&
|
|
182
|
-
facts.resolverPresent &&
|
|
170
|
+
`[QK:${QK}-INPUTS] this checkout's pi-durable carrier and the pi SDK set it resolves are the pin (${carrier.ok ? "verified" : `${carrier.reason}: ${carrier.detail}`}), and the bridge bundle's registry matches this checkout`,
|
|
171
|
+
carrier.ok &&
|
|
183
172
|
fs.existsSync(bridgeEntry) &&
|
|
184
173
|
fs.existsSync(bundleRegistry) &&
|
|
185
174
|
fs.readFileSync(bundleRegistry).equals(fs.readFileSync(path.join(REPO, "pi", "entwurf-capabilities.json"))),
|
|
@@ -192,33 +181,11 @@ ok(
|
|
|
192
181
|
`[QK:${QK}-BUNDLE-OWNER-JOIN] the bridge bundle's compiled owner join admits pi-durable-host (${joinLine || "not found"})`,
|
|
193
182
|
joinLine.includes('"pi-durable-host"'),
|
|
194
183
|
);
|
|
195
|
-
if (failed > 0) fatal("inputs are not the
|
|
184
|
+
if (failed > 0) fatal("inputs are not the verified carrier, this checkout's registry and owner join");
|
|
196
185
|
|
|
197
186
|
// Coarse pi-durable-specific operator guard (as G-contact): no operator-byte claim beyond it.
|
|
198
187
|
const operatorAgent = path.join(os.homedir(), ".pi", "agent");
|
|
199
|
-
|
|
200
|
-
const backendOf = (file: string): unknown => {
|
|
201
|
-
try {
|
|
202
|
-
return (JSON.parse(fs.readFileSync(file, "utf8")) as { backend?: unknown }).backend;
|
|
203
|
-
} catch {
|
|
204
|
-
return undefined;
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
const list = (dir: string, suffix: string) =>
|
|
208
|
-
fs.existsSync(dir)
|
|
209
|
-
? fs
|
|
210
|
-
.readdirSync(dir)
|
|
211
|
-
.filter((n) => n.endsWith(suffix))
|
|
212
|
-
.filter((n) => backendOf(path.join(dir, n)) === "pi-durable")
|
|
213
|
-
: [];
|
|
214
|
-
return JSON.stringify({
|
|
215
|
-
durableRecords: list(path.join(operatorAgent, "meta-sessions"), ".meta.json"),
|
|
216
|
-
senders: fs.existsSync(path.join(operatorAgent, "meta-senders", "pi-durable")),
|
|
217
|
-
durableReceivers: list(path.join(operatorAgent, "meta-receivers"), ".json"),
|
|
218
|
-
durableSessions: fs.existsSync(path.join(operatorAgent, "experimental", "durable-sessions")),
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
const operatorBefore = operatorPiDurableFacts();
|
|
188
|
+
const operatorBefore = operatorPiDurableSnapshot(operatorAgent);
|
|
222
189
|
|
|
223
190
|
// ---------------------------------------------------------------------------
|
|
224
191
|
// Sandbox: one private world — HOME/XDG/agent, the four garden roots and ENTWURF_DIR.
|
|
@@ -1096,13 +1063,9 @@ if (receiptsInput !== undefined) {
|
|
|
1096
1063
|
ok(`[QK:${QK}-RECEIPTS-EXPORTED] receipt export failed: ${(error as Error).message}`, false);
|
|
1097
1064
|
}
|
|
1098
1065
|
}
|
|
1099
|
-
const before = JSON.parse(operatorBefore) as { durableRecords: string[]; senders: boolean; durableReceivers: string[] };
|
|
1100
1066
|
ok(
|
|
1101
|
-
`[QK:${QK}-OPERATOR-UNTOUCHED] coarse pi-durable guard: the operator's
|
|
1102
|
-
|
|
1103
|
-
before.durableRecords.length === 0 &&
|
|
1104
|
-
!before.senders &&
|
|
1105
|
-
before.durableReceivers.length === 0,
|
|
1067
|
+
`[QK:${QK}-OPERATOR-UNTOUCHED] coarse pi-durable registration guard: this run left the operator's pi-durable registrations exactly as it found them (${describeOperatorSnapshot(operatorBefore)} — same names, same bytes; existing state is not a red; no auth or session content read)`,
|
|
1068
|
+
operatorSnapshotUnchanged(operatorBefore, operatorPiDurableSnapshot(operatorAgent)),
|
|
1106
1069
|
);
|
|
1107
1070
|
if (alive.length > 0) console.log(`sandbox kept because owned processes are still alive: ${root}`);
|
|
1108
1071
|
else if (process.env.ENTWURF_PI_DURABLE_KEEP === "1") console.log(`sandbox kept: ${root}`);
|