fix(reconstruction): authenticate obligation proof boundaries
This commit is contained in:
parent
eeb4349f1f
commit
7b1e3bda00
|
|
@ -4,6 +4,50 @@ 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"]],
|
||||
};
|
||||
|
||||
const proofEvidenceAuthorities: Readonly<
|
||||
Record<ProofAuthority, readonly Evidence["authority"][]>
|
||||
> = {
|
||||
unit: ["shipped-artifact", "controlled-replay"],
|
||||
integration: ["controlled-replay"],
|
||||
protocol: ["controlled-replay", "external-service"],
|
||||
renderer: ["controlled-replay", "external-service"],
|
||||
"packaged-process": ["controlled-replay"],
|
||||
"native-abi": ["shipped-artifact", "controlled-replay"],
|
||||
"live-observation": ["controlled-replay", "external-service"],
|
||||
external: ["external-service"],
|
||||
};
|
||||
|
||||
/** Return whether an actual reconstruction proof is comparable to a requirement. */
|
||||
export const proofAuthoritySatisfies = (
|
||||
|
|
@ -46,9 +90,11 @@ export const evidenceAuthoritySupportsProof = (
|
|||
authority: ProofAuthority,
|
||||
): boolean =>
|
||||
evidence.confidence === "observed" &&
|
||||
(authority === "external"
|
||||
? evidence.authority === "external-service"
|
||||
: ["shipped-artifact", "controlled-replay"].includes(evidence.authority));
|
||||
proofEvidenceAuthorities[authority].includes(evidence.authority) &&
|
||||
proofEvidenceShapes[authority].some(
|
||||
([predicate, operation]) =>
|
||||
evidence.predicate_type === predicate && evidence.operation === operation,
|
||||
);
|
||||
|
||||
/** Check that Evidence observes the original at the required boundary. */
|
||||
export const evidenceSupportsOriginalAuthority = (
|
||||
|
|
|
|||
|
|
@ -454,35 +454,31 @@ const applyDependencyDiagnostics = (
|
|||
obligations: readonly ReconstructionObligation[],
|
||||
context: EvaluationContext,
|
||||
): ReconstructionObligation[] => {
|
||||
const statuses = new Map(
|
||||
obligations.map((obligation) => [
|
||||
obligation.obligation_id,
|
||||
obligation.status,
|
||||
]),
|
||||
);
|
||||
return obligations.map((obligation) => {
|
||||
const diagnostics = [...obligation.diagnostics];
|
||||
for (const dependencyId of obligation.dependency_obligation_ids) {
|
||||
if (!context.candidates.has(dependencyId))
|
||||
addDiagnostic(
|
||||
diagnostics,
|
||||
"dependency-missing",
|
||||
`Dependency obligation is missing: ${dependencyId}.`,
|
||||
);
|
||||
else if (statuses.get(dependencyId) !== "verified")
|
||||
addDiagnostic(
|
||||
diagnostics,
|
||||
"dependency-open",
|
||||
`Dependency obligation is not verified: ${dependencyId}.`,
|
||||
);
|
||||
}
|
||||
const normalized = uniqueDiagnostics(diagnostics);
|
||||
return {
|
||||
...obligation,
|
||||
status: dependencyStatus(obligation.status, normalized),
|
||||
diagnostics: normalized,
|
||||
};
|
||||
});
|
||||
let evaluated = [...obligations];
|
||||
for (let pass = 0; pass < obligations.length; pass += 1) {
|
||||
const statuses = new Map(
|
||||
evaluated.map(({ obligation_id: id, status }) => [id, status]),
|
||||
);
|
||||
let changed = false;
|
||||
evaluated = evaluated.map((obligation) => {
|
||||
const diagnostics = [...obligation.diagnostics];
|
||||
for (const dependencyId of obligation.dependency_obligation_ids) {
|
||||
const missing = !context.candidates.has(dependencyId);
|
||||
if (missing || statuses.get(dependencyId) !== "verified")
|
||||
addDiagnostic(
|
||||
diagnostics,
|
||||
missing ? "dependency-missing" : "dependency-open",
|
||||
`Dependency obligation is ${missing ? "missing" : "not verified"}: ${dependencyId}.`,
|
||||
);
|
||||
}
|
||||
const normalized = uniqueDiagnostics(diagnostics);
|
||||
const status = dependencyStatus(obligation.status, normalized);
|
||||
changed ||= status !== obligation.status;
|
||||
return { ...obligation, status, diagnostics: normalized };
|
||||
});
|
||||
if (!changed) break;
|
||||
}
|
||||
return evaluated;
|
||||
};
|
||||
|
||||
const obligationStatus = (
|
||||
|
|
|
|||
|
|
@ -256,13 +256,12 @@ describe("reconstruction obligation ledger", () => {
|
|||
|
||||
it("closes only when every required case and verifier has comparable authority", () => {
|
||||
const capture = processEvidence();
|
||||
const proof = proofEvidence("packaged");
|
||||
const initial = build(request([capture, proof]));
|
||||
const initial = build(request([capture]));
|
||||
const obligation = initial.obligations[0];
|
||||
if (obligation === undefined)
|
||||
throw new Error("Expected process obligation");
|
||||
const result = build(
|
||||
request([capture, proof], {
|
||||
request([capture], {
|
||||
manifest: {
|
||||
schema_version: 1,
|
||||
bindings: [
|
||||
|
|
@ -279,7 +278,7 @@ describe("reconstruction obligation ledger", () => {
|
|||
fixture_id: `packaged.${caseKind}`,
|
||||
case_kind: caseKind,
|
||||
authority: "packaged-process",
|
||||
evidence_ids: [proof.evidence_id],
|
||||
evidence_ids: [capture.evidence_id],
|
||||
})),
|
||||
verifier: {
|
||||
verifier_id: "verifier.packaged",
|
||||
|
|
@ -287,7 +286,7 @@ describe("reconstruction obligation ledger", () => {
|
|||
command: "npm run verify:package -- process",
|
||||
authority: "packaged-process",
|
||||
status: "pass",
|
||||
result_evidence_id: proof.evidence_id,
|
||||
result_evidence_id: capture.evidence_id,
|
||||
enumerated_obligation_ids: [obligation.obligation_id],
|
||||
nondeterminism: {
|
||||
mode: "partial-order",
|
||||
|
|
@ -310,6 +309,140 @@ describe("reconstruction obligation ledger", () => {
|
|||
expect(result.summary.required_open).toBe(0);
|
||||
});
|
||||
|
||||
it("does not authenticate packaged-process proof with generic replay evidence", () => {
|
||||
const capture = processEvidence();
|
||||
const proof = proofEvidence("generic-packaged");
|
||||
const initial = build(request([capture, proof]));
|
||||
const obligation = initial.obligations[0];
|
||||
if (obligation === undefined)
|
||||
throw new Error("Expected process obligation");
|
||||
const result = build(
|
||||
request([capture, proof], {
|
||||
manifest: {
|
||||
schema_version: 1,
|
||||
bindings: [
|
||||
{
|
||||
obligation_id: obligation.obligation_id,
|
||||
owner: {
|
||||
module_path: "src/processOwner.ts",
|
||||
symbol: "runProcess",
|
||||
owner_sha256: "5".repeat(64),
|
||||
},
|
||||
parser_type: null,
|
||||
original_cases: originalCases(obligation, capture.evidence_id),
|
||||
fixtures: obligation.required_case_kinds.map((caseKind) => ({
|
||||
fixture_id: `packaged.${caseKind}`,
|
||||
case_kind: caseKind,
|
||||
authority: "packaged-process",
|
||||
evidence_ids: [proof.evidence_id],
|
||||
})),
|
||||
verifier: {
|
||||
verifier_id: "verifier.packaged",
|
||||
claim_id: "claim.process",
|
||||
command: "npm run verify:package -- process",
|
||||
authority: "packaged-process",
|
||||
status: "pass",
|
||||
result_evidence_id: proof.evidence_id,
|
||||
enumerated_obligation_ids: [obligation.obligation_id],
|
||||
nondeterminism: {
|
||||
mode: "partial-order",
|
||||
specification: "Fixture scheduling is partially ordered.",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
contradictions: [],
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.obligations[0]?.diagnostics.map(({ code }) => code)).toEqual(
|
||||
expect.arrayContaining([
|
||||
"weak-fixture-authority",
|
||||
"weak-verifier-authority",
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
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 reviewed = ids.map((id, index) => ({
|
||||
...reviewedObligation(id, evidence.evidence_id),
|
||||
dependency_obligation_ids:
|
||||
index === 0 ? [ids[1]] : index === 1 ? [ids[2]] : ["obl.chain.missing"],
|
||||
}));
|
||||
const bindings = reviewed.map((obligation) => ({
|
||||
obligation_id: obligation.obligation_id,
|
||||
owner: {
|
||||
module_path: `src/${obligation.obligation_id}.ts`,
|
||||
symbol: "verify",
|
||||
owner_sha256: "6".repeat(64),
|
||||
},
|
||||
parser_type: null,
|
||||
original_cases: [
|
||||
{
|
||||
kind: "positive" as const,
|
||||
evidence_id: evidence.evidence_id,
|
||||
location: "/fixture/positive",
|
||||
},
|
||||
],
|
||||
fixtures: [
|
||||
{
|
||||
fixture_id: `fixture.${obligation.obligation_id}`,
|
||||
case_kind: "positive" as const,
|
||||
authority: "unit" as const,
|
||||
evidence_ids: [evidence.evidence_id],
|
||||
},
|
||||
],
|
||||
verifier: {
|
||||
verifier_id: `verifier.${obligation.obligation_id}`,
|
||||
claim_id: `claim.${obligation.obligation_id}`,
|
||||
command: "npm test -- fixture-chain",
|
||||
authority: "unit" as const,
|
||||
status: "pass" as const,
|
||||
result_evidence_id: evidence.evidence_id,
|
||||
enumerated_obligation_ids: [obligation.obligation_id],
|
||||
nondeterminism: {
|
||||
mode: "total-order" as const,
|
||||
specification: "Fixture execution is ordered.",
|
||||
},
|
||||
},
|
||||
}));
|
||||
const result = build(
|
||||
request([evidence], {
|
||||
reviewed_obligations: reviewed,
|
||||
manifest: { schema_version: 1, bindings, contradictions: [] },
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.obligations.map(({ status }) => status)).toEqual([
|
||||
"blocked",
|
||||
"blocked",
|
||||
"blocked",
|
||||
]);
|
||||
expect(
|
||||
result.obligations.map(({ diagnostics }) =>
|
||||
diagnostics.map(({ code }) => code),
|
||||
),
|
||||
).toEqual([
|
||||
["dependency-open"],
|
||||
["dependency-open"],
|
||||
["dependency-missing"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("fails closed on parser gaps, ambiguous ownership, and contradiction", () => {
|
||||
const original = createEvidence(
|
||||
undefined,
|
||||
|
|
|
|||
Loading…
Reference in New Issue