fix(snapshot): decline ambiguous exact evidence replay (#7037)

This commit is contained in:
Wenyu Chiou
2026-10-24 18:35:27 -04:00
committed by GitHub
parent 238914974d
commit 07eb044f77
3 changed files with 169 additions and 14 deletions
+45
View File
@@ -422,6 +422,51 @@ describe("analysis snapshot canonical ordering and scope", () => {
});
});
it("does not select arbitrary provenance when multiple authenticated records match one replay entry", () => {
const { current, snapshot } = snapshotWithHistoricalEvidence();
const source = snapshot.evidence_bundle.records.find(
(record) => record.operation === "unprofiled_query",
)!;
const alternative = createEvidence(
ANALYSIS_SNAPSHOT_TARGET,
ANALYSIS_SNAPSHOT_PROVIDER,
{
operation: current.operation,
parameters: current.parameters,
result: current.normalized_result,
analysisProfile: ANALYSIS_SNAPSHOT_PROFILE,
evidenceLinks: [source.evidence_id],
},
);
expect(alternative.evidence_id).not.toBe(current.evidence_id);
const ambiguous = parseAnalysisSnapshot({
...snapshot,
evidence_bundle: createEvidenceBundle([
...snapshot.evidence_bundle.records,
alternative,
]),
});
expect(ambiguous.entries).toEqual(snapshot.entries);
const reversedInsertion = parseAnalysisSnapshot({
...ambiguous,
evidence_bundle: createEvidenceBundle(
[...ambiguous.evidence_bundle.records].reverse(),
),
});
expect(reversedInsertion).toEqual(ambiguous);
for (const candidate of [ambiguous, reversedInsertion])
expect(
snapshotEvidenceForQuery(candidate, {
target: ANALYSIS_SNAPSHOT_TARGET,
bindingProfile: ANALYSIS_SNAPSHOT_PROFILE,
operation: current.operation,
parameters: current.parameters,
provider: ANALYSIS_SNAPSHOT_PROVIDER,
evidenceProfile: ANALYSIS_SNAPSHOT_PROFILE,
}),
).toBeUndefined();
});
describe("analysis snapshot Evidence binding", () => {
it("finds only Evidence committed to the exact binding and profile", () => {
const { current, snapshot } = snapshotWithHistoricalEvidence();
+25 -14
View File
@@ -502,20 +502,31 @@ export const snapshotEvidenceForQuery = (
(candidate) => candidate.query_id === queryId,
);
if (entry === undefined && workflowEntry === undefined) return undefined;
const evidence = snapshot.evidence_bundle.records.find(
(record) =>
record.subject?.digest.sha256 === target.sha256 &&
record.operation === operation &&
record.provider.id === provider.id &&
record.provider.name === provider.name &&
record.provider.version === provider.version &&
record.analysis_profile !== null &&
analysisProfilesEqual(record.analysis_profile, evidenceProfile) &&
canonicalJson(record.parameters) === encodedParameters &&
((entry !== undefined && evidenceMatchesEntry(record, entry, snapshot)) ||
(workflowEntry !== undefined &&
evidenceMatchesWorkflowEntry(record, workflowEntry, snapshot))),
);
let evidence: Evidence | undefined;
for (const record of snapshot.evidence_bundle.records) {
if (
!(
record.subject?.digest.sha256 === target.sha256 &&
record.operation === operation &&
record.provider.id === provider.id &&
record.provider.name === provider.name &&
record.provider.version === provider.version &&
record.analysis_profile !== null &&
analysisProfilesEqual(record.analysis_profile, evidenceProfile) &&
canonicalJson(record.parameters) === encodedParameters &&
((entry !== undefined &&
evidenceMatchesEntry(record, entry, snapshot)) ||
(workflowEntry !== undefined &&
evidenceMatchesWorkflowEntry(record, workflowEntry, snapshot)))
)
)
continue;
// Entries bind the result, not every part of the Evidence commitment
// (for example, upstream links). Do not choose provenance by ID sort order.
if (evidence !== undefined && evidence.evidence_id !== record.evidence_id)
return undefined;
evidence = record;
}
return evidence === undefined
? undefined
: rebindEvidenceToTargetPath(evidence, target);
@@ -6,6 +6,105 @@ import { expect, it } from "vitest";
import { runDirectAnalysis } from "../../../src/application/DirectAnalysis.js";
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
import { composedWorkflowFixture } from "../../fixtures/composedWorkflowEvidence.js";
import { readFile, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { createEvidenceBundle } from "../../../src/domain/evidenceBundle.js";
it.each([0, 1, 12])(
"does not replay an arbitrary historical workflow provenance when the snapshot binding is ambiguous (%i procedures)",
async (procedureCount) => {
const original = await composedWorkflowFixture();
original.summary.procedure_count = procedureCount;
const target = await original.open();
const snapshotPath = join(dirname(original.path), "analysis.json");
const current = parseEvidence(
await runDirectAnalysis(
original.dependencies,
original.path,
"binary_overview",
{},
{ snapshotPath },
),
);
expect(current.evidence_links).toHaveLength(1);
const snapshot = parseAnalysisSnapshot(
JSON.parse(await readFile(snapshotPath, "utf8")),
);
const uniqueReplay = await composedWorkflowFixture();
expect(uniqueReplay.session.importAnalysisSnapshot(snapshot).ok).toBe(true);
expect(uniqueReplay.session.importAnalysisSnapshot(snapshot)).toEqual(
ok(0),
);
const unique = parseEvidence(
await runDirectAnalysis(
uniqueReplay.dependencies,
original.path,
"binary_overview",
{},
{ snapshotPath },
),
);
expect(unique.evidence_id).toBe(current.evidence_id);
expect(unique.evidence_links).toEqual(current.evidence_links);
expect(uniqueReplay.calls).toEqual([]);
// Earlier composed observations did not carry links. Both are authentic,
// but the serialized replay binding does not identify which provenance won.
const historical = createEvidence(target, current.provider, {
operation: current.operation,
parameters: current.parameters,
result: current.normalized_result,
rawResult: current.raw_result,
analysisProfile: current.analysis_profile!,
confidence: "derived",
limitations: current.limitations,
});
expect(historical.evidence_links).toEqual([]);
expect(historical.evidence_id).not.toBe(current.evidence_id);
const ambiguous = parseAnalysisSnapshot({
...snapshot,
evidence_bundle: createEvidenceBundle([
...snapshot.evidence_bundle.records,
historical,
]),
});
expect(ambiguous.workflow_entries).toEqual(snapshot.workflow_entries);
await writeFile(snapshotPath, JSON.stringify(ambiguous));
const replay = await composedWorkflowFixture();
const delivered = parseEvidence(
await runDirectAnalysis(
replay.dependencies,
original.path,
"binary_overview",
{},
{ snapshotPath },
),
);
expect(delivered.normalized_result).toEqual(current.normalized_result);
expect([current.evidence_id, historical.evidence_id]).not.toContain(
delivered.evidence_id,
);
expect(delivered.evidence_links).toEqual(current.evidence_links);
expect(
delivered.limitations.some((limitation) =>
limitation.includes("this call did not re-run the provider"),
),
).toBe(true);
expect(replay.calls).toEqual([]);
const refreshedReplay = await composedWorkflowFixture();
const refreshed = parseEvidence(
await runDirectAnalysis(
refreshedReplay.dependencies,
original.path,
"binary_overview",
{},
{ snapshotPath },
),
);
expect(refreshed.evidence_id).toBe(delivered.evidence_id);
expect(refreshed.evidence_links).toEqual(delivered.evidence_links);
expect(refreshedReplay.calls).toEqual([]);
},
);
it.each([false, true])(
"links a profiled binary overview only to the atomic record it consumed (cached=%s)",