Files
stack/extensions/goal/test/fencing.test.ts
T

254 lines
11 KiB
TypeScript

// Hermetic NG-7 tests: goal-state incarnation fencing (Mercer F1 class).
// Run: node --test test/fencing.test.ts
//
// SAFETY (NG7-SAFETY-V1W2): every arm uses an ISOLATED temp directory —
// no live seat state, no Mercer/Marcie/marcie-2 files, no running sessions.
// The extension source edit is confined to store resolution (store.ts) and
// the one-line index.ts wiring.
import assert from "node:assert/strict";
import { test } from "node:test";
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync, readdirSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
resolveStatePath,
fencedStateFilePath,
loadState,
saveState,
stateFilePath,
} from "../lib/store.ts";
import { setGoal, pauseGoal, initialState } from "../lib/state.ts";
function fixtureDir(): string {
return mkdtempSync(join(tmpdir(), "goal-fence-"));
}
function writeLegacy(dir: string, text: string): void {
writeFileSync(stateFilePath(dir), JSON.stringify(setGoal(initialState(), text), null, 2) + "\n");
}
// ---- (a) hostile two-incarnation collision: the measured class ---------------
test("(a) two incarnations of one seat never share or clobber state", () => {
const dir = fixtureDir();
try {
const a = resolveStatePath(dir, { incarnationId: "inc-marcie" });
const b = resolveStatePath(dir, { incarnationId: "inc-marcie-2" });
assert.notEqual(a, b, "distinct incarnations must resolve distinct files");
// incarnation A sets an active goal
const stateA = setGoal(initialState(), "ship the canary");
saveState(stateA, a);
// incarnation B (a second process of the SAME seat) loads ITS state
const stateB = loadState(b);
assert.equal(stateB.status, "none", "B must not inherit A's active focus");
assert.equal(stateB.text, "", "B sees no goal text of A's");
// B sets its own goal; A's file is untouched
saveState(setGoal(initialState(), "B's own goal"), b);
assert.equal(loadState(a).text, "ship the canary", "A's focus survives B's activity");
assert.equal(loadState(b).text, "B's own goal");
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test("(a) B cannot pause or settle A's focus through any store operation", () => {
const dir = fixtureDir();
try {
const a = resolveStatePath(dir, { incarnationId: "inc-a" });
const b = resolveStatePath(dir, { incarnationId: "inc-b" });
saveState(setGoal(initialState(), "A active"), a);
// Every mutation B performs addresses b's path only; A's bytes cannot
// change through B's store API — proven by writing from B and re-reading A.
saveState(pauseGoal(loadState(b), "blocked:sibling", "cap"), b);
const aNow = loadState(a);
assert.equal(aNow.status, "active", "A's focus is still active after B's pause");
assert.equal(aNow.pausedReason, undefined);
// And the directory contains exactly the two fenced files.
const names = readdirSync(dir).filter((n) => n.startsWith("goal-state"));
assert.equal(names.length, 2);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test("(a) session_start-style reload stays incarnation-scoped", () => {
const dir = fixtureDir();
try {
const a = resolveStatePath(dir, { incarnationId: "inc-a" });
saveState(setGoal(initialState(), "A goal v1"), a);
// simulate: A's session reloads (new/resume/reload all re-load from ITS path)
const reloaded = loadState(resolveStatePath(dir, { incarnationId: "inc-a" }));
assert.equal(reloaded.text, "A goal v1");
// a sibling's state never leaks into A's reload
saveState(setGoal(initialState(), "sibling"), resolveStatePath(dir, { incarnationId: "inc-sib" }));
assert.equal(loadState(resolveStatePath(dir, { incarnationId: "inc-a" })).text, "A goal v1");
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
// ---- (b) legacy migration fails safe -------------------------------------------
test("(b) legacy unfenced state is QUARANTINED, never claimed (NG-7 F4)", () => {
const dir = fixtureDir();
try {
writeLegacy(dir, "legacy active goal");
const a = resolveStatePath(dir, { incarnationId: "inc-first" });
// FAIL SAFE: the first incarnation must NOT inherit the unknown owner's
// ACTIVE focus — it starts fresh.
const state = loadState(a);
assert.equal(state.status, "none", "no active focus inherited from legacy");
assert.equal(state.text, "");
// EVIDENCE PRESERVED: the legacy bytes live on in the quarantine file.
const quarantined = JSON.parse(readFileSync(join(dir, "goal-state.legacy.json"), "utf8")) as { text: string };
assert.equal(quarantined.text, "legacy active goal");
const names = readdirSync(dir);
assert.equal(names.includes("goal-state.json"), false, "legacy name vacated");
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test("(b/F4) ACTIVE legacy + fresh incarnation: no active focus inherited, evidence preserved", () => {
const dir = fixtureDir();
try {
// the live canary shape: legacy state carrying an ACTIVE focus of a
// previous (unknown-owner) incarnation
writeLegacy(dir, "mercer's previous focus");
for (const inc of ["inc-canary-1", "inc-canary-2"]) {
const p = resolveStatePath(dir, { incarnationId: inc });
assert.equal(loadState(p).status, "none", `${inc} inherits no active focus`);
}
// second quarantine slot when another legacy reappears (counter path)
writeLegacy(dir, "second legacy");
resolveStatePath(dir, { incarnationId: "inc-third" });
assert.equal(
readFileSync(join(dir, "goal-state.legacy.1.json"), "utf8").includes("second legacy"),
true,
"counter slot preserves the second legacy",
);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test("(b/F3) migration race: rename attempted with legacy present, failure -> fresh", () => {
const dir = fixtureDir();
try {
writeLegacy(dir, "legacy goal");
let renameCalls = 0;
const path = resolveStatePath(dir, {
incarnationId: "inc-raced",
io: {
// sequenced: fenced absent (first probe), legacy present (second)
existsSync: (p: string) => p === stateFilePath(dir),
renameSync: () => {
renameCalls += 1;
throw new Error("EEXIST: sibling quarantined it first");
},
},
});
assert.equal(path, fencedStateFilePath(dir, "inc-raced"));
assert.equal(renameCalls, 1, "the rename branch EXECUTED (not the fenced early-return)");
assert.equal(loadState(path).status, "none", "race loses to fresh");
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
test("(b/F2) traversal-shaped launcher claim is rejected at the identity source", async () => {
const { incarnationIdentity, resetIncarnationGlobal } = await import("../../mosaic-core/lib/incarnation.ts");
const prev = process.env.MOSAIC_LAUNCH_INCARNATION;
resetIncarnationGlobal();
try {
for (const hostile of ["../../etc/pwn", "..", "/abs", "a/b", ".hidden", "x".repeat(200)]) {
process.env.MOSAIC_LAUNCH_INCARNATION = hostile;
const id = incarnationIdentity(() => `minted-for-${hostile.slice(0, 3)}`);
assert.notEqual(id, hostile, `claim ${JSON.stringify(hostile)} must not become the identity`);
assert.equal(id.startsWith("minted-for-"), true, `falls through to mint for ${JSON.stringify(hostile)}`);
resetIncarnationGlobal();
}
} finally {
if (prev === undefined) delete process.env.MOSAIC_LAUNCH_INCARNATION;
else process.env.MOSAIC_LAUNCH_INCARNATION = prev;
resetIncarnationGlobal();
}
});
test("(b/F2) hostile claim keeps BOTH consumer paths inside the state root", async () => {
const { createJournal } = await import("../../mosaic-core/lib/journal.ts");
const { incarnationIdentity, resetIncarnationGlobal } = await import("../../mosaic-core/lib/incarnation.ts");
const prev = process.env.MOSAIC_LAUNCH_INCARNATION;
resetIncarnationGlobal();
try {
process.env.MOSAIC_LAUNCH_INCARNATION = "../../outside";
const id = incarnationIdentity(); // no-arg production form (F1)
assert.notEqual(id, "../../outside");
const j = createJournal({ incarnationId: id, stateHome: "/xdg/state", io: { mkdirSync() {}, appendFileSync() {} } });
assert.equal(j.path.startsWith("/xdg/state/mosaic-core/"), true, "journal contained");
assert.equal(j.path.includes(".."), false);
const dir = fixtureDir();
const gp = resolveStatePath(dir, { incarnationId: id });
assert.equal(gp.startsWith(dir), true, "goal path contained");
assert.equal(gp.includes(".."), false);
rmSync(dir, { recursive: true, force: true });
} finally {
if (prev === undefined) delete process.env.MOSAIC_LAUNCH_INCARNATION;
else process.env.MOSAIC_LAUNCH_INCARNATION = prev;
resetIncarnationGlobal();
}
});
test("(b) corrupt legacy never blocks fencing", () => {
const dir = fixtureDir();
try {
writeFileSync(stateFilePath(dir), "{corrupt");
const a = resolveStatePath(dir, { incarnationId: "inc-x" });
assert.equal(loadState(a).status, "none", "corrupt legacy -> fresh initial state");
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
// ---- (c) PI_SESSION_ID is never the incarnation --------------------------------
test("(c) fenced paths key on the process-launch identity, not PI_SESSION_ID", async () => {
const dir = fixtureDir();
try {
const prev = process.env.PI_SESSION_ID;
process.env.PI_SESSION_ID = "session-should-not-appear";
const { incarnationIdentity } = await import("../../mosaic-core/lib/incarnation.ts");
const id = incarnationIdentity(() => "minted-check");
process.env.PI_SESSION_ID = prev === undefined ? "" : prev;
if (prev === undefined) delete process.env.PI_SESSION_ID;
assert.notEqual(id, "session-should-not-appear");
const p = resolveStatePath(dir, { incarnationId: id });
assert.doesNotMatch(p, /session-should-not-appear/);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});
// ---- compatibility ---------------------------------------------------------------
test("goal_report compatibility: the state machine surface is unchanged", async () => {
// The pure state machine (setGoal/pauseGoal/resume/recordReport/validateReport)
// is untouched by fencing; the existing goal suite covers it. Here we pin
// the compatibility contract fencing must not break: a state saved and
// loaded through the fenced path round-trips exactly as the legacy path did.
const dir = fixtureDir();
try {
const p = resolveStatePath(dir, { incarnationId: "inc-compat" });
const s = setGoal(initialState(), "round trip");
saveState(s, p);
assert.deepEqual(loadState(p), s);
} finally {
rmSync(dir, { recursive: true, force: true });
}
});