fix(reconstruction): bind proof evidence to claims

This commit is contained in:
morluto 2026-07-24 14:39:52 +00:00
parent 9f9f9c3968
commit 83b3368d8d
3 changed files with 100 additions and 62 deletions

View File

@ -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<ProofAuthority, readonly ProofEvidenceShape[]>
> = {
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<ProofAuthority, readonly Evidence["authority"][]>
@ -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 = (

View File

@ -46,7 +46,6 @@ interface EvaluationContext {
readonly contradictions: ReadonlyMap<string, readonly string[]>;
}
/** 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,

View File

@ -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<ReconstructionObligationLedgerInput> = {},
@ -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: