test(persistence): verify Luker preserves unknown graph fields

This commit is contained in:
youzini
2026-05-30 18:13:16 +00:00
parent 25ec23a381
commit bf0d79c41b
2 changed files with 100 additions and 0 deletions

View File

@@ -15,6 +15,7 @@
"test:graph-snapshot-schema": "node tests/graph-snapshot-schema.mjs", "test:graph-snapshot-schema": "node tests/graph-snapshot-schema.mjs",
"test:graph-snapshot-upgrade": "node tests/graph-snapshot-upgrade.mjs", "test:graph-snapshot-upgrade": "node tests/graph-snapshot-upgrade.mjs",
"test:snapshot-forward-compat": "node tests/snapshot-forward-compat.mjs", "test:snapshot-forward-compat": "node tests/snapshot-forward-compat.mjs",
"test:luker-snapshot-forward-compat": "node tests/luker-snapshot-forward-compat.mjs",
"test:reroll-transaction-boundary": "node tests/reroll-transaction-boundary.mjs", "test:reroll-transaction-boundary": "node tests/reroll-transaction-boundary.mjs",
"test:vector-gate": "node tests/vector-gate.mjs", "test:vector-gate": "node tests/vector-gate.mjs",
"test:hide-engine": "node tests/hide-engine.mjs", "test:hide-engine": "node tests/hide-engine.mjs",

View File

@@ -0,0 +1,99 @@
// 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");