Files
ST-Bionic-Memory-Ecology/tests/luker-snapshot-forward-compat.mjs

100 lines
3.6 KiB
JavaScript

// ST-BME restrained rebirth — Phase 3c Luker sidecar forward-compat tests.
//
// Luker stores the graph as a serialized graph blob inside the checkpoint, not as
// the durable snapshot shape. This test proves the IMPORTANT property: unknown
// future fields on graph nodes/edges survive a Luker checkpoint round-trip
// (build -> normalize -> deserialize). It also documents the intentional boundary
// that the sidecar ENVELOPE metadata (manifest stats, checkpoint meta) is
// normalized/whitelisted on purpose — those are rebuildable operational metrics,
// not graph data.
import assert from "node:assert/strict";
import {
buildLukerGraphCheckpointV2,
normalizeLukerGraphCheckpointV2,
} from "../graph/graph-persistence.js";
import {
addEdge,
addNode,
createEdge,
createNode,
createEmptyGraph,
deserializeGraph,
} from "../graph/graph.js";
// Build a graph and inject unknown FUTURE fields onto a node and an edge.
const graph = createEmptyGraph();
const nodeA = addNode(
graph,
createNode({ type: "char", fields: { name: "恬恬" }, seq: 1 }),
);
const nodeB = addNode(
graph,
createNode({ type: "event", fields: { title: "相遇" }, seq: 2 }),
);
// Simulate a future writer adding new fields the current build does not know.
nodeA.futureNodeField = { trait: "playful", version: 99 };
const edge = addEdge(
graph,
createEdge({ fromId: nodeA.id, toId: nodeB.id, relation: "participatesIn" }),
);
edge.futureEdgeField = "edge-keep-me";
// 1. Checkpoint round-trip preserves unknown graph record fields.
const checkpoint = buildLukerGraphCheckpointV2(graph, {
revision: 7,
chatId: "chat-luker-fc",
integrity: "integrity-luker",
reason: "test",
});
assert.ok(checkpoint, "checkpoint built");
assert.ok(checkpoint.serializedGraph, "checkpoint carries serialized graph");
const restored = deserializeGraph(checkpoint.serializedGraph);
const restoredNodeA = restored.nodes.find((n) => n.id === nodeA.id);
const restoredEdge = restored.edges.find((e) => e.fromId === nodeA.id);
assert.ok(restoredNodeA, "node survived Luker checkpoint round-trip");
assert.deepEqual(
restoredNodeA.futureNodeField,
{ trait: "playful", version: 99 },
"unknown future node field preserved through Luker checkpoint",
);
assert.ok(restoredEdge, "edge survived Luker checkpoint round-trip");
assert.equal(
restoredEdge.futureEdgeField,
"edge-keep-me",
"unknown future edge field preserved through Luker checkpoint",
);
console.log(" ✓ Luker checkpoint preserves unknown future graph record fields");
// 2. normalize re-parse of a checkpoint payload preserves the serialized graph
// verbatim (so unknown fields inside it are never touched by the envelope layer).
const reNormalized = normalizeLukerGraphCheckpointV2({
...checkpoint,
// A future writer may add an unknown ENVELOPE field. It is intentionally
// dropped (rebuildable operational metadata), but graph data must be intact.
futureEnvelopeField: "envelope-meta-can-be-dropped",
});
assert.equal(
reNormalized.serializedGraph,
checkpoint.serializedGraph,
"serialized graph blob is preserved verbatim by the envelope normalizer",
);
const reRestoredNode = deserializeGraph(reNormalized.serializedGraph).nodes.find(
(n) => n.id === nodeA.id,
);
assert.deepEqual(
reRestoredNode.futureNodeField,
{ trait: "playful", version: 99 },
"graph record unknown fields still intact after envelope re-normalize",
);
assert.equal(
"futureEnvelopeField" in reNormalized,
false,
"envelope-level unknown metadata is intentionally normalized away (rebuildable)",
);
console.log(" ✓ Luker envelope normalize keeps graph data intact while normalizing metadata");
console.log("luker-snapshot-forward-compat tests passed");