fix(evidence): enforce the combined record limit

This commit is contained in:
N0zoM1z0 2026-07-14 20:37:10 +00:00
parent 528c05e2fa
commit 834c70cf4b
2 changed files with 52 additions and 2 deletions

View File

@ -62,7 +62,12 @@ export class EvidenceLedger {
? ok("duplicate")
: err(new EvidenceIntegrityError("Conflicting evidence record"));
}
if (this.#records.size >= this.limits.maxRecords)
if (
this.#exceedsRecordLimit(
this.#records.size + 1,
this.#unknownRevisions.size,
)
)
return err(new EvidenceLimitError("records", this.limits.maxRecords));
const bytes = serializedBytes(evidence);
if (this.#bytes + bytes > this.limits.maxBytes)
@ -344,7 +349,7 @@ export class EvidenceLedger {
number,
EvidenceIntegrityError | EvidenceLimitError | UnknownRegistryError
> {
if (records.size + unknowns.size > this.limits.maxRecords)
if (this.#exceedsRecordLimit(records.size, unknowns.size))
return err(new EvidenceLimitError("records", this.limits.maxRecords));
const bytes =
[...records.values()].reduce(
@ -367,6 +372,10 @@ export class EvidenceLedger {
return ok(bytes);
}
#exceedsRecordLimit(recordCount: number, unknownCount: number): boolean {
return recordCount + unknownCount > this.limits.maxRecords;
}
#commit(
records: ReadonlyMap<string, Evidence>,
unknowns: ReadonlyMap<string, ResidualUnknown>,

View File

@ -204,6 +204,47 @@ describe("analysis evidence", () => {
expect(byteBound.export().records).toEqual([]);
});
it("counts unknown revisions when enforcing direct record limits", () => {
const ledger = new EvidenceLedger({
maxRecords: 2,
maxBytes: 1_000_000,
});
const mutation = createEvidence(undefined, PROVIDER, {
predicateType: "rea.residual-unknown-mutation/v1",
operation: "record_unknown",
parameters: {},
result: { action: "record" },
});
const unknown = recordUnknownInputSchema.parse({
approved: true,
question: "What remains unresolved?",
severity: "high",
domain: "record-limit-test",
supporting_evidence_ids: [],
contradicting_evidence_ids: [],
required_authority: "shipped-artifact",
required_confidence: "observed",
required_environment: null,
recommended_probes: [],
relationships: [],
});
expect(ledger.recordUnknown(unknown, mutation).ok).toBe(true);
const direct = createEvidence(TARGET, PROVIDER, {
operation: "health",
parameters: {},
result: true,
});
expect(ledger.record(direct)).toMatchObject({
ok: false,
error: { _tag: "EvidenceLimitError", limit: "records", maximum: 2 },
});
expect(ledger.export()).toMatchObject({
records: [mutation],
unknowns: [expect.objectContaining({ domain: "record-limit-test" })],
});
});
it("derives byte-stable manifests independent of record insertion order", () => {
const artifactEvidence = createEvidence(TARGET, PROVIDER, {
operation: "health",