fix(snapshot): export only target-bound observations

This commit is contained in:
morluto committed 2026-10-27 07:55:16 +08:00
1 parent 7fa54e2d62
commit 2ad599dace
5 files changed
+186 -9

No files matched your search

@@ -8,7 +8,10 @@ import {
ANALYSIS_SNAPSHOT_TARGET,
} from "../../domain/analysisSnapshot.fixture.js";
import { createEvidence } from "../../domain/evidence.js";
import { createEvidenceBundle } from "../../domain/evidenceBundle.js";
import {
createEvidenceBundle,
evidenceBundleForTarget,
} from "../../domain/evidenceBundle.js";
import { ok } from "../../domain/result.js";
import { createAnalysisExecution } from "../AnalysisProvider.js";
import {
@@ -291,6 +294,90 @@ it.each(["provider", "workflow"] as const)(
},
);
it.each(["provider", "workflow"] as const)(
"exports target-scoped %s rows without changing retained observations",
(kind) => {
const cache = new AnalysisSnapshotCache();
const ledger = new EvidenceLedger();
const target = ANALYSIS_SNAPSHOT_TARGET;
const profile = ANALYSIS_SNAPSHOT_PROFILE;
const operation = kind === "provider" ? "procedure_info" : "trace_feature";
const provider =
kind === "provider" ? ANALYSIS_SNAPSHOT_PROVIDER : REA_WORKFLOW_PROVIDER;
const analysisProfile =
kind === "provider"
? profile
: workflowAnalysisProfile(profile, operation);
const child = { ...target, path: "/tmp/child", sha256: "b".repeat(64) };
for (const [index, subject] of [target, null, child].entries()) {
const parameters = { address: `0x${index}000` };
const result = { observation: index };
const execution = createAnalysisExecution(result, provider, {
subject,
analysisProfile,
});
const input = {
target,
profile,
operation,
parameters,
execution: { ...execution, subject, analysisProfile },
};
if (kind === "provider")
cache.record({ ...input, operation: "procedure_info" });
else cache.recordWorkflow(input);
expect(
ledger.record(
createEvidence(subject ?? undefined, provider, {
operation,
parameters,
result,
analysisProfile,
confidence: kind === "provider" ? "observed" : "derived",
}),
).ok,
).toBe(true);
}
const before =
kind === "provider" ? cache.entries() : cache.workflowEntries();
expect(before).toHaveLength(3);
const evidence = ledger.export();
const exported = cache.export(
target,
profile,
evidenceBundleForTarget(evidence, target.sha256),
);
if (!exported.ok) throw exported.error;
const snapshot = parseAnalysisSnapshot(
JSON.parse(serializeAnalysisSnapshot(exported.value)),
);
const saved =
kind === "provider" ? snapshot.entries : snapshot.workflow_entries;
expect(saved).toHaveLength(1);
expect(saved[0]?.execution.subject?.sha256).toBe(target.sha256);
expect(snapshot.evidence_bundle.records).toHaveLength(1);
expect(
kind === "provider" ? cache.entries() : cache.workflowEntries(),
).toEqual(before);
expect(ledger.export()).toEqual(evidence);
const restored = new AnalysisSnapshotCache();
expect(restored.stage(snapshot)).toBe(1);
expect(
kind === "provider" ? restored.entries() : restored.workflowEntries(),
).toEqual(saved);
if (kind === "provider") {
expect(
cache.lookup(target, profile, "procedure_info", { address: "0x1000" })
?.subject,
).toBeNull();
expect(
cache.lookup(target, profile, "procedure_info", { address: "0x2000" })
?.subject?.sha256,
).toBe(child.sha256);
}
},
);
it("counts newly retained direct and workflow bindings when staging", () => {
const source = new AnalysisSnapshotCache();
const target = ANALYSIS_SNAPSHOT_TARGET;
@@ -291,13 +291,19 @@ export class AnalysisSnapshotCache {
try {
const snapshotTargetIdentity = snapshotTarget(target);
const binding = snapshotBinding(profile);
const entries = [...this.#entries.values()].sort((left, right) =>
compareUnicodeCodePoints(left.query_id, right.query_id),
);
const workflows = [...this.#workflowEntries.values()].sort(
(left, right) =>
// In-memory replay can preserve unbound and child-artifact observations.
// A persisted target snapshot can authenticate only rows whose Evidence
// has that target subject; the record owner applies the same scope to its bundle.
const entries = [...this.#entries.values()]
.filter((entry) => entry.execution.subject?.sha256 === target.sha256)
.sort((left, right) =>
compareUnicodeCodePoints(left.query_id, right.query_id),
);
);
const workflows = [...this.#workflowEntries.values()]
.filter((entry) => entry.execution.subject?.sha256 === target.sha256)
.sort((left, right) =>
compareUnicodeCodePoints(left.query_id, right.query_id),
);
// Parsing owns the returned JSON and metadata; pre-cloning the same
// payloads here only adds another full materialization.
return ok(
@@ -73,6 +73,81 @@ it("preserves an explicitly unbound provider observation", async () => {
}
});
it("keeps unbound observations out of persisted target snapshots", async () => {
const { session, deep } = openSession();
const directory = await createTestTempDirectory("rea-unbound-snapshot-");
const path = join(directory, "snapshot.json");
const parameters = { document: "0", address: "0x1000" };
try {
expect((await session.open(process.execPath)).ok).toBe(true);
const bound = await session.execute("address_name", parameters);
expect(bound.ok).toBe(true);
const child = deep.created[0];
if (child === undefined || !bound.ok)
throw new Error("Expected bound read");
child.execute = () =>
Promise.resolve(
ok(
createAnalysisExecution("unbound observation", deepIdentity, {
subject: null,
}),
),
);
const execution = await session.execute("address_name", {
...parameters,
address: "0x2000",
});
expect(execution.ok).toBe(true);
if (!execution.ok) return;
expect(execution.value.subject).toBeNull();
expect(session.exportEvidenceBundle().records).toEqual(
expect.arrayContaining([
expect.objectContaining({
subject: null,
normalized_result: "unbound observation",
}),
]),
);
const snapshot = session.exportAnalysisSnapshot();
expect(snapshot.ok, snapshot.ok ? undefined : snapshot.error.message).toBe(
true,
);
if (!snapshot.ok) return;
expect(snapshot.value.entries).toHaveLength(1);
expect(snapshot.value.entries[0]?.parameters).toEqual(parameters);
const unboundReplay = await session.execute("address_name", {
...parameters,
address: "0x2000",
});
expect(unboundReplay.ok).toBe(true);
if (unboundReplay.ok) {
expect(unboundReplay.value.subject).toBeNull();
expect(unboundReplay.value.evidenceId).toBe(execution.value.evidenceId);
}
expect(
(await session.writeAnalysisSnapshot(snapshot.value, path, false)).ok,
).toBe(true);
expect((await session.close()).ok).toBe(true);
expect(
(
await session.open(process.execPath, {
loadSnapshot: () => session.readAnalysisSnapshot(path),
})
).ok,
).toBe(true);
const replay = await session.execute("address_name", parameters);
expect(replay.ok).toBe(true);
if (replay.ok) {
expect(replay.value.evidenceId).toBe(bound.value.evidenceId);
expect(replay.value.limitations).toContain(
"Loaded from a local REA analysis snapshot; this call did not re-run the provider.",
);
}
} finally {
await session.close();
}
});
describe("BinarySession open liveness", () => {
it("retires a locally blocked owner even when its native health is idle", async () => {
const { session, deep } = openSession();