mirror of
https://github.com/morluto/rea
synced 2026-10-25 14:01:13 -04:00
fix(artifacts): preserve integrity contradictions for one-sided members (#7076)
This commit is contained in:
@@ -324,17 +324,20 @@ const buildArtifactChangeBase = (input: ClassifyPathInput) => ({
|
||||
evidence_links: [...input.evidenceLinks],
|
||||
});
|
||||
|
||||
const classifyPresence = (input: ClassifyPathInput): ArtifactChange | null => {
|
||||
const classifyPresence = (
|
||||
input: ClassifyPathInput,
|
||||
base: ReturnType<typeof buildArtifactChangeBase>,
|
||||
): ArtifactChange | null => {
|
||||
const covered = input.path === "." ? input.rootCovered : input.entriesCovered;
|
||||
if (input.leftOccurrence === undefined)
|
||||
return {
|
||||
...buildArtifactChangeBase(input),
|
||||
...base,
|
||||
classification: covered ? "added" : "unknown",
|
||||
dimensions: [covered ? "content" : "availability"],
|
||||
};
|
||||
if (input.rightOccurrence === undefined)
|
||||
return {
|
||||
...buildArtifactChangeBase(input),
|
||||
...base,
|
||||
classification: covered ? "removed" : "unknown",
|
||||
dimensions: [covered ? "content" : "availability"],
|
||||
};
|
||||
@@ -372,11 +375,12 @@ const classifyAvailability = (
|
||||
};
|
||||
|
||||
const classifyPath = (input: ClassifyPathInput): ArtifactChange | null => {
|
||||
const presence = classifyPresence(input);
|
||||
if (presence !== null) return presence;
|
||||
const base = buildArtifactChangeBase(input);
|
||||
// A declared/observed mismatch is known even without an opposite occurrence.
|
||||
const integrity = classifyIntegrity(input, base);
|
||||
if (integrity !== null) return integrity;
|
||||
const presence = classifyPresence(input, base);
|
||||
if (presence !== null) return presence;
|
||||
const declaration = classifyPyInstallerDeclaration(input, base);
|
||||
if (declaration.handled) return declaration.change;
|
||||
const availability = classifyAvailability(input, base);
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
occurrenceIdForLocation,
|
||||
} from "../../../src/domain/artifactIdentity.js";
|
||||
import { artifactInspectionResultSchema } from "../../../src/domain/artifactInspection.js";
|
||||
import { parseArtifactInventoryEvidence } from "../../../src/domain/artifactInventoryEvidence.js";
|
||||
import {
|
||||
ok as resultOk,
|
||||
err as resultErr,
|
||||
@@ -1258,6 +1259,9 @@ it("attributes ASAR integrity contradictions to their actual inputs and withhold
|
||||
const root = await createTestTempDirectory("rea-asar-attribution-");
|
||||
const source = join(root, "source");
|
||||
await mkdir(source);
|
||||
await writeFile(join(source, "keep.txt"), "unchanged sibling\n");
|
||||
const absentArchive = join(root, "absent.asar");
|
||||
await createPackageWithOptions(source, absentArchive, {});
|
||||
const original = "console.log('ok');\n";
|
||||
const changed = "console.log('no');\n";
|
||||
await writeFile(join(source, "main.js"), original);
|
||||
@@ -1280,7 +1284,12 @@ it("attributes ASAR integrity contradictions to their actual inputs and withhold
|
||||
await server.connect(st);
|
||||
await client.connect(ct);
|
||||
const observations = [];
|
||||
for (const path of [originalArchive, staleArchive, currentArchive]) {
|
||||
for (const path of [
|
||||
originalArchive,
|
||||
staleArchive,
|
||||
currentArchive,
|
||||
absentArchive,
|
||||
]) {
|
||||
expect(
|
||||
(await client.callTool({ name: "open_binary", arguments: { path } }))
|
||||
.isError,
|
||||
@@ -1298,20 +1307,32 @@ it("attributes ASAR integrity contradictions to their actual inputs and withhold
|
||||
PROCESS_COMPARISON_EVIDENCE,
|
||||
])
|
||||
expect(session.recordEvidence(record).ok).toBe(true);
|
||||
const [originalEvidence, stale, current] = observations;
|
||||
const [originalEvidence, stale, current, absent] = observations;
|
||||
if (
|
||||
originalEvidence === undefined ||
|
||||
stale === undefined ||
|
||||
current === undefined
|
||||
current === undefined ||
|
||||
absent === undefined
|
||||
)
|
||||
throw new Error("Expected three artifact observations");
|
||||
for (const [left, right, mismatches] of [
|
||||
[stale, current, [stale.evidence_id]],
|
||||
[current, stale, [stale.evidence_id]],
|
||||
[stale, stale, [stale.evidence_id]],
|
||||
[originalEvidence, current, []],
|
||||
throw new Error("Expected four artifact observations");
|
||||
for (const [left, right, mismatches, presence] of [
|
||||
[stale, current, [stale.evidence_id], "changed"],
|
||||
[current, stale, [stale.evidence_id], "changed"],
|
||||
[stale, stale, [stale.evidence_id], "changed"],
|
||||
[originalEvidence, current, [], "changed"],
|
||||
[absent, stale, [stale.evidence_id], "added"],
|
||||
[stale, absent, [stale.evidence_id], "removed"],
|
||||
[absent, current, [], "added"],
|
||||
[current, absent, [], "removed"],
|
||||
] as const)
|
||||
await assertIntegrityComparison(client, session, left, right, mismatches);
|
||||
await assertIntegrityComparison(
|
||||
client,
|
||||
session,
|
||||
left,
|
||||
right,
|
||||
mismatches,
|
||||
presence,
|
||||
);
|
||||
} finally {
|
||||
await Promise.allSettled([client.close(), server.close(), session.close()]);
|
||||
}
|
||||
@@ -1323,6 +1344,7 @@ const assertIntegrityComparison = async (
|
||||
left: ReturnType<typeof toolEvidence>,
|
||||
right: ReturnType<typeof toolEvidence>,
|
||||
mismatches: readonly string[],
|
||||
presence: "added" | "removed" | "changed",
|
||||
) => {
|
||||
const compared = await client.callTool({
|
||||
name: "compare_artifacts",
|
||||
@@ -1332,12 +1354,31 @@ const assertIntegrityComparison = async (
|
||||
const comparison = toolEvidence(compared.structuredContent);
|
||||
expect(comparison.normalized_result).toMatchObject({
|
||||
status: mismatches.length === 0 ? "changed" : "contradiction",
|
||||
summary: { contradiction: mismatches.length === 0 ? 0 : 1 },
|
||||
integrity_mismatch_evidence_ids: mismatches,
|
||||
});
|
||||
const unknown = session
|
||||
.listUnknowns({ domain: "artifact-comparison" })
|
||||
.find((item) => item.question.includes(comparison.evidence_id));
|
||||
expect(unknown).toBeDefined();
|
||||
const [leftMember, rightMember] = [left, right].map((record) =>
|
||||
parseArtifactInventoryEvidence(record).inventory.occurrences.find(
|
||||
({ logical_path }) => logical_path === "main.js",
|
||||
),
|
||||
);
|
||||
expect(comparison.normalized_result).toMatchObject({
|
||||
changes: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
logical_path: "main.js",
|
||||
classification: mismatches.length > 0 ? "contradiction" : presence,
|
||||
...(mismatches.length > 0 ? { dimensions: ["integrity"] } : {}),
|
||||
left_occurrence_id: leftMember?.occurrence_id ?? null,
|
||||
left_artifact_id: leftMember?.artifact_id ?? null,
|
||||
right_occurrence_id: rightMember?.occurrence_id ?? null,
|
||||
right_artifact_id: rightMember?.artifact_id ?? null,
|
||||
}),
|
||||
]),
|
||||
});
|
||||
if (mismatches.length > 0) {
|
||||
expect(unknown?.contradicting_evidence_ids).toEqual(mismatches);
|
||||
expect(unknown?.supporting_evidence_ids).toEqual(
|
||||
|
||||
Reference in New Issue
Block a user