113 lines
3.8 KiB
TypeScript
113 lines
3.8 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import { EvidenceLedger } from "../src/application/EvidenceLedger.js";
|
|
import type { BinaryTarget } from "../src/domain/binaryTarget.js";
|
|
import {
|
|
createEvidence,
|
|
evidenceSchema,
|
|
parseEvidence,
|
|
} from "../src/domain/evidence.js";
|
|
|
|
const TARGET: BinaryTarget = {
|
|
path: "/tmp/fixture",
|
|
sha256: "a".repeat(64),
|
|
kind: "executable",
|
|
format: "mach-o",
|
|
architecture: "arm64",
|
|
availableArchitectures: ["arm64"],
|
|
loaderArgs: ["-l", "Mach-O", "--aarch64"],
|
|
};
|
|
const PROVIDER = { id: "fixture", name: "Fixture provider", version: "1" };
|
|
|
|
describe("analysis evidence", () => {
|
|
it("creates provider-neutral, deterministic Evidence v2", () => {
|
|
const observation = {
|
|
operation: "procedure_info",
|
|
parameters: { document: null, procedure: "0x1000" },
|
|
result: { name: "main" },
|
|
redactedRawPayload: { token: "<redacted:token>" },
|
|
} as const;
|
|
const evidence = createEvidence(TARGET, PROVIDER, observation);
|
|
expect(evidenceSchema.parse(evidence)).toEqual(evidence);
|
|
expect(parseEvidence(evidence)).toEqual(evidence);
|
|
expect(evidence).toMatchObject({
|
|
schema_version: 2,
|
|
provider: PROVIDER,
|
|
subject: { digest: { sha256: "a".repeat(64) } },
|
|
confidence: "observed",
|
|
authority: "shipped-artifact",
|
|
});
|
|
expect(evidence.evidence_id).toMatch(/^ev_[a-f0-9]{64}$/u);
|
|
expect(evidence.raw_payload_sha256).toMatch(/^[a-f0-9]{64}$/u);
|
|
expect(createEvidence(TARGET, PROVIDER, observation)).toEqual(evidence);
|
|
});
|
|
|
|
it("excludes local paths and redacted raw payloads from semantic identity", () => {
|
|
const observation = {
|
|
operation: "health",
|
|
parameters: {},
|
|
result: true,
|
|
redactedRawPayload: { pid: 100 },
|
|
} as const;
|
|
const first = createEvidence(TARGET, PROVIDER, observation);
|
|
const moved = createEvidence(
|
|
{ ...TARGET, path: "/other/renamed-fixture" },
|
|
PROVIDER,
|
|
{ ...observation, redactedRawPayload: { pid: 200 } },
|
|
);
|
|
expect(moved.evidence_id).toBe(first.evidence_id);
|
|
expect(moved.raw_payload_sha256).not.toBe(first.raw_payload_sha256);
|
|
});
|
|
|
|
it("rejects semantic tampering", () => {
|
|
const evidence = createEvidence(TARGET, PROVIDER, {
|
|
operation: "health",
|
|
parameters: {},
|
|
result: true,
|
|
});
|
|
expect(() => parseEvidence({ ...evidence, result: false })).toThrow(
|
|
"semantic identifier",
|
|
);
|
|
});
|
|
|
|
it("deduplicates and atomically imports bounded bundles", () => {
|
|
const evidence = createEvidence(TARGET, PROVIDER, {
|
|
operation: "health",
|
|
parameters: {},
|
|
result: true,
|
|
});
|
|
const ledger = new EvidenceLedger({ maxRecords: 1 });
|
|
expect(ledger.record(evidence)).toBe("added");
|
|
expect(ledger.record(evidence)).toBe("duplicate");
|
|
const relocated = createEvidence(
|
|
{ ...TARGET, path: "/relocated/different-name" },
|
|
PROVIDER,
|
|
{ operation: "health", parameters: {}, result: true },
|
|
);
|
|
expect(relocated.evidence_id).toBe(evidence.evidence_id);
|
|
expect(ledger.record(relocated)).toBe("duplicate");
|
|
expect(ledger.import({ bundle_version: 1, records: [evidence] })).toBe(0);
|
|
expect(ledger.export().records).toEqual([evidence]);
|
|
ledger.clear();
|
|
expect(ledger.export().records).toEqual([]);
|
|
});
|
|
|
|
it("rejects duplicate semantic IDs with conflicting raw payload hashes", () => {
|
|
const ledger = new EvidenceLedger({ maxRecords: 2 });
|
|
const first = createEvidence(TARGET, PROVIDER, {
|
|
operation: "health",
|
|
parameters: {},
|
|
result: true,
|
|
redactedRawPayload: { pid: 1 },
|
|
});
|
|
const second = createEvidence(TARGET, PROVIDER, {
|
|
operation: "health",
|
|
parameters: {},
|
|
result: true,
|
|
redactedRawPayload: { pid: 2 },
|
|
});
|
|
ledger.record(first);
|
|
expect(() => ledger.record(second)).toThrow("Conflicting evidence");
|
|
});
|
|
});
|