mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
fix(ledger): withhold readiness for unresolved evidence (#7928)
This commit is contained in:
1 parent
a4bd924772
commit
63491645fd
3 files changed
+175
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createEvidenceBundle } from "../domain/evidenceBundle.js";
|
||||
|
||||
import {
|
||||
build,
|
||||
@@ -8,6 +9,7 @@ import {
|
||||
processEvidence,
|
||||
proofEvidence,
|
||||
readyProcessLedgerRequest,
|
||||
readyReviewedLedgerRequest,
|
||||
request,
|
||||
reviewedObligation,
|
||||
} from "./ReconstructionObligationLedger.fixture.js";
|
||||
@@ -256,6 +258,114 @@ describe("reconstruction obligation ledger case closure", () => {
|
||||
});
|
||||
|
||||
describe("reconstruction obligation manifest references", () => {
|
||||
it("does not claim readiness with unresolved references on an optional obligation", () => {
|
||||
const input = readyReviewedLedgerRequest();
|
||||
const reviewed = input.reviewed_obligations[0]!;
|
||||
const binding = input.manifest.bindings[0]!;
|
||||
const absent = `ev_${"c".repeat(64)}`;
|
||||
const original = binding.original_cases[0]!.evidence_id;
|
||||
const proof = binding.verifier!.result_evidence_id;
|
||||
const optional = { ...reviewed, required: false };
|
||||
const candidates = [
|
||||
{
|
||||
...binding,
|
||||
original_cases: [
|
||||
...binding.original_cases,
|
||||
{
|
||||
kind: "malformed" as const,
|
||||
evidence_id: absent,
|
||||
location: "/extra",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
...binding,
|
||||
fixtures: [
|
||||
...binding.fixtures,
|
||||
{
|
||||
fixture_id: "fixture.extra",
|
||||
case_kind: "malformed" as const,
|
||||
authority: "unit" as const,
|
||||
evidence_ids: [absent],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
...binding,
|
||||
verifier: { ...binding.verifier!, result_evidence_id: absent },
|
||||
},
|
||||
];
|
||||
for (const candidate of candidates) {
|
||||
const result = build({
|
||||
...input,
|
||||
reviewed_obligations: [optional],
|
||||
manifest: { bindings: [candidate], contradictions: [] },
|
||||
});
|
||||
expect(result.status).toBe("unknown");
|
||||
expect(result.coverage.status).toBe("partial");
|
||||
expect(result.evidence_links).toContain(absent);
|
||||
expect(result.limitations).toContain(
|
||||
"The ledger retains 1 Evidence reference(s) absent from the input bundle; closure cannot be claimed.",
|
||||
);
|
||||
}
|
||||
const contradiction = build({
|
||||
...input,
|
||||
reviewed_obligations: [optional],
|
||||
manifest: {
|
||||
bindings: [binding],
|
||||
contradictions: [
|
||||
{
|
||||
obligation_id: optional.obligation_id,
|
||||
evidence_ids: [original, absent],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(contradiction.status).not.toBe("ready");
|
||||
expect(contradiction.evidence_links).toContain(absent);
|
||||
const resolved = build({
|
||||
...input,
|
||||
reviewed_obligations: [optional],
|
||||
manifest: {
|
||||
bindings: [
|
||||
{
|
||||
...candidates[1]!,
|
||||
fixtures: [
|
||||
...binding.fixtures,
|
||||
{
|
||||
fixture_id: "fixture.extra",
|
||||
case_kind: "malformed",
|
||||
authority: "unit",
|
||||
evidence_ids: [proof],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
contradictions: [],
|
||||
},
|
||||
});
|
||||
expect(resolved.status).toBe("ready");
|
||||
expect(resolved.obligations[0]?.status).toBe("verified");
|
||||
|
||||
const required = readyProcessLedgerRequest();
|
||||
const mixed = build({
|
||||
evidence_bundle: createEvidenceBundle([
|
||||
...input.evidence_bundle.records,
|
||||
...required.evidence_bundle.records,
|
||||
]),
|
||||
reviewed_obligations: [optional],
|
||||
manifest: {
|
||||
bindings: [candidates[1]!, ...required.manifest.bindings],
|
||||
contradictions: [],
|
||||
},
|
||||
});
|
||||
expect(mixed.summary.required_open).toBe(0);
|
||||
expect(mixed.status).toBe("unknown");
|
||||
expect(mixed.evidence_links).toContain(absent);
|
||||
});
|
||||
});
|
||||
|
||||
describe("reconstruction obligation manifest reference controls", () => {
|
||||
it("withholds readiness when a manifest entry names absent Evidence", () => {
|
||||
const ledgerRequest = readyProcessLedgerRequest();
|
||||
// A valid entry for the same case kind must not hide an absent reference.
|
||||
|
||||
@@ -80,6 +80,7 @@ export const evaluateReconstructionObligationLedger = ({
|
||||
generationLimitations,
|
||||
candidates.length,
|
||||
orphanManifestIds,
|
||||
evidenceLinks.filter((id) => !records.has(id)).length,
|
||||
);
|
||||
const coverage = reconstructionObligationCoverage(limitations);
|
||||
const semantic = {
|
||||
@@ -158,6 +159,7 @@ const ledgerLimitations = (
|
||||
generationLimitations: readonly string[],
|
||||
candidateCount: number,
|
||||
orphanManifestIds: readonly string[],
|
||||
missingEvidenceCount: number,
|
||||
): string[] =>
|
||||
[
|
||||
...new Set([
|
||||
@@ -172,6 +174,11 @@ const ledgerLimitations = (
|
||||
`Manifest entries reference unknown obligations: ${orphanManifestIds.join(", ")}.`,
|
||||
]
|
||||
: []),
|
||||
...(missingEvidenceCount > 0
|
||||
? [
|
||||
`The ledger retains ${String(missingEvidenceCount)} Evidence reference(s) absent from the input bundle; closure cannot be claimed.`,
|
||||
]
|
||||
: []),
|
||||
]),
|
||||
].sort();
|
||||
|
||||
|
||||
@@ -17,11 +17,69 @@ import {
|
||||
MIXED_AUTHORITY_SUMMARY,
|
||||
mixedAuthorityRequest,
|
||||
readyProcessLedgerRequest,
|
||||
readyReviewedLedgerRequest,
|
||||
} from "../../../src/application/ReconstructionObligationLedger.fixture.js";
|
||||
|
||||
const execute = promisify(execFile);
|
||||
|
||||
describe("reconstruction obligation ledger CLI parity", () => {
|
||||
it("withholds readiness for an optional obligation's missing Evidence", async () => {
|
||||
const input = readyReviewedLedgerRequest();
|
||||
const binding = input.manifest.bindings[0]!;
|
||||
const absent = `ev_${"c".repeat(64)}`;
|
||||
const extra = {
|
||||
fixture_id: "fixture.extra",
|
||||
case_kind: "malformed" as const,
|
||||
authority: "unit" as const,
|
||||
evidence_ids: [absent],
|
||||
};
|
||||
for (const evidenceId of [absent, binding.verifier!.result_evidence_id]) {
|
||||
const variant = {
|
||||
...input,
|
||||
reviewed_obligations: [
|
||||
{ ...input.reviewed_obligations[0]!, required: false },
|
||||
],
|
||||
manifest: {
|
||||
...input.manifest,
|
||||
bindings: [
|
||||
{
|
||||
...binding,
|
||||
fixtures: [
|
||||
...binding.fixtures,
|
||||
{ ...extra, evidence_ids: [evidenceId] },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const { stdout } = await execute(
|
||||
process.execPath,
|
||||
[
|
||||
"scripts/rea.mjs",
|
||||
"build-reconstruction-obligation-ledger",
|
||||
JSON.stringify(variant),
|
||||
"--json",
|
||||
],
|
||||
{ cwd: process.cwd(), env: process.env, maxBuffer: 16 * 1_024 * 1_024 },
|
||||
);
|
||||
const ledger = reconstructionObligationLedgerSchema.parse(
|
||||
JSON.parse(stdout).normalized_result,
|
||||
);
|
||||
expect(ledger.status).toBe(evidenceId === absent ? "unknown" : "ready");
|
||||
if (evidenceId === absent) {
|
||||
expect(ledger.evidence_links).toContain(absent);
|
||||
expect(ledger.obligations[0]?.diagnostics).toContainEqual(
|
||||
expect.objectContaining({
|
||||
code: "missing-case",
|
||||
detail: expect.stringContaining(absent),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}, 20_000);
|
||||
});
|
||||
|
||||
describe("reconstruction obligation ledger CLI bundle validation", () => {
|
||||
it("reports non-canonical authenticated bundles as caller input", async () => {
|
||||
const records = ["first", "second"].map((label) =>
|
||||
createEvidence(
|
||||
|
||||
Reference in new issue
Block a user