From 83b3368d8debf95df29095ef53a4112ca666bcb2 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Fri, 24 Jul 2026 14:39:52 +0000 Subject: [PATCH] fix(reconstruction): bind proof evidence to claims --- .../ReconstructionObligationAuthority.ts | 67 ++++++++---------- ...econstructionObligationLedgerEvaluation.ts | 25 ++++--- tests/reconstructionObligationLedger.test.ts | 70 +++++++++++++++---- 3 files changed, 100 insertions(+), 62 deletions(-) diff --git a/src/application/ReconstructionObligationAuthority.ts b/src/application/ReconstructionObligationAuthority.ts index 4ebed5f81..cf2813d9b 100644 --- a/src/application/ReconstructionObligationAuthority.ts +++ b/src/application/ReconstructionObligationAuthority.ts @@ -4,37 +4,13 @@ import type { ReconstructionObligation } from "../domain/reconstructionObligatio type ProofAuthority = ReconstructionObligation["required_fixture_authority"]; type OriginalAuthority = ReconstructionObligation["required_original_authority"]; -type ProofEvidenceShape = readonly [predicate: string, operation: string]; - -const proofEvidenceShapes: Readonly< - Record -> = { - unit: [["rea.fixture-verification/v1", "run_fixture_verifier"]], - integration: [["rea.fixture-verification/v1", "run_fixture_verifier"]], - protocol: [["rea.protocol-observation/v1", "observe_protocol"]], - renderer: [ - ["rea.browser-scenario-capture/v1", "capture_browser_scenario"], - ["rea.electron-page-inspection/v1", "inspect_electron_page"], - ], - "packaged-process": [ - ["rea.process-capture/v4", "capture_process_scenario"], - [ - "rea.reconstruction-readiness-fixture/v1", - "run_reconstruction_readiness_fixture", - ], - ], - "native-abi": [ - ["rea.managed-native-verification/v1", "verify_managed_native_boundaries"], - ], - "live-observation": [ - ["javascript-controlled-replay-observation", "run_controlled_replay"], - ["rea.browser-scenario-capture/v1", "capture_browser_scenario"], - ["rea.electron-page-inspection/v1", "inspect_electron_page"], - ["rea.javascript-runtime-observation/v1", "observe_javascript_runtime"], - ["rea.runtime-characterization/v1", "execute_node_characterization"], - ], - external: [["rea.protocol-observation/v1", "observe_protocol"]], -}; +interface ExpectedProof { + readonly obligationId: string; + readonly fixtureId?: string; + readonly caseKind?: string; + readonly verifierId?: string; + readonly claimId?: string; +} const proofEvidenceAuthorities: Readonly< Record @@ -88,13 +64,30 @@ export const proofAuthoritySatisfies = ( export const evidenceAuthoritySupportsProof = ( evidence: Evidence, authority: ProofAuthority, -): boolean => - evidence.confidence === "observed" && - proofEvidenceAuthorities[authority].includes(evidence.authority) && - proofEvidenceShapes[authority].some( - ([predicate, operation]) => - evidence.predicate_type === predicate && evidence.operation === operation, + expected: ExpectedProof, +): boolean => { + if ( + evidence.confidence !== "observed" || + !proofEvidenceAuthorities[authority].includes(evidence.authority) || + evidence.predicate_type !== "rea.reconstruction-proof/v1" || + evidence.operation !== "verify_reconstruction_obligations" + ) + return false; + const result = evidence.normalized_result; + if (typeof result !== "object" || result === null || Array.isArray(result)) + return false; + const includes = (key: string, value: string | undefined): boolean => + value === undefined || + (Array.isArray(result[key]) && result[key].includes(value)); + return ( + result.passed === true && + includes("obligation_ids", expected.obligationId) && + includes("fixture_ids", expected.fixtureId) && + includes("case_kinds", expected.caseKind) && + includes("verifier_ids", expected.verifierId) && + includes("claim_ids", expected.claimId) ); +}; /** Check that Evidence observes the original at the required boundary. */ export const evidenceSupportsOriginalAuthority = ( diff --git a/src/application/ReconstructionObligationLedgerEvaluation.ts b/src/application/ReconstructionObligationLedgerEvaluation.ts index 384be39cf..402d175af 100644 --- a/src/application/ReconstructionObligationLedgerEvaluation.ts +++ b/src/application/ReconstructionObligationLedgerEvaluation.ts @@ -46,7 +46,6 @@ interface EvaluationContext { readonly contradictions: ReadonlyMap; } -/** Evaluate candidates and manifest bindings into one canonical fail-closed ledger. */ export const evaluateReconstructionObligationLedger = ({ candidates: candidatesInput, bundle: bundleInput, @@ -68,12 +67,10 @@ export const evaluateReconstructionObligationLedger = ({ const candidates = allCandidates.slice(0, maxObligations); const omittedCount = allCandidates.length - candidates.length; const context = createEvaluationContext(candidates, bundle, manifest); - const preliminary = candidates.map((candidate) => - evaluateObligation(candidate, context), + const obligations = applyDependencyDiagnostics( + candidates.map((candidate) => evaluateObligation(candidate, context)), + context, ); - const obligations = applyDependencyDiagnostics(preliminary, context); - const summary = summarizeObligations(obligations); - const reports = obligationReports(obligations); const ownershipGraph = obligations .flatMap((obligation) => obligation.binding === null @@ -109,8 +106,8 @@ export const evaluateReconstructionObligationLedger = ({ ); const semantic = { coverage, - summary, - reports, + summary: summarizeObligations(obligations), + reports: obligationReports(obligations), ownership_graph: ownershipGraph, dependency_graph: dependencyGraph, obligations, @@ -327,7 +324,11 @@ const evaluateBinding = ( const evidence = context.evidence.get(id); return ( evidence !== undefined && - evidenceAuthoritySupportsProof(evidence, fixture.authority) + evidenceAuthoritySupportsProof(evidence, fixture.authority, { + obligationId: candidate.obligation_id, + fixtureId: fixture.fixture_id, + caseKind: fixture.case_kind, + }) ); }), ) @@ -375,7 +376,11 @@ const evaluateVerifier = ( candidate.required_verifier_authority, ) || (resultEvidence !== undefined && - !evidenceAuthoritySupportsProof(resultEvidence, verifier.authority)) + !evidenceAuthoritySupportsProof(resultEvidence, verifier.authority, { + obligationId: candidate.obligation_id, + verifierId: verifier.verifier_id, + claimId: verifier.claim_id, + })) ) addDiagnostic( diagnostics, diff --git a/tests/reconstructionObligationLedger.test.ts b/tests/reconstructionObligationLedger.test.ts index e269d838c..e1339d0f9 100644 --- a/tests/reconstructionObligationLedger.test.ts +++ b/tests/reconstructionObligationLedger.test.ts @@ -44,6 +44,37 @@ const processEvidence = () => authority: "controlled-replay", }); +const boundProofEvidence = ( + id: string, + result: { + readonly obligation_ids: readonly string[]; + readonly fixture_ids: readonly string[]; + readonly case_kinds: readonly string[]; + readonly verifier_ids: readonly string[]; + readonly claim_ids: readonly string[]; + }, + authority: "shipped-artifact" | "controlled-replay" = "controlled-replay", +) => + createEvidence( + undefined, + { id: `proof-${id}`, name: "Reconstruction proof", version: "1" }, + { + predicateType: "rea.reconstruction-proof/v1", + operation: "verify_reconstruction_obligations", + parameters: {}, + result: { + passed: true, + obligation_ids: [...result.obligation_ids], + fixture_ids: [...result.fixture_ids], + case_kinds: [...result.case_kinds], + verifier_ids: [...result.verifier_ids], + claim_ids: [...result.claim_ids], + }, + confidence: "observed", + authority, + }, + ); + const request = ( records: readonly Evidence[], overrides: Partial = {}, @@ -260,8 +291,18 @@ describe("reconstruction obligation ledger", () => { const obligation = initial.obligations[0]; if (obligation === undefined) throw new Error("Expected process obligation"); + const fixtureIds = obligation.required_case_kinds.map( + (caseKind) => `packaged.${caseKind}`, + ); + const proof = boundProofEvidence("packaged", { + obligation_ids: [obligation.obligation_id], + fixture_ids: fixtureIds, + case_kinds: obligation.required_case_kinds, + verifier_ids: ["verifier.packaged"], + claim_ids: ["claim.process"], + }); const result = build( - request([capture], { + request([capture, proof], { manifest: { schema_version: 1, bindings: [ @@ -278,7 +319,7 @@ describe("reconstruction obligation ledger", () => { fixture_id: `packaged.${caseKind}`, case_kind: caseKind, authority: "packaged-process", - evidence_ids: [capture.evidence_id], + evidence_ids: [proof.evidence_id], })), verifier: { verifier_id: "verifier.packaged", @@ -286,7 +327,7 @@ describe("reconstruction obligation ledger", () => { command: "npm run verify:package -- process", authority: "packaged-process", status: "pass", - result_evidence_id: capture.evidence_id, + result_evidence_id: proof.evidence_id, enumerated_obligation_ids: [obligation.obligation_id], nondeterminism: { mode: "partial-order", @@ -365,19 +406,18 @@ describe("reconstruction obligation ledger", () => { }); it("propagates an open dependency through the full obligation chain", () => { - const evidence = createEvidence( - undefined, - { id: "fixture-chain", name: "Fixture chain", version: "1" }, - { - predicateType: "rea.fixture-verification/v1", - operation: "run_fixture_verifier", - parameters: {}, - result: { passed: true }, - confidence: "observed", - authority: "shipped-artifact", - }, - ); const ids = ["obl.chain.a", "obl.chain.b", "obl.chain.c"] as const; + const evidence = boundProofEvidence( + "fixture-chain", + { + obligation_ids: ids, + fixture_ids: ids.map((id) => `fixture.${id}`), + case_kinds: ["positive"], + verifier_ids: ids.map((id) => `verifier.${id}`), + claim_ids: ids.map((id) => `claim.${id}`), + }, + "shipped-artifact", + ); const reviewed = ids.map((id, index) => ({ ...reviewedObligation(id, evidence.evidence_id), dependency_obligation_ids: