mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-06-13 18:31:16 +08:00
test(persistence): verify Luker preserves unknown graph fields
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
"test:graph-snapshot-schema": "node tests/graph-snapshot-schema.mjs",
|
||||
"test:graph-snapshot-upgrade": "node tests/graph-snapshot-upgrade.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:vector-gate": "node tests/vector-gate.mjs",
|
||||
"test:hide-engine": "node tests/hide-engine.mjs",
|
||||
|
||||
99
tests/luker-snapshot-forward-compat.mjs
Normal file
99
tests/luker-snapshot-forward-compat.mjs
Normal 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");
|
||||
Reference in New Issue
Block a user