fix(conformance): preserve own JSON keys during comparison (#573)

This commit is contained in:
Kaoru0822 2026-10-05 06:42:26 -07:00 committed by GitHub
parent f5013c50ca
commit 3460eda9ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 91 additions and 5 deletions

View File

@ -319,11 +319,11 @@ function sortKeys(value: unknown): unknown {
if (value === null || typeof value !== "object") return value;
if (Array.isArray(value)) return value.map(sortKeys);
if (!isRecord(value)) return value;
const sorted: Record<string, unknown> = {};
for (const key of Object.keys(value).sort()) {
sorted[key] = sortKeys(value[key]);
}
return sorted;
return Object.fromEntries(
Object.keys(value)
.sort()
.map((key) => [key, sortKeys(value[key])]),
);
}
/**

View File

@ -262,3 +262,53 @@ describe("conformance replay package boundary", () => {
).rejects.toThrow(/replay_plans references unknown scenario missing/u);
});
});
describe("conformance structured JSON drift", () => {
it("detects an added own __proto__ field in captured JSON as drift", async () => {
const expected = createEvidence(
undefined,
{ id: "fixture-provider", name: "Fixture provider", version: "1" },
{ operation: "run", parameters: {}, result: { filesystem: {} } },
);
const pkg = createConformancePackage({
...validPackageInput,
expected_evidence: [
{
scenario_id: "s1",
envelopes: [expected],
bundle: null,
required_dimensions: ["filesystem"],
},
],
verifier_contracts: [
{
scenario_id: "s1",
dimensions: [
{ name: "filesystem", required: true, comparison: "semantic" },
],
timing_tolerance_ms: 0,
},
],
});
const captured: unknown = JSON.parse('{"__proto__":{"added":true}}');
const actualEvidenceId = `ev_${"4".repeat(64)}`;
const result = await replayConformancePackage(
pkg,
{
s1: {
evidence_id: actualEvidenceId,
normalized_result: { filesystem: captured },
},
},
passingRunner,
);
expect(result.drift_detected).toBe(true);
expect(result.trust_gate_results[0]?.verdict).toBe("fail");
expect(result.first_drift).toMatchObject({
scenario_id: "s1",
dimension: "filesystem",
evidence_id: actualEvidenceId,
});
});
});

View File

@ -198,3 +198,39 @@ describe("conformance trust gates", () => {
expect(result.verdict).toBe("fail");
});
});
describe("conformance structured JSON comparison", () => {
it.each([
['{"__proto__":{"version":1}}', '{"__proto__":{"version":2}}'],
['{"__proto__":"before"}', '{"__proto__":"after"}'],
["{}", '{"__proto__":null}'],
[
'{"entries":[{"payload":{"__proto__":{"version":1}}}]}',
'{"entries":[{"payload":{"__proto__":{"version":2}}}]}',
],
['{"constructor":1}', '{"constructor":2}'],
['{"prototype":1}', '{"prototype":2}'],
])("preserves changed own JSON keys in %s", (before, after) => {
const expected: unknown = JSON.parse(before);
const actual: unknown = JSON.parse(after);
expect(compareDimension("filesystem", expected, actual)).toMatchObject({
status: "mismatch",
});
expect(JSON.stringify(expected)).toBe(before);
expect(JSON.stringify(actual)).toBe(after);
});
it("ignores key order while preserving own JSON keys in nested objects", () => {
const expected: unknown = JSON.parse(
'{"__proto__":{"a":1,"b":2},"constructor":[{"x":1,"prototype":2}]}',
);
const actual: unknown = JSON.parse(
'{"constructor":[{"prototype":2,"x":1}],"__proto__":{"b":2,"a":1}}',
);
expect(compareDimension("filesystem", expected, actual)).toMatchObject({
status: "match",
});
});
});