mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
fix(reconstruction): evaluate one effective completion scope
Include required-claim surfaces and reachable dependencies in ownership, verifier commitments, contradiction, package proof and summary checks. Preserve all applicable package outcomes instead of accepting any passing proof. Adds authenticated synthetic scope cases and a shared public CLI/MCP regression workflow. Validation is pending the centrally coordinated lane; single-file formatting and lint completed.
This commit is contained in:
@@ -11,6 +11,23 @@ record can still be insufficient proof: a passing verifier summary cannot
|
||||
promote inferred Evidence into a replay observation. That case returns
|
||||
`unknown`, while a missing or tampered record remains an input error.
|
||||
|
||||
## Effective completion scope
|
||||
|
||||
The selected boundary requires the union of its explicit surface IDs, the
|
||||
surfaces of every required claim, and every declared dependency reachable from
|
||||
those surfaces. Cycles are visited once. Missing reachable dependencies remain
|
||||
visible as `dependency-surface-missing`; they keep closure partial. The
|
||||
`required_surfaces` summary counts this complete set, including absent IDs,
|
||||
and `required_claims` counts unique required claims.
|
||||
|
||||
Every reachable surface receives the same ownership, path, distribution,
|
||||
disposition and authority-independence checks. Active contradictions on any of
|
||||
these surfaces affect the boundary even when the caller did not repeat the
|
||||
surface ID in `required_surface_ids`. Package proof must cover the artifacts of
|
||||
this effective scope. A claim verifier must commit to its own surfaces and
|
||||
their reachable dependencies, including their artifact and implemented-owner
|
||||
digests. Callers do not need to duplicate the transitive closure in the request.
|
||||
|
||||
## Comparable verifier Evidence
|
||||
|
||||
At least one record explicitly cited by the latest verifier observation must
|
||||
@@ -41,3 +58,9 @@ neither a passing nor a failed package workflow.
|
||||
The evaluator preserves Evidence provenance and returns an actionable unknown
|
||||
when qualification is unavailable. It does not change the cited records to
|
||||
make their authority match a caller's declaration.
|
||||
|
||||
All applicable package-proof outcomes participate. A failed applicable proof
|
||||
fails closure even when another proof passes; unknown, unsupported or skipped
|
||||
applicable outcomes keep it unknown. The result cites all applicable outcomes.
|
||||
Proof array order does not establish freshness or supersession. A proof for
|
||||
unrelated artifact digests does not contradict the selected boundary.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { reconstructionCoverageScopeCases } from "./reconstructionCoverageScope.fixture.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
@@ -608,3 +609,28 @@ describe("reconstruction coverage reference constraints", () => {
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("effective reconstruction boundary", () => {
|
||||
for (const {
|
||||
label,
|
||||
coverage,
|
||||
status,
|
||||
codes,
|
||||
surfaces,
|
||||
evidenceIds,
|
||||
} of reconstructionCoverageScopeCases())
|
||||
it(label, () => {
|
||||
const result = assertReconstructionCoverageEvaluation(
|
||||
coverage,
|
||||
"replacement.cli",
|
||||
RECONSTRUCTION_COVERAGE_NOW,
|
||||
);
|
||||
expect(result.status).toBe(status);
|
||||
expect(result.summary.required_surfaces).toBe(surfaces);
|
||||
expect(result.reasons.map(({ code }) => code).sort()).toEqual(
|
||||
[...codes].sort(),
|
||||
);
|
||||
expect(result.evidence_ids).toEqual(expect.arrayContaining(evidenceIds));
|
||||
expect(result.recommended_probes).toHaveLength(codes.length);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { reconstructionCoverageScope } from "./reconstructionCoverageScope.js";
|
||||
import { z } from "zod";
|
||||
import { AnalysisInputError } from "./analysisErrorCore.js";
|
||||
import type { EvidenceIntegrityError } from "./evidenceErrors.js";
|
||||
@@ -345,6 +346,7 @@ export const evaluateParsedReconstructionClosure = (
|
||||
const context: ReconstructionEvaluationContext = {
|
||||
...parsed,
|
||||
boundary,
|
||||
scope: reconstructionCoverageScope(parsed, boundary),
|
||||
nowEpochMs,
|
||||
nowInstant,
|
||||
reasons: [],
|
||||
@@ -360,8 +362,8 @@ export const evaluateParsedReconstructionClosure = (
|
||||
boundary_id: boundary.boundary_id,
|
||||
status: closureStatus(reasons),
|
||||
summary: {
|
||||
required_surfaces: boundary.required_surface_ids.length,
|
||||
required_claims: boundary.required_claim_ids.length,
|
||||
required_surfaces: context.scope.surfaceIds.size,
|
||||
required_claims: context.scope.claimIds.size,
|
||||
reasons: reasons.length,
|
||||
},
|
||||
reasons,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { ReconstructionCoverageScope } from "./reconstructionCoverageScope.js";
|
||||
import type {
|
||||
ReconstructionClosureResult,
|
||||
ReconstructionCoverageData,
|
||||
@@ -42,6 +43,7 @@ type Boundary = ReconstructionCoverageData["boundaries"][number];
|
||||
type ClosureReason = ReconstructionClosureResult["reasons"][number];
|
||||
export interface ReconstructionEvaluationContext extends ParsedReconstructionCoverage {
|
||||
readonly boundary: Boundary;
|
||||
readonly scope: ReconstructionCoverageScope;
|
||||
readonly nowEpochMs: number;
|
||||
readonly nowInstant: OffsetDateTimeInstant;
|
||||
readonly reasons: ClosureReason[];
|
||||
@@ -51,26 +53,28 @@ export interface ReconstructionEvaluationContext extends ParsedReconstructionCov
|
||||
export const evaluateReconstructionSurfaces = (
|
||||
context: ReconstructionEvaluationContext,
|
||||
): void => {
|
||||
for (const surfaceId of context.boundary.required_surface_ids) {
|
||||
for (const surfaceId of context.scope.surfaceIds) {
|
||||
const surface = context.surfaces.get(surfaceId);
|
||||
if (surface === undefined) {
|
||||
addReason(
|
||||
context,
|
||||
"surface-missing",
|
||||
surfaceId,
|
||||
"Required surface is absent from the authoritative inventory.",
|
||||
);
|
||||
const dependents = context.scope.missingDependencies.get(surfaceId);
|
||||
if (dependents === undefined)
|
||||
addReason(
|
||||
context,
|
||||
"surface-missing",
|
||||
surfaceId,
|
||||
"Required surface is absent from the authoritative inventory.",
|
||||
);
|
||||
else
|
||||
for (const dependentId of dependents)
|
||||
addReason(
|
||||
context,
|
||||
"dependency-surface-missing",
|
||||
dependentId,
|
||||
`Declared dependency surface is absent: ${surfaceId}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
addEvidence(context, surface.evidence_ids);
|
||||
for (const dependencyId of surface.dependency_surface_ids)
|
||||
if (!context.surfaces.has(dependencyId))
|
||||
addReason(
|
||||
context,
|
||||
"dependency-surface-missing",
|
||||
surfaceId,
|
||||
`Declared dependency surface is absent: ${dependencyId}`,
|
||||
);
|
||||
evaluateOwner(context, surfaceId);
|
||||
}
|
||||
};
|
||||
@@ -152,7 +156,7 @@ export const evaluateReconstructionClaims = (
|
||||
context: ReconstructionEvaluationContext,
|
||||
): void => {
|
||||
const indexes = createVerifierIndexes(context);
|
||||
for (const claimId of context.boundary.required_claim_ids) {
|
||||
for (const claimId of context.scope.claimIds) {
|
||||
const claim = context.claims.get(claimId);
|
||||
if (claim === undefined) {
|
||||
addReason(
|
||||
@@ -189,6 +193,7 @@ export const evaluateReconstructionClaims = (
|
||||
};
|
||||
|
||||
interface VerifierIndexes {
|
||||
readonly claimSurfaceIds: ReconstructionCoverageScope["claimSurfaceIds"];
|
||||
readonly contractsByClaim: ReadonlyMap<
|
||||
string,
|
||||
readonly ReconstructionVerifierContract[]
|
||||
@@ -226,6 +231,7 @@ const createVerifierIndexes = (
|
||||
}
|
||||
|
||||
return {
|
||||
claimSurfaceIds: context.scope.claimSurfaceIds,
|
||||
contractsByClaim,
|
||||
latestResultsByVerifier: latestVerifierObservationsByVerifier(
|
||||
coverage.verifier_results,
|
||||
@@ -342,7 +348,10 @@ const verifierResultMatchesContract = (
|
||||
result: ReconstructionCoverageData["verifier_results"][number],
|
||||
indexes: VerifierIndexes,
|
||||
): boolean => {
|
||||
const expectedArtifactDigests = claim.surface_ids.flatMap((surfaceId) => {
|
||||
const requiredSurfaceIds = [
|
||||
...(indexes.claimSurfaceIds.get(claim.claim_id) ?? []),
|
||||
];
|
||||
const expectedArtifactDigests = requiredSurfaceIds.flatMap((surfaceId) => {
|
||||
const surface = indexes.surfaces.get(surfaceId);
|
||||
const artifact =
|
||||
surface === undefined
|
||||
@@ -350,7 +359,7 @@ const verifierResultMatchesContract = (
|
||||
: indexes.artifacts.get(surface.artifact_id);
|
||||
return artifact === undefined ? [] : [artifact.artifact_sha256];
|
||||
});
|
||||
const expectedOwnerDigests = claim.surface_ids.flatMap((surfaceId) =>
|
||||
const expectedOwnerDigests = requiredSurfaceIds.flatMap((surfaceId) =>
|
||||
(indexes.ownersBySurface.get(surfaceId) ?? []).flatMap(({ ownership }) =>
|
||||
ownership.disposition === "implemented" ? [ownership.owner_sha256] : [],
|
||||
),
|
||||
@@ -399,11 +408,9 @@ export const evaluateReconstructionCoverageRisks = (
|
||||
if (
|
||||
contradiction.status === "active" &&
|
||||
(contradiction.surface_ids.some((id) =>
|
||||
context.boundary.required_surface_ids.includes(id),
|
||||
context.scope.surfaceIds.has(id),
|
||||
) ||
|
||||
contradiction.claim_ids.some((id) =>
|
||||
context.boundary.required_claim_ids.includes(id),
|
||||
))
|
||||
contradiction.claim_ids.some((id) => context.scope.claimIds.has(id)))
|
||||
) {
|
||||
addEvidence(context, contradiction.evidence_ids);
|
||||
addReason(
|
||||
@@ -418,7 +425,7 @@ export const evaluateReconstructionCoverageRisks = (
|
||||
export const evaluateReconstructionPackageProofs = (
|
||||
context: ReconstructionEvaluationContext,
|
||||
): void => {
|
||||
const requiredArtifactDigests = context.boundary.required_surface_ids.flatMap(
|
||||
const requiredArtifactDigests = [...context.scope.surfaceIds].flatMap(
|
||||
(surfaceId) => {
|
||||
const surface = context.surfaces.get(surfaceId);
|
||||
const artifact =
|
||||
@@ -438,34 +445,34 @@ export const evaluateReconstructionPackageProofs = (
|
||||
requiredArtifactDigests.every((digest) =>
|
||||
proof.artifact_sha256s.includes(digest),
|
||||
) && packageProofHasComparableEvidence(proof, context.evidenceById);
|
||||
const passing = proofs.find(
|
||||
(proof) => proof.status === "pass" && applicable(proof),
|
||||
const applicableProofs = proofs.filter(applicable);
|
||||
addEvidence(
|
||||
context,
|
||||
applicableProofs.flatMap((proof) => proof.evidence_ids),
|
||||
);
|
||||
if (passing !== undefined) {
|
||||
addEvidence(context, passing.evidence_ids);
|
||||
continue;
|
||||
}
|
||||
const failing = proofs.find(
|
||||
(proof) => proof.status === "fail" && applicable(proof),
|
||||
);
|
||||
if (failing !== undefined) {
|
||||
addEvidence(context, failing.evidence_ids);
|
||||
if (applicableProofs.some((proof) => proof.status === "fail")) {
|
||||
addReason(
|
||||
context,
|
||||
"package-proof-failed",
|
||||
kind,
|
||||
"Required package or integration proof failed.",
|
||||
"Required package or integration proof failed; all applicable outcomes are retained.",
|
||||
);
|
||||
} else if (
|
||||
applicableProofs.length > 0 &&
|
||||
applicableProofs.every((proof) => proof.status === "pass")
|
||||
) {
|
||||
continue;
|
||||
} else if (proofs.length > 0) {
|
||||
addEvidence(
|
||||
context,
|
||||
proofs.flatMap(({ evidence_ids }) => evidence_ids),
|
||||
);
|
||||
if (applicableProofs.length === 0)
|
||||
addEvidence(
|
||||
context,
|
||||
proofs.flatMap((proof) => proof.evidence_ids),
|
||||
);
|
||||
addReason(
|
||||
context,
|
||||
"package-proof-unknown",
|
||||
kind,
|
||||
"Required package or integration proof has no qualifying passing Evidence.",
|
||||
"Required package or integration proof has unresolved or unqualified outcomes.",
|
||||
);
|
||||
} else
|
||||
addReason(
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
import { createEvidence } from "./evidence.js";
|
||||
import { createEvidenceBundle } from "./evidenceBundle.js";
|
||||
import {
|
||||
completeReconstructionCoverageData,
|
||||
reconstructionCoverageEvidenceId,
|
||||
} from "./reconstructionCoverage.fixture.js";
|
||||
import type {
|
||||
ReconstructionCoverageData,
|
||||
ReconstructionClosureResult,
|
||||
} from "./reconstructionCoverage.js";
|
||||
|
||||
const indirectSurface = (coverage: ReconstructionCoverageData): void => {
|
||||
coverage.surfaces.push({
|
||||
...coverage.surfaces[0]!,
|
||||
surface_id: "cli.indirect",
|
||||
});
|
||||
coverage.owners.push({ ...coverage.owners[0]!, surface_id: "cli.indirect" });
|
||||
};
|
||||
|
||||
const conflictingPackageProof = (
|
||||
coverage: ReconstructionCoverageData,
|
||||
status: "fail" | "unknown",
|
||||
): string => {
|
||||
const evidence = createEvidence(
|
||||
undefined,
|
||||
{
|
||||
id: "package-proof-fixture",
|
||||
name: "Package proof fixture",
|
||||
version: "1",
|
||||
},
|
||||
{
|
||||
operation: "record_package_proof",
|
||||
parameters: {},
|
||||
result: { status },
|
||||
authority: "controlled-replay",
|
||||
confidence: "observed",
|
||||
},
|
||||
);
|
||||
coverage.evidence_bundle = createEvidenceBundle([
|
||||
...coverage.evidence_bundle.records,
|
||||
evidence,
|
||||
]);
|
||||
coverage.package_proofs.push({
|
||||
...coverage.package_proofs[0]!,
|
||||
proof_id: `proof.additional.${status}`,
|
||||
status,
|
||||
evidence_ids: [evidence.evidence_id],
|
||||
});
|
||||
return evidence.evidence_id;
|
||||
};
|
||||
|
||||
const cases: {
|
||||
label: string;
|
||||
status: ReconstructionClosureResult["status"];
|
||||
codes: readonly string[];
|
||||
surfaces: number;
|
||||
change: (coverage: ReconstructionCoverageData) => readonly string[];
|
||||
}[] = [
|
||||
{
|
||||
label: "complete control",
|
||||
status: "ready",
|
||||
codes: [],
|
||||
surfaces: 1,
|
||||
change: () => [],
|
||||
},
|
||||
{
|
||||
label: "reachable dependency cycle",
|
||||
status: "ready",
|
||||
codes: [],
|
||||
surfaces: 2,
|
||||
change: (coverage) => {
|
||||
indirectSurface(coverage);
|
||||
coverage.surfaces[0]!.dependency_surface_ids = ["cli.indirect"];
|
||||
coverage.surfaces[1]!.dependency_surface_ids = ["cli.help"];
|
||||
return [];
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "missing transitive dependency",
|
||||
status: "partial",
|
||||
codes: ["dependency-surface-missing"],
|
||||
surfaces: 3,
|
||||
change: (coverage) => {
|
||||
indirectSurface(coverage);
|
||||
coverage.surfaces[0]!.dependency_surface_ids = ["cli.indirect"];
|
||||
coverage.surfaces[1]!.dependency_surface_ids = ["cli.absent"];
|
||||
return [];
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "claim-only authority routing",
|
||||
status: "failed",
|
||||
codes: ["authority-routing-detected"],
|
||||
surfaces: 2,
|
||||
change: (coverage) => {
|
||||
indirectSurface(coverage);
|
||||
coverage.claims[0]!.surface_ids = ["cli.indirect"];
|
||||
const owner = coverage.owners[1]!.ownership;
|
||||
if (owner.disposition === "implemented")
|
||||
owner.authority_route = "detected";
|
||||
return [];
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "claim-only active contradiction",
|
||||
status: "failed",
|
||||
codes: ["active-contradiction"],
|
||||
surfaces: 2,
|
||||
change: (coverage) => {
|
||||
indirectSurface(coverage);
|
||||
coverage.claims[0]!.surface_ids = ["cli.indirect"];
|
||||
const evidenceIds = [
|
||||
reconstructionCoverageEvidenceId("1"),
|
||||
reconstructionCoverageEvidenceId("5"),
|
||||
];
|
||||
coverage.contradictions = [
|
||||
{
|
||||
contradiction_id: "contradiction.indirect",
|
||||
status: "active",
|
||||
surface_ids: ["cli.indirect"],
|
||||
claim_ids: [],
|
||||
evidence_ids: evidenceIds,
|
||||
},
|
||||
];
|
||||
return evidenceIds;
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "claim-only artifact package coverage",
|
||||
status: "unknown",
|
||||
codes: ["package-proof-unknown", "package-proof-unknown"],
|
||||
surfaces: 2,
|
||||
change: (coverage) => {
|
||||
indirectSurface(coverage);
|
||||
coverage.artifacts.push({
|
||||
...coverage.artifacts[0]!,
|
||||
artifact_id: "authority.v2",
|
||||
artifact_sha256: "9".repeat(64),
|
||||
});
|
||||
coverage.surfaces[1]!.artifact_id = "authority.v2";
|
||||
coverage.claims[0]!.surface_ids = ["cli.indirect"];
|
||||
coverage.verifier_results[0]!.artifact_sha256s.push("9".repeat(64));
|
||||
return [];
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "transitive owner commitment",
|
||||
status: "unknown",
|
||||
codes: ["verifier-result-incompatible"],
|
||||
surfaces: 2,
|
||||
change: (coverage) => {
|
||||
indirectSurface(coverage);
|
||||
coverage.surfaces[0]!.dependency_surface_ids = ["cli.indirect"];
|
||||
const owner = coverage.owners[1]!.ownership;
|
||||
if (owner.disposition === "implemented")
|
||||
owner.owner_sha256 = "9".repeat(64);
|
||||
return [];
|
||||
},
|
||||
},
|
||||
...([false, true] as const).map((reverse) => ({
|
||||
label: `conflicting package proofs ${reverse ? "reversed" : "forward"}`,
|
||||
status: "failed" as const,
|
||||
codes: ["package-proof-failed"],
|
||||
surfaces: 1,
|
||||
change: (coverage: ReconstructionCoverageData) => {
|
||||
const evidenceId = conflictingPackageProof(coverage, "fail");
|
||||
if (reverse) coverage.package_proofs.reverse();
|
||||
return [evidenceId];
|
||||
},
|
||||
})),
|
||||
{
|
||||
label: "unresolved applicable package proof",
|
||||
status: "unknown",
|
||||
codes: ["package-proof-unknown"],
|
||||
surfaces: 1,
|
||||
change: (coverage) => [conflictingPackageProof(coverage, "unknown")],
|
||||
},
|
||||
];
|
||||
/** Small authenticated synthetic workspaces shared by domain and public workflows. */
|
||||
export const reconstructionCoverageScopeCases = () =>
|
||||
cases.map(({ change, ...expectation }) => {
|
||||
const coverage = completeReconstructionCoverageData();
|
||||
return { ...expectation, coverage, evidenceIds: change(coverage) };
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
import type {
|
||||
ParsedReconstructionCoverage,
|
||||
ReconstructionCoverageData,
|
||||
} from "./reconstructionCoverage.js";
|
||||
|
||||
/** One completion scope shared by ownership, proof, risk and summary checks. */
|
||||
export interface ReconstructionCoverageScope {
|
||||
readonly surfaceIds: ReadonlySet<string>;
|
||||
readonly claimIds: ReadonlySet<string>;
|
||||
readonly claimSurfaceIds: ReadonlyMap<string, ReadonlySet<string>>;
|
||||
readonly missingDependencies: ReadonlyMap<string, ReadonlySet<string>>;
|
||||
}
|
||||
|
||||
export const reconstructionCoverageScope = (
|
||||
parsed: ParsedReconstructionCoverage,
|
||||
boundary: ReconstructionCoverageData["boundaries"][number],
|
||||
): ReconstructionCoverageScope => {
|
||||
const claimIds = new Set(boundary.required_claim_ids);
|
||||
const claimSurfaceIds = new Map<string, ReadonlySet<string>>();
|
||||
for (const claimId of claimIds) {
|
||||
const claim = parsed.claims.get(claimId);
|
||||
if (claim !== undefined)
|
||||
claimSurfaceIds.set(
|
||||
claimId,
|
||||
reachableSurfaces(claim.surface_ids, parsed.surfaces).surfaceIds,
|
||||
);
|
||||
}
|
||||
return {
|
||||
...reachableSurfaces(
|
||||
[
|
||||
...boundary.required_surface_ids,
|
||||
...[...claimSurfaceIds.values()].flatMap((ids) => [...ids]),
|
||||
],
|
||||
parsed.surfaces,
|
||||
),
|
||||
claimIds,
|
||||
claimSurfaceIds,
|
||||
};
|
||||
};
|
||||
|
||||
const reachableSurfaces = (
|
||||
roots: readonly string[],
|
||||
surfaces: ParsedReconstructionCoverage["surfaces"],
|
||||
): Pick<ReconstructionCoverageScope, "surfaceIds" | "missingDependencies"> => {
|
||||
const surfaceIds = new Set(roots);
|
||||
const pending = [...surfaceIds];
|
||||
const missingDependencies = new Map<string, Set<string>>();
|
||||
for (let index = 0; index < pending.length; index += 1) {
|
||||
const surface = surfaces.get(pending[index]!);
|
||||
if (surface === undefined) continue;
|
||||
for (const dependencyId of surface.dependency_surface_ids) {
|
||||
if (!surfaces.has(dependencyId)) {
|
||||
const dependents = missingDependencies.get(dependencyId) ?? new Set();
|
||||
dependents.add(surface.surface_id);
|
||||
missingDependencies.set(dependencyId, dependents);
|
||||
}
|
||||
if (!surfaceIds.has(dependencyId)) {
|
||||
surfaceIds.add(dependencyId);
|
||||
pending.push(dependencyId);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { surfaceIds, missingDependencies };
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
import { reconstructionClosureResultSchema } from "../../../src/domain/reconstructionCoverage.js";
|
||||
import { reconstructionCoverageScopeCases } from "../../../src/domain/reconstructionCoverageScope.fixture.js";
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { expect } from "vitest";
|
||||
@@ -115,3 +117,39 @@ cliTest(
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
cliTest(
|
||||
"accounts for effective scope and conflicting proofs through CLI and MCP",
|
||||
async ({ cli }) => {
|
||||
const call = await cli.sharedMcpCall?.();
|
||||
if (call === undefined) throw new Error("Expected shared file MCP session");
|
||||
for (const {
|
||||
label,
|
||||
coverage,
|
||||
status,
|
||||
codes,
|
||||
surfaces,
|
||||
evidenceIds,
|
||||
} of reconstructionCoverageScopeCases()) {
|
||||
coverage.verifier_results[0]!.observed_at = new Date().toISOString();
|
||||
const cliResult = await evaluate(cli, coverage);
|
||||
expect(cliResult.exitCode, label).toBe(0);
|
||||
const mcpResult = await call("evaluate_reconstruction_coverage", {
|
||||
coverage,
|
||||
boundary_id: "replacement.cli",
|
||||
});
|
||||
expect(mcpResult.isError, label).not.toBe(true);
|
||||
for (const output of [cliResult.json, mcpResult.structuredContent]) {
|
||||
const result = reconstructionClosureResultSchema.parse(output);
|
||||
expect(result.status, label).toBe(status);
|
||||
expect(result.summary.required_surfaces, label).toBe(surfaces);
|
||||
expect(result.reasons.map(({ code }) => code).sort(), label).toEqual(
|
||||
[...codes].sort(),
|
||||
);
|
||||
expect(result.evidence_ids, label).toEqual(
|
||||
expect.arrayContaining(evidenceIds),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user