mirror of
https://github.com/openprose/prose.git
synced 2026-09-19 05:55:05 +08:00
9248d949e7
The open-prose skill ships as a plain folder, so everything under skills/open-prose/examples/ reaches every install. Each runtime example shipped a committed replay/ state-dir (around 480 binary world-model files), a generator, and a test suite wired to the workspace through a dist alias with no local package.json. That machinery is internal validation, not the template a user lifts: it cannot run from a copied folder, and it shipped generated artifacts nobody should copy. Keep the shipped examples to what a user actually adapts, and move the validation into the repo's test tree: - Move each example's tests, generator, and committed replay/ into tests/open-prose/examples/<name>/. The shipped example is now its .prose contracts under src/, a README, and a .gitignore. - Re-point the vitest include and the test scripts; the byte-deterministic offline coverage runs unchanged, just rooted under tests/. - Rewrite the example READMEs to point at the reactor CLI for producing and replaying a run, and drop the stale committed-fixture sections. - Update the examples-gate workflow description to match the new layout. No language, CLI, or runtime behavior changes, and the example contracts are untouched. Verified with the offline gate (REACTOR_OFFLINE=1): the conformance suite, the relocated example tests, and the eval harness all pass.
697 lines
26 KiB
TypeScript
697 lines
26 KiB
TypeScript
// THE DETERMINISTIC OFFLINE GATE for the basic-unit-suite example (zero
|
||
// model spend). It drives the REAL @openprose/reactor reconciler via the public
|
||
// `@openprose/reactor` + `/sdk` exports (no private internals) and asserts the
|
||
// full validity contract every example must satisfy:
|
||
//
|
||
// 1. Compiles to the frozen artifact set (topology valid, single entry gateway,
|
||
// acyclic; labels.json + beats.json present; flat receipts.json;
|
||
// world-models/<hexNodeId>/{published.json, versions/sha256_*.bin}).
|
||
// 2. Cold-start renders all nodes; an identical re-wake SKIPS all (a skip
|
||
// propagates nothing, wakes nothing).
|
||
// 3. cost.surprise_cause === wake.source on every committed receipt.
|
||
// 4. ATOMIC_FACET for facet-less producers; no "*" tokens anywhere.
|
||
// 5. Chain-verifies: verifyReceiptChain passes over the raw on-disk receipts.
|
||
// 6. Byte-deterministic: a second regeneration yields identical
|
||
// receipts.json / topology.json / labels.json.
|
||
//
|
||
// PLUS the per-mechanic acceptance assertions (U00–U12) this substrate exists to
|
||
// teach: facet subscription (U05), the diamond single-wake (U06), the function
|
||
// boundary (U07), the projection boundary (U08), self-continuity (U09), and
|
||
// failure containment (U10).
|
||
//
|
||
// The mounted-reconciler arc mirrors the EVALS.md "drive the reconciler yourself"
|
||
// snippet (the canonical doc-snippet-is-the-test pattern): mountDag → dag.ingest
|
||
// returning ReconcileResult[], assert dispositions, then createReplaySession
|
||
// .costRollup, then verifyReceiptChain.
|
||
//
|
||
// RUN (offline): REACTOR_OFFLINE=1 pnpm test:examples
|
||
// (or scope to this file: REACTOR_OFFLINE=1 npx vitest run \
|
||
// tests/open-prose/examples/basic-unit-suite). Resolution of the public
|
||
// @openprose/reactor subpaths is handled by the root vitest.config.ts alias.
|
||
|
||
import {
|
||
mkdtempSync,
|
||
readFileSync,
|
||
existsSync,
|
||
readdirSync,
|
||
rmSync,
|
||
} from "node:fs";
|
||
import { tmpdir } from "node:os";
|
||
import { join } from "node:path";
|
||
import { fileURLToPath } from "node:url";
|
||
import { describe, expect, it, afterAll } from "vitest";
|
||
|
||
import { createFileSystemStorageAdapter } from "@openprose/reactor";
|
||
import {
|
||
mountDag,
|
||
createFileSystemReceiptLedger,
|
||
createReplaySession,
|
||
verifyReceiptChain,
|
||
files,
|
||
textFile,
|
||
ATOMIC_FACET,
|
||
type RenderContext,
|
||
} from "@openprose/reactor";
|
||
import type {
|
||
ReconcilerTopology,
|
||
TopologyWorldModel,
|
||
} from "@openprose/reactor/internals";
|
||
|
||
import {
|
||
generateBasicUnitSuiteFixture,
|
||
GATEWAY,
|
||
COUNT_SUMMARY,
|
||
ALERT_STATE,
|
||
ALERT_PROJECTION,
|
||
RAW_EVENT_AUDITOR,
|
||
COUNT_TREND,
|
||
EXECUTIVE_SNAPSHOT,
|
||
COUNTS_FACET,
|
||
RAW_EVENTS_FACET,
|
||
} from "./generate";
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Shared generation: one fixture all the structural tests read.
|
||
// ---------------------------------------------------------------------------
|
||
|
||
const tmpDirs: string[] = [];
|
||
function freshStateDir(tag: string): string {
|
||
const dir = mkdtempSync(join(tmpdir(), `bus-${tag}-`));
|
||
tmpDirs.push(dir);
|
||
return dir;
|
||
}
|
||
afterAll(() => {
|
||
for (const d of tmpDirs) rmSync(d, { recursive: true, force: true });
|
||
});
|
||
|
||
function openSession(stateDir: string) {
|
||
const storage = createFileSystemStorageAdapter({ directory: stateDir });
|
||
const ledger = createFileSystemReceiptLedger({ storage });
|
||
return { session: createReplaySession({ ledger }), ledger };
|
||
}
|
||
|
||
function readTopology(stateDir: string): TopologyWorldModel {
|
||
return JSON.parse(
|
||
readFileSync(join(stateDir, "compile", "topology.json"), "utf8"),
|
||
) as TopologyWorldModel;
|
||
}
|
||
|
||
const hex = (s: string) => Buffer.from(s, "utf8").toString("hex");
|
||
|
||
// ===========================================================================
|
||
// (1) The frozen artifact set.
|
||
// ===========================================================================
|
||
|
||
describe("basic-unit-suite — (1) compiles to the frozen devtools state-dir", () => {
|
||
it("emits topology.json + labels.json + beats.json + a flat receipts.json", () => {
|
||
const stateDir = freshStateDir("artifacts");
|
||
const result = generateBasicUnitSuiteFixture({ stateDir });
|
||
|
||
expect(existsSync(join(stateDir, "receipts.json"))).toBe(true);
|
||
expect(existsSync(join(stateDir, "world-models"))).toBe(true);
|
||
expect(existsSync(join(stateDir, "compile", "topology.json"))).toBe(true);
|
||
expect(existsSync(join(stateDir, "compile", "labels.json"))).toBe(true);
|
||
expect(existsSync(join(stateDir, "beats.json"))).toBe(true);
|
||
// receipts.json is a FLAT root file, NOT a receipts/ subdir.
|
||
expect(existsSync(join(stateDir, "receipts"))).toBe(false);
|
||
|
||
// every node has a hex-encoded world-model dir with published.json + a version.
|
||
for (const node of [
|
||
GATEWAY,
|
||
COUNT_SUMMARY,
|
||
ALERT_STATE,
|
||
ALERT_PROJECTION,
|
||
RAW_EVENT_AUDITOR,
|
||
COUNT_TREND,
|
||
EXECUTIVE_SNAPSHOT,
|
||
]) {
|
||
const wmDir = join(stateDir, "world-models", hex(node));
|
||
expect(existsSync(join(wmDir, "published.json"))).toBe(true);
|
||
const versions = readdirSync(join(wmDir, "versions"));
|
||
expect(versions.some((f) => /^sha256_[0-9a-f]+\.bin$/.test(f))).toBe(
|
||
true,
|
||
);
|
||
}
|
||
|
||
expect(result.receiptsCount).toBeGreaterThan(0);
|
||
});
|
||
|
||
it("the topology is a valid TopologyWorldModel: acyclic, single entry gateway", () => {
|
||
const stateDir = freshStateDir("topo");
|
||
const result = generateBasicUnitSuiteFixture({ stateDir });
|
||
const topology = readTopology(stateDir);
|
||
|
||
expect(topology.acyclic).toBe(true);
|
||
expect(topology.entry_points).toEqual([GATEWAY]);
|
||
// 7 real nodes (the phantom ingress source is NOT a topology node).
|
||
expect(topology.nodes.length).toBe(7);
|
||
expect(result.nodeCount).toBe(7);
|
||
|
||
// every edge is a resolved subscription {subscriber, producer, facet}.
|
||
for (const e of topology.edges) {
|
||
expect(typeof e.subscriber).toBe("string");
|
||
expect(typeof e.producer).toBe("string");
|
||
expect(typeof e.facet).toBe("string");
|
||
}
|
||
// the diamond: Executive Snapshot has exactly three inbound edges.
|
||
expect(
|
||
topology.edges.filter((e) => e.subscriber === EXECUTIVE_SNAPSHOT).length,
|
||
).toBe(3);
|
||
});
|
||
|
||
it('(4) ATOMIC_FACET for facet-less producers; NO "*" tokens anywhere', () => {
|
||
const stateDir = freshStateDir("atomic");
|
||
generateBasicUnitSuiteFixture({ stateDir });
|
||
const topology = readTopology(stateDir);
|
||
|
||
// facet-less diamond edges subscribe to the exported ATOMIC_FACET constant.
|
||
const diamond = topology.edges.filter(
|
||
(e) => e.subscriber === EXECUTIVE_SNAPSHOT,
|
||
);
|
||
for (const e of diamond) expect(e.facet).toBe(ATOMIC_FACET);
|
||
|
||
// NO "*" wildcard token in any artifact on disk.
|
||
for (const rel of [
|
||
"compile/topology.json",
|
||
"compile/labels.json",
|
||
"receipts.json",
|
||
"beats.json",
|
||
]) {
|
||
const raw = readFileSync(join(stateDir, rel), "utf8");
|
||
expect(raw.includes('"*"')).toBe(false);
|
||
}
|
||
});
|
||
});
|
||
|
||
// ===========================================================================
|
||
// (3) cost.surprise_cause === wake.source on EVERY committed receipt.
|
||
// ===========================================================================
|
||
|
||
describe("basic-unit-suite — (3) surprise_cause equals the wake source", () => {
|
||
it("every committed receipt's cost.surprise_cause matches its wake.source", () => {
|
||
const stateDir = freshStateDir("surprise");
|
||
generateBasicUnitSuiteFixture({ stateDir });
|
||
const { session } = openSession(stateDir);
|
||
expect(session.receipts.length).toBeGreaterThan(0);
|
||
for (const r of session.receipts) {
|
||
expect(r.cost.surprise_cause).toBe(r.wake.source);
|
||
}
|
||
});
|
||
});
|
||
|
||
// ===========================================================================
|
||
// (5) Chain-verify over the RAW on-disk receipts.
|
||
// ===========================================================================
|
||
|
||
describe("basic-unit-suite — (5) the on-disk receipt chain verifies", () => {
|
||
it("verifyReceiptChain passes over every node-scoped slice of the committed ledger", () => {
|
||
const stateDir = freshStateDir("chain");
|
||
generateBasicUnitSuiteFixture({ stateDir });
|
||
const { ledger } = openSession(stateDir);
|
||
// verifyReceiptChain is NODE-SCOPED (each slice is a prev-linked chain for one
|
||
// node). Group the flat trail by node and verify each chain.
|
||
const all = ledger.all();
|
||
const byNode = new Map<string, (typeof all)[number][]>();
|
||
for (const r of all) {
|
||
const arr = byNode.get(r.node) ?? [];
|
||
arr.push(r);
|
||
byNode.set(r.node, arr);
|
||
}
|
||
expect(byNode.size).toBeGreaterThan(0);
|
||
for (const [node, chain] of byNode) {
|
||
const result = verifyReceiptChain(chain);
|
||
expect(result.ok, `chain for ${node} must verify`).toBe(true);
|
||
}
|
||
});
|
||
});
|
||
|
||
// ===========================================================================
|
||
// (6) Byte-determinism: two regenerations are identical.
|
||
// ===========================================================================
|
||
|
||
describe("basic-unit-suite — (6) byte-deterministic regeneration", () => {
|
||
it("receipts.json / topology.json / labels.json are byte-identical across two runs", () => {
|
||
const a = freshStateDir("det-a");
|
||
const b = freshStateDir("det-b");
|
||
generateBasicUnitSuiteFixture({ stateDir: a });
|
||
generateBasicUnitSuiteFixture({ stateDir: b });
|
||
|
||
for (const rel of [
|
||
"receipts.json",
|
||
"compile/topology.json",
|
||
"compile/labels.json",
|
||
"beats.json",
|
||
]) {
|
||
expect(readFileSync(join(a, rel), "utf8")).toBe(
|
||
readFileSync(join(b, rel), "utf8"),
|
||
);
|
||
}
|
||
});
|
||
|
||
it("a fresh regeneration matches the COMMITTED replay/ bytes (catches contract/SDK drift)", () => {
|
||
const fresh = freshStateDir("det-committed");
|
||
generateBasicUnitSuiteFixture({ stateDir: fresh });
|
||
const committed = fileURLToPath(new URL("./replay", import.meta.url));
|
||
for (const rel of [
|
||
"receipts.json",
|
||
"compile/topology.json",
|
||
"compile/labels.json",
|
||
"beats.json",
|
||
]) {
|
||
expect(
|
||
readFileSync(join(fresh, rel), "utf8"),
|
||
`committed replay/${rel} is stale — regenerate via generate.ts`,
|
||
).toBe(readFileSync(join(committed, rel), "utf8"));
|
||
}
|
||
});
|
||
});
|
||
|
||
// ===========================================================================
|
||
// (2) Cold-start renders all; an identical re-wake SKIPS all. Driven LIVE through
|
||
// the public mountDag front door (the EVALS.md doc-snippet pattern), independent
|
||
// of the committed fixture, so the memo-key gate is proved in-process.
|
||
// ===========================================================================
|
||
|
||
describe("basic-unit-suite — (2) cold-start renders all; a quiet re-wake skips all", () => {
|
||
it("the memo key gates render; a skip propagates nothing; a contract edit re-renders", () => {
|
||
const dir = freshStateDir("memo");
|
||
const storage = createFileSystemStorageAdapter({ directory: dir });
|
||
const ledger = createFileSystemReceiptLedger({ storage });
|
||
|
||
// The EVALS.md fake render: deterministic, surprise_cause read off the context.
|
||
const render = (text: string) => (ctx: RenderContext) => ({
|
||
world_model: files({ "out.txt": textFile(text) }),
|
||
cost: {
|
||
provider: "none",
|
||
model: "fake",
|
||
tokens: { fresh: 1, reused: 0 },
|
||
surprise_cause: ctx.wake.source,
|
||
},
|
||
});
|
||
|
||
const mkTopo = (sourceFp: string): ReconcilerTopology => ({
|
||
topology: {
|
||
nodes: [
|
||
{
|
||
node: "source",
|
||
contract_fingerprint: sourceFp,
|
||
wake_source: "external",
|
||
},
|
||
{
|
||
node: "digest",
|
||
contract_fingerprint: "fp-digest",
|
||
wake_source: "input",
|
||
},
|
||
],
|
||
edges: [
|
||
{ subscriber: "digest", producer: "source", facet: ATOMIC_FACET },
|
||
],
|
||
entry_points: ["source"],
|
||
acyclic: true,
|
||
},
|
||
contract_fingerprints: { source: sourceFp, digest: "fp-digest" },
|
||
});
|
||
|
||
const dag = mountDag({
|
||
topology: mkTopo("fp-source"),
|
||
mounts: {
|
||
source: { render: render("v1") },
|
||
digest: { render: render("digest of v1") },
|
||
},
|
||
ledger,
|
||
});
|
||
|
||
// cold-start: BOTH render.
|
||
const first = dag.ingest("source");
|
||
expect(first.map((r) => `${r.node}:${r.disposition}`).sort()).toEqual([
|
||
"digest:rendered",
|
||
"source:rendered",
|
||
]);
|
||
|
||
// identical re-wake: source SKIPS and digest is NOT even woken (a skip
|
||
// propagates nothing).
|
||
const second = dag.ingest("source");
|
||
expect(second.map((r) => `${r.node}:${r.disposition}`)).toEqual([
|
||
"source:skipped",
|
||
]);
|
||
expect(createReplaySession({ ledger }).costRollup.total.fresh).toBe(2);
|
||
|
||
// a contract_fingerprint edit MOVES the memo key → render + propagate.
|
||
const dag2 = mountDag({
|
||
topology: mkTopo("fp-source-v2"),
|
||
mounts: {
|
||
source: { render: render("v2") },
|
||
digest: { render: render("digest of v2") },
|
||
},
|
||
ledger,
|
||
});
|
||
const third = dag2.ingest("source");
|
||
expect(third.map((r) => `${r.node}:${r.disposition}`).sort()).toEqual([
|
||
"digest:rendered",
|
||
"source:rendered",
|
||
]);
|
||
// fresh moved 2 → 4 (it does NOT move on a quiet wake, DOES on a contract edit).
|
||
expect(createReplaySession({ ledger }).costRollup.total.fresh).toBe(4);
|
||
});
|
||
});
|
||
|
||
// ===========================================================================
|
||
// The cost rollup over the COMMITTED fixture: byCause partitions total, and the
|
||
// quiet memo-skip beat (U03) carried ZERO fresh.
|
||
// ===========================================================================
|
||
|
||
describe("basic-unit-suite — the cost rollup is the lesson", () => {
|
||
it("byCause partitions the total exactly; skips carry zero fresh", () => {
|
||
const stateDir = freshStateDir("rollup");
|
||
generateBasicUnitSuiteFixture({ stateDir });
|
||
const { session } = openSession(stateDir);
|
||
const rollup = session.costRollup;
|
||
|
||
const byCauseFresh =
|
||
rollup.byCause.input.fresh +
|
||
rollup.byCause.self.fresh +
|
||
rollup.byCause.external.fresh;
|
||
expect(byCauseFresh).toBe(rollup.total.fresh);
|
||
const byCauseReceipts =
|
||
rollup.byCause.input.receipts +
|
||
rollup.byCause.self.receipts +
|
||
rollup.byCause.external.receipts;
|
||
expect(byCauseReceipts).toBe(rollup.total.receipts);
|
||
|
||
// U03: at least one memo-skip exists and every skip carries zero fresh.
|
||
const skips = session.receipts.filter((r) => r.status === "skipped");
|
||
expect(skips.length).toBeGreaterThan(0);
|
||
for (const s of skips) expect(s.cost.tokens.fresh).toBe(0);
|
||
});
|
||
});
|
||
|
||
// ===========================================================================
|
||
// The per-mechanic acceptance assertions (U05–U10) this substrate teaches.
|
||
// ===========================================================================
|
||
|
||
describe("basic-unit-suite — the micro-mechanics (U05–U10)", () => {
|
||
function gen() {
|
||
const stateDir = freshStateDir("mech");
|
||
generateBasicUnitSuiteFixture({ stateDir });
|
||
return openSession(stateDir).session;
|
||
}
|
||
|
||
it("U05 facet subscription: a raw_events-only move lights the auditor, not the summary", () => {
|
||
const session = gen();
|
||
// Find a gateway rendered frame that moved raw_events but NOT counts.
|
||
let found = false;
|
||
for (let i = 0; i < session.receipts.length; i++) {
|
||
const r = session.receipts[i]!;
|
||
if (r.node !== GATEWAY || r.status !== "rendered") continue;
|
||
const moved = session.movedFacetsByIndex[i]!;
|
||
if (moved.has(RAW_EVENTS_FACET) && !moved.has(COUNTS_FACET)) {
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
expect(found).toBe(true);
|
||
});
|
||
|
||
it("U06 diamond single-wake: Executive Snapshot never renders twice in one fixpoint window", () => {
|
||
const session = gen();
|
||
// The snapshot is the diamond apex; across the whole trail it never renders
|
||
// more than once back-to-back for a single driving event (one render per
|
||
// input-fingerprint tuple, not per inbound edge). Assert no two consecutive
|
||
// snapshot receipts are both `rendered` with no other node between them.
|
||
const snapIdx = session.receipts
|
||
.map((r, i) => ({ r, i }))
|
||
.filter(
|
||
({ r }) => r.node === EXECUTIVE_SNAPSHOT && r.status === "rendered",
|
||
)
|
||
.map(({ i }) => i);
|
||
for (let k = 1; k < snapIdx.length; k++) {
|
||
// there must be at least one non-snapshot receipt between two snapshot renders.
|
||
expect(snapIdx[k]! - snapIdx[k - 1]!).toBeGreaterThan(1);
|
||
}
|
||
// and the snapshot did render at least once (the diamond fired).
|
||
expect(snapIdx.length).toBeGreaterThanOrEqual(1);
|
||
});
|
||
|
||
it("U07 function boundary: Format Alert Copy is NOT a node and nothing subscribes to it", () => {
|
||
const stateDir = freshStateDir("fn");
|
||
generateBasicUnitSuiteFixture({ stateDir });
|
||
const topology = readTopology(stateDir);
|
||
const ids = new Set(topology.nodes.map((n) => n.node));
|
||
expect(ids.has("function.format-alert-copy")).toBe(false);
|
||
expect([...ids].some((n) => n.includes("format-alert-copy"))).toBe(false);
|
||
// no edge targets or sources the function.
|
||
for (const e of topology.edges) {
|
||
expect(e.producer.includes("format-alert-copy")).toBe(false);
|
||
expect(e.subscriber.includes("format-alert-copy")).toBe(false);
|
||
}
|
||
// the projection truth records that the helper ran inside its render.
|
||
const { session } = openSession(stateDir);
|
||
const projRender = session.receipts.find(
|
||
(r) => r.node === ALERT_PROJECTION && r.status === "rendered",
|
||
);
|
||
expect(projRender).toBeTruthy();
|
||
});
|
||
|
||
it("U08 on disk: the committed trajectory contains a projection cosmetic re-render that moves @atomic but NOT structured", () => {
|
||
// The headline U08 frame must be VISIBLE in the committed replay/ trajectory a
|
||
// judge inspects, not only proved in-process below. The generator drives a
|
||
// cosmetic CONTRACT revision (a bumped projection contract_fingerprint + a
|
||
// bumped wording nonce): the projection genuinely RE-RENDERS, so its `@atomic`
|
||
// truth moves, but its `structured` facet token is re-derived from the same
|
||
// structured_summary and stays byte-identical. The projection has no subscribers,
|
||
// so the move wakes nothing downstream.
|
||
const stateDir = freshStateDir("u08-disk");
|
||
generateBasicUnitSuiteFixture({ stateDir });
|
||
const { session } = openSession(stateDir);
|
||
|
||
const projRenders = session.receipts
|
||
.map((r, i) => ({ r, i }))
|
||
.filter(
|
||
({ r }) => r.node === ALERT_PROJECTION && r.status === "rendered",
|
||
);
|
||
// at least two projection renders: the cascade render AND the cosmetic re-render.
|
||
expect(projRenders.length).toBeGreaterThanOrEqual(2);
|
||
|
||
// find a CONSECUTIVE pair of projection renders where @atomic moved but the
|
||
// structured facet did NOT (the cosmetic re-render frame).
|
||
let cosmeticFound = false;
|
||
for (let k = 1; k < projRenders.length; k++) {
|
||
const prev = projRenders[k - 1]!.r;
|
||
const cur = projRenders[k]!.r;
|
||
const atomicMoved =
|
||
prev.fingerprints[ATOMIC_FACET] !== cur.fingerprints[ATOMIC_FACET];
|
||
const structuredFlat =
|
||
prev.fingerprints["structured"] !== undefined &&
|
||
prev.fingerprints["structured"] === cur.fingerprints["structured"];
|
||
if (atomicMoved && structuredFlat) {
|
||
cosmeticFound = true;
|
||
// the cosmetic re-render carries a REVISED contract_fingerprint (the
|
||
// legitimate memo-key move): a contract revision, not an input move.
|
||
expect(cur.contract_fingerprint).not.toBe(prev.contract_fingerprint);
|
||
break;
|
||
}
|
||
}
|
||
expect(
|
||
cosmeticFound,
|
||
"a cosmetic projection re-render frame must exist on disk",
|
||
).toBe(true);
|
||
|
||
// and nothing downstream of the projection woke from it: the projection has NO
|
||
// subscribers, so no edge in the committed topology targets it as a producer.
|
||
const topology = readTopology(stateDir);
|
||
expect(topology.edges.some((e) => e.producer === ALERT_PROJECTION)).toBe(
|
||
false,
|
||
);
|
||
});
|
||
|
||
it("U08 projection boundary: a cosmetic re-render moves the atomic truth but NOT the structured facet → no downstream wake", () => {
|
||
// Proved against the LIVE reconciler in-process. A 3-node chain:
|
||
// alert-state ── style ─▶ projection ── structured ─▶ downstream
|
||
// The projection subscribes to alert-state's COSMETIC `style` facet and exposes
|
||
// a MATERIAL `structured` facet. When alert-state re-issues a cosmetic style
|
||
// change (the legitimate memo-key move: a bumped contract_fingerprint), the
|
||
// projection wakes and re-renders new markdown, but its `structured` facet
|
||
// does NOT move, so the downstream subscriber to `structured` is NEVER woken.
|
||
const dir = freshStateDir("u08");
|
||
const storage = createFileSystemStorageAdapter({ directory: dir });
|
||
const ledger = createFileSystemReceiptLedger({ storage });
|
||
|
||
const sha = (s: string) =>
|
||
`sha256:${require("node:crypto").createHash("sha256").update(s).digest("hex")}`;
|
||
|
||
// alert-state: a fixed material `status` + a cosmetic `style` token bumped by
|
||
// the test. Its canonicalizer exposes both as independent facets.
|
||
let style = "v1";
|
||
const alertRender = (ctx: RenderContext) => ({
|
||
world_model: files({
|
||
"truth.json": textFile(JSON.stringify({ status: "quiet", style })),
|
||
}),
|
||
cost: {
|
||
provider: "none",
|
||
model: "fake",
|
||
tokens: { fresh: 1, reused: 0 },
|
||
surprise_cause: ctx.wake.source,
|
||
},
|
||
});
|
||
const alertCanon = (fm: Record<string, Uint8Array>) => {
|
||
const t = JSON.parse(Buffer.from(fm["truth.json"]!).toString("utf8")) as {
|
||
status: string;
|
||
style: string;
|
||
};
|
||
return {
|
||
[ATOMIC_FACET]: sha(JSON.stringify(t)),
|
||
status: sha(t.status),
|
||
style: sha(t.style),
|
||
};
|
||
};
|
||
|
||
// projection: reads alert-state's style into cosmetic markdown; the structured
|
||
// summary is FIXED (status quiet). Its canonicalizer exposes `structured` over
|
||
// ONLY the structured summary, so cosmetic markdown churn does not move it.
|
||
const projectionRender = (ctx: RenderContext) => ({
|
||
world_model: files({
|
||
"structured.json": textFile(JSON.stringify({ status: "quiet" })),
|
||
"out.md": textFile(`# Alert\n\nwording ${style}`),
|
||
}),
|
||
cost: {
|
||
provider: "none",
|
||
model: "fake",
|
||
tokens: { fresh: 1, reused: 0 },
|
||
surprise_cause: ctx.wake.source,
|
||
},
|
||
});
|
||
const projectionCanon = (fm: Record<string, Uint8Array>) => {
|
||
const atomic = Object.keys(fm)
|
||
.sort()
|
||
.map((k) => `${k}:${Buffer.from(fm[k]!).toString("hex")}`)
|
||
.join("|");
|
||
return {
|
||
[ATOMIC_FACET]: sha(atomic),
|
||
structured: sha(Buffer.from(fm["structured.json"]!).toString("utf8")),
|
||
};
|
||
};
|
||
|
||
const downstreamRender = (ctx: RenderContext) => ({
|
||
world_model: files({ "out.txt": textFile("derived") }),
|
||
cost: {
|
||
provider: "none",
|
||
model: "fake",
|
||
tokens: { fresh: 1, reused: 0 },
|
||
surprise_cause: ctx.wake.source,
|
||
},
|
||
});
|
||
|
||
const store = undefined;
|
||
const mkTopo = (alertFp: string): ReconcilerTopology => ({
|
||
topology: {
|
||
nodes: [
|
||
{
|
||
node: "alert",
|
||
contract_fingerprint: alertFp,
|
||
wake_source: "external",
|
||
},
|
||
{
|
||
node: "projection",
|
||
contract_fingerprint: "fp-proj",
|
||
wake_source: "input",
|
||
},
|
||
{
|
||
node: "downstream",
|
||
contract_fingerprint: "fp-down",
|
||
wake_source: "input",
|
||
},
|
||
],
|
||
edges: [
|
||
{ subscriber: "projection", producer: "alert", facet: "style" },
|
||
{
|
||
subscriber: "downstream",
|
||
producer: "projection",
|
||
facet: "structured",
|
||
},
|
||
],
|
||
entry_points: ["alert"],
|
||
acyclic: true,
|
||
},
|
||
contract_fingerprints: {
|
||
alert: alertFp,
|
||
projection: "fp-proj",
|
||
downstream: "fp-down",
|
||
},
|
||
});
|
||
|
||
const sharedStore = store;
|
||
const fsStore =
|
||
new (require("@openprose/reactor/adapters").FileSystemWorldModelStore)({
|
||
directory: join(dir, "wm"),
|
||
});
|
||
void sharedStore;
|
||
const mount = (alertFp: string) =>
|
||
mountDag({
|
||
topology: mkTopo(alertFp),
|
||
mounts: {
|
||
alert: { render: alertRender, canonicalizer: alertCanon },
|
||
projection: {
|
||
render: projectionRender,
|
||
canonicalizer: projectionCanon,
|
||
},
|
||
downstream: { render: downstreamRender },
|
||
},
|
||
ledger,
|
||
store: fsStore,
|
||
});
|
||
|
||
// cold start: all three render.
|
||
const first = mount("fp-alert-v1").ingest("alert");
|
||
expect(first.map((r) => `${r.node}:${r.disposition}`).sort()).toEqual([
|
||
"alert:rendered",
|
||
"downstream:rendered",
|
||
"projection:rendered",
|
||
]);
|
||
|
||
// cosmetic re-issue: bump the style AND the alert contract_fp (the legitimate
|
||
// memo-key move). alert re-renders; its `style` facet moves but `status` does
|
||
// not. The projection wakes (it subscribes `style`), re-renders new markdown,
|
||
// but its `structured` facet stays flat → downstream is NOT woken.
|
||
style = "v2";
|
||
const second = mount("fp-alert-v2").ingest("alert");
|
||
const d = second.map((r) => `${r.node}:${r.disposition}`);
|
||
expect(d).toContain("alert:rendered");
|
||
expect(d).toContain("projection:rendered");
|
||
expect(d).not.toContain("downstream:rendered");
|
||
});
|
||
|
||
it("U09 self-continuity: Count Trend self-ticks and a no-op recheck propagates nothing", () => {
|
||
const session = gen();
|
||
const selfTrend = session.receipts.filter(
|
||
(r) => r.node === COUNT_TREND && r.wake.source === "self",
|
||
);
|
||
expect(selfTrend.length).toBeGreaterThanOrEqual(1);
|
||
// a no-op self recheck is a `skipped` receipt carrying zero fresh.
|
||
expect(
|
||
selfTrend.some(
|
||
(r) => r.status === "skipped" && r.cost.tokens.fresh === 0,
|
||
),
|
||
).toBe(true);
|
||
});
|
||
|
||
it("U10 failure containment: a failed receipt carries zero fresh and corrupts no prior truth", () => {
|
||
const stateDir = freshStateDir("fail");
|
||
generateBasicUnitSuiteFixture({ stateDir });
|
||
const { session } = openSession(stateDir);
|
||
|
||
const failed = session.receipts.filter((r) => r.status === "failed");
|
||
expect(failed.length).toBeGreaterThanOrEqual(1);
|
||
expect(failed.some((r) => r.node === ALERT_STATE)).toBe(true);
|
||
for (const f of failed) expect(f.cost.tokens.fresh).toBe(0);
|
||
|
||
// the prior Alert State truth is still readable (a published version exists);
|
||
// the failure did not corrupt it.
|
||
const storage = createFileSystemStorageAdapter({ directory: stateDir });
|
||
void storage;
|
||
const lastGoodAlert = session.receipts
|
||
.filter((r) => r.node === ALERT_STATE && r.status === "rendered")
|
||
.at(-1);
|
||
expect(lastGoodAlert).toBeTruthy();
|
||
});
|
||
});
|