From ed069bb9d40c3d7b4f97b87144dc1fa3d6833128 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Fri, 23 Oct 2026 09:29:30 +0800 Subject: [PATCH] fix(coverage): consolidate request validation ownership Route CLI and MCP coverage requests through the shared strict workflow boundary before authenticating source Evidence. Preserve canonical advertised schemas, use integrity errors for malformed source bundles, and use the workflow operation for input diagnostics. Remove the unused throwing domain facade and keep semantic fixture assertions on the parsed Result APIs. Validate compiled CLI and stdio MCP parity, strict request rejection, domain authority/time semantics, generated schemas, and unexpected failure propagation. No external providers execute in these checks. --- src/cli/applicationCommands.ts | 3 +- src/domain/reconstructionCoverage.fixture.ts | 29 +++++++++++ src/domain/reconstructionCoverage.test.ts | 48 ++++++++++-------- src/domain/reconstructionCoverage.ts | 17 ------- .../reconstructionCoverageEvidence.test.ts | 4 +- .../registerApplicationTools/coverage.ts | 4 +- .../cli/reconstructionCoverageCli.test.ts | 50 +++++++++++++++++++ .../mcp/reconstructionCoverageMcp.test.ts | 36 +++++++++++++ 8 files changed, 148 insertions(+), 43 deletions(-) diff --git a/src/cli/applicationCommands.ts b/src/cli/applicationCommands.ts index 4cc918b767..a52bf7af06 100644 --- a/src/cli/applicationCommands.ts +++ b/src/cli/applicationCommands.ts @@ -1,4 +1,3 @@ -import { reconstructionCoverageEvaluationInputSchema } from "../domain/reconstructionCoverageInput.js"; import { Cli, z } from "incur"; import { CLI_COMMANDS } from "../cliCommandNames.js"; @@ -145,7 +144,7 @@ const registerCoverageCommand = (cli: CliInstance, logger: Logger): void => logger, name: CLI_COMMANDS.evaluateReconstructionCoverage, description: "Evaluate inline fail-closed reconstruction coverage", - inputSchema: reconstructionCoverageEvaluationInputSchema, + inputSchema: z.unknown(), workflow: async (input) => { const { evaluateReconstructionCoverage } = await import("../application/ReconstructionCoverageService.js"); diff --git a/src/domain/reconstructionCoverage.fixture.ts b/src/domain/reconstructionCoverage.fixture.ts index 78d4b892be..b3052af438 100644 --- a/src/domain/reconstructionCoverage.fixture.ts +++ b/src/domain/reconstructionCoverage.fixture.ts @@ -1,8 +1,12 @@ +import { expect } from "vitest"; + import { createEvidence } from "./evidence.js"; import { createEvidenceBundle } from "./evidenceBundle.js"; import { createReconstructionCoverageData, createReconstructionVerifierContract, + evaluateParsedReconstructionClosure, + readReconstructionCoverageData, } from "./reconstructionCoverage.js"; const digest = (character: string): string => character.repeat(64); @@ -149,6 +153,31 @@ export const completeReconstructionCoverageData = () => { }); }; +/** Assert successful domain parsing and evaluation for semantic test fixtures. */ +export const assertReconstructionCoverageEvaluation = ( + input: unknown, + boundaryId: string, + nowEpochMs: number, +) => { + const parsed = readReconstructionCoverageData(input, ["coverage"]); + expect(parsed.ok).toBe(true); + if (!parsed.ok) + throw new Error("Expected valid reconstruction coverage fixture", { + cause: parsed.error, + }); + const result = evaluateParsedReconstructionClosure( + parsed.value, + boundaryId, + nowEpochMs, + ); + expect(result.ok).toBe(true); + if (!result.ok) + throw new Error("Expected reconstruction coverage evaluation success", { + cause: result.error, + }); + return result.value; +}; + /** Complete workspace with its first verifier result and boundary narrowed once. */ export const completeReconstructionCoverageFixture = () => { const workspace = completeReconstructionCoverageData(); diff --git a/src/domain/reconstructionCoverage.test.ts b/src/domain/reconstructionCoverage.test.ts index 7663b10a59..e1a84e46c3 100644 --- a/src/domain/reconstructionCoverage.test.ts +++ b/src/domain/reconstructionCoverage.test.ts @@ -3,13 +3,14 @@ import { describe, expect, it } from "vitest"; import { completeReconstructionCoverageData, completeReconstructionCoverageFixture, + assertReconstructionCoverageEvaluation, RECONSTRUCTION_COVERAGE_NOW, reconstructionCoverageEvidenceId, } from "./reconstructionCoverage.fixture.js"; import { createReconstructionCoverageData, createReconstructionVerifierContract, - evaluateReconstructionClosure, + evaluateParsedReconstructionClosure, readReconstructionCoverageData, type ReconstructionCoverageData, } from "./reconstructionCoverage.js"; @@ -20,7 +21,7 @@ it("preserves duplicate coverage owners instead of overwriting them in shared in const workspace = completeReconstructionCoverageData(); const owner = workspace.owners[0]; if (owner === undefined) throw new Error("Expected fixture owner"); - const result = evaluateReconstructionClosure( + const result = assertReconstructionCoverageEvaluation( { ...workspace, owners: [owner, owner], @@ -44,7 +45,7 @@ describe("reconstruction coverage closure", () => { const workspace = completeReconstructionCoverageData(); expect( - evaluateReconstructionClosure( + assertReconstructionCoverageEvaluation( workspace, "replacement.cli", RECONSTRUCTION_COVERAGE_NOW, @@ -78,7 +79,7 @@ describe("reconstruction coverage closure", () => { }); expect( - evaluateReconstructionClosure( + assertReconstructionCoverageEvaluation( incomplete, "replacement.cli", RECONSTRUCTION_COVERAGE_NOW, @@ -115,7 +116,7 @@ describe("reconstruction coverage closure", () => { boundaries: [{ ...boundary, required_surface_ids: missingSurfaces }], }); - const result = evaluateReconstructionClosure( + const result = assertReconstructionCoverageEvaluation( incomplete, boundary.boundary_id, RECONSTRUCTION_COVERAGE_NOW, @@ -147,7 +148,7 @@ describe("reconstruction coverage closure", () => { ], }); - const result = evaluateReconstructionClosure( + const result = assertReconstructionCoverageEvaluation( changed, "replacement.cli", RECONSTRUCTION_COVERAGE_NOW, @@ -173,7 +174,7 @@ describe("reconstruction coverage closure", () => { }); expect( - evaluateReconstructionClosure( + assertReconstructionCoverageEvaluation( stale, "replacement.cli", RECONSTRUCTION_COVERAGE_NOW, @@ -233,7 +234,7 @@ describe("reconstruction coverage verifier observations", () => { }); expect( - evaluateReconstructionClosure( + assertReconstructionCoverageEvaluation( shared, boundary.boundary_id, RECONSTRUCTION_COVERAGE_NOW, @@ -281,7 +282,7 @@ describe("reconstruction coverage verifier selection", () => { ], }); expect( - evaluateReconstructionClosure( + assertReconstructionCoverageEvaluation( ambiguous, boundary.boundary_id, RECONSTRUCTION_COVERAGE_NOW, @@ -298,7 +299,7 @@ describe("reconstruction coverage verifier selection", () => { verifier_results: [], }); expect( - evaluateReconstructionClosure( + assertReconstructionCoverageEvaluation( missing, boundary.boundary_id, RECONSTRUCTION_COVERAGE_NOW, @@ -330,12 +331,12 @@ describe("reconstruction coverage conflicting latest verifier observations", () verifier_results: [failed, result], }); - const firstPassResult = evaluateReconstructionClosure( + const firstPassResult = assertReconstructionCoverageEvaluation( firstPass, boundary.boundary_id, RECONSTRUCTION_COVERAGE_NOW, ); - const firstFailResult = evaluateReconstructionClosure( + const firstFailResult = assertReconstructionCoverageEvaluation( firstFail, boundary.boundary_id, RECONSTRUCTION_COVERAGE_NOW, @@ -384,12 +385,12 @@ describe("reconstruction coverage equivalent latest verifier observations", () = ...workspace, verifier_results: [equivalent, first], }); - const forwardResult = evaluateReconstructionClosure( + const forwardResult = assertReconstructionCoverageEvaluation( forward, boundary.boundary_id, RECONSTRUCTION_COVERAGE_NOW, ); - const reversedResult = evaluateReconstructionClosure( + const reversedResult = assertReconstructionCoverageEvaluation( reversed, boundary.boundary_id, RECONSTRUCTION_COVERAGE_NOW, @@ -421,7 +422,7 @@ describe("reconstruction coverage verifier timestamp selection", () => { }); expect( - evaluateReconstructionClosure( + assertReconstructionCoverageEvaluation( changed, boundary.boundary_id, RECONSTRUCTION_COVERAGE_NOW, @@ -441,7 +442,7 @@ describe("reconstruction coverage verifier timestamp selection", () => { }); expect( - evaluateReconstructionClosure( + assertReconstructionCoverageEvaluation( changed, "replacement.cli", RECONSTRUCTION_COVERAGE_NOW, @@ -478,7 +479,7 @@ describe("reconstruction coverage verifier future timestamps", () => { }); expect( - evaluateReconstructionClosure( + assertReconstructionCoverageEvaluation( changed, "replacement.cli", Date.parse("2026-07-16T12:00:00.123Z") + 0.5, @@ -491,8 +492,15 @@ describe("reconstruction coverage verifier future timestamps", () => { it("rejects a non-finite evaluation clock", () => { const workspace = completeReconstructionCoverageData(); + const parsed = readReconstructionCoverageData(workspace, ["coverage"]); + expect(parsed.ok).toBe(true); + if (!parsed.ok) throw parsed.error; expect(() => - evaluateReconstructionClosure(workspace, "replacement.cli", Number.NaN), + evaluateParsedReconstructionClosure( + parsed.value, + "replacement.cli", + Number.NaN, + ), ).toThrow("Expected finite epoch milliseconds."); }); @@ -507,7 +515,7 @@ describe("reconstruction coverage verifier future timestamps", () => { }); expect( - evaluateReconstructionClosure( + assertReconstructionCoverageEvaluation( changed, "replacement.cli", RECONSTRUCTION_COVERAGE_NOW, @@ -533,7 +541,7 @@ describe("reconstruction coverage verifier future timestamps", () => { })), }); - const result = evaluateReconstructionClosure( + const result = assertReconstructionCoverageEvaluation( incomplete, "replacement.cli", RECONSTRUCTION_COVERAGE_NOW, diff --git a/src/domain/reconstructionCoverage.ts b/src/domain/reconstructionCoverage.ts index c8ce64e9ac..5fd1c73e85 100644 --- a/src/domain/reconstructionCoverage.ts +++ b/src/domain/reconstructionCoverage.ts @@ -325,23 +325,6 @@ export const createReconstructionVerifierContract = ( }); }; -/** Evaluate inline coverage data against one named fail-closed boundary. */ -export const evaluateReconstructionClosure = ( - coverageInput: unknown, - boundaryId: string, - nowEpochMs: number, -): ReconstructionClosureResult => { - const parsed = readReconstructionCoverageData(coverageInput, ["coverage"]); - if (!parsed.ok) throw parsed.error; - const result = evaluateParsedReconstructionClosure( - parsed.value, - boundaryId, - nowEpochMs, - ); - if (!result.ok) throw result.error; - return result.value; -}; - /** Evaluate the owner's authenticated workspace and lookup facts. */ export const evaluateParsedReconstructionClosure = ( parsed: ParsedReconstructionCoverage, diff --git a/src/domain/reconstructionCoverageEvidence.test.ts b/src/domain/reconstructionCoverageEvidence.test.ts index aea275a3d9..6b0212ecb0 100644 --- a/src/domain/reconstructionCoverageEvidence.test.ts +++ b/src/domain/reconstructionCoverageEvidence.test.ts @@ -4,12 +4,12 @@ import { createEvidence, type Evidence } from "./evidence.js"; import { createEvidenceBundle } from "./evidenceBundle.js"; import { completeReconstructionCoverageFixture, + assertReconstructionCoverageEvaluation, RECONSTRUCTION_COVERAGE_NOW, } from "./reconstructionCoverage.fixture.js"; import { createReconstructionCoverageData, createReconstructionVerifierContract, - evaluateReconstructionClosure, type ReconstructionCoverageData, } from "./reconstructionCoverage.js"; @@ -79,7 +79,7 @@ const withPackageProof = ( }; const evaluate = (coverage: ReconstructionCoverageData) => - evaluateReconstructionClosure( + assertReconstructionCoverageEvaluation( coverage, "replacement.cli", RECONSTRUCTION_COVERAGE_NOW, diff --git a/src/server/registerApplicationTools/coverage.ts b/src/server/registerApplicationTools/coverage.ts index 3b986da1ba..8957d170f1 100644 --- a/src/server/registerApplicationTools/coverage.ts +++ b/src/server/registerApplicationTools/coverage.ts @@ -4,7 +4,7 @@ import { runAdmittedToolOperation } from "../admittedToolOperation.js"; import { evaluateReconstructionCoverage } from "../../application/ReconstructionCoverageService.js"; import { applicationToolContract } from "../../contracts/applicationToolContracts.js"; import { logToolExecution } from "../toolLogging.js"; -import { toolRegistrationOptions } from "../toolRegistrationOptions.js"; +import { applicationToolRegistrationOptions } from "../toolRegistrationOptions.js"; import type { ApplicationToolRegistration } from "./types.js"; const contract = applicationToolContract("evaluate_reconstruction_coverage"); @@ -16,7 +16,7 @@ export const registerCoverageTools = ( ): void => { server.registerTool( contract.name, - toolRegistrationOptions(contract), + applicationToolRegistrationOptions(contract), async (input, context) => runAdmittedToolOperation( server, diff --git a/tests/boundary/cli/reconstructionCoverageCli.test.ts b/tests/boundary/cli/reconstructionCoverageCli.test.ts index 68d15e1fcf..618625fb74 100644 --- a/tests/boundary/cli/reconstructionCoverageCli.test.ts +++ b/tests/boundary/cli/reconstructionCoverageCli.test.ts @@ -3,6 +3,7 @@ import { join } from "node:path"; import { expect } from "vitest"; import { completeReconstructionCoverageData } from "../../../src/domain/reconstructionCoverage.fixture.js"; +import { parseMcpToolError } from "../../fixtures/mcpToolError.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { cliTest, type TestCli } from "../../support/cli/cliFixture.js"; @@ -65,3 +66,52 @@ cliTest( }); }, ); + +cliTest( + "preserves shared integrity diagnostics for malformed source Evidence", + async ({ cli }) => { + const coverage = currentCoverage(); + const malformed = { + ...coverage, + evidence_bundle: { + ...coverage.evidence_bundle, + records: coverage.evidence_bundle.records.map((record, index) => + index === 0 + ? { ...record, provider: { ...record.provider, name: 3 } } + : record, + ), + }, + }; + const call = await cli.sharedMcpCall?.(); + if (call === undefined) throw new Error("Expected file MCP session"); + const mcp = await call("evaluate_reconstruction_coverage", { + coverage: malformed, + boundary_id: "replacement.cli", + }); + expect(mcp.isError).toBe(true); + expect(parseMcpToolError(mcp)).toMatchObject({ + error: { code: "evidence_integrity_mismatch" }, + }); + const result = await evaluate(cli, malformed); + expect(result.exitCode).toBe(1); + expect(result.json).toMatchObject({ + code: "evidence_integrity_mismatch", + category: "integrity_mismatch", + }); + }, +); + +cliTest( + "locates malformed coverage through its shared workflow owner", + async ({ cli }) => { + const result = await evaluate(cli, { ...currentCoverage(), owners: 3 }); + expect(result.exitCode).toBe(1); + expect(result.json).toMatchObject({ + code: "invalid_request", + details: { + operation: "evaluate_reconstruction_coverage", + issues: [{ path: ["coverage", "owners"], reason: "invalid_type" }], + }, + }); + }, +); diff --git a/tests/boundary/mcp/reconstructionCoverageMcp.test.ts b/tests/boundary/mcp/reconstructionCoverageMcp.test.ts index 525049c1c5..5919c250a2 100644 --- a/tests/boundary/mcp/reconstructionCoverageMcp.test.ts +++ b/tests/boundary/mcp/reconstructionCoverageMcp.test.ts @@ -123,6 +123,42 @@ describe("reconstruction coverage inline Evidence", () => { }); describe("reconstruction coverage selection", () => { + it.each([ + { + label: "unknown root argument", + selection: { boundary_id: "replacement.cli", unexpected: true }, + path: ["unexpected"], + reason: "unknown_argument", + }, + { + label: "missing boundary", + selection: {}, + path: ["boundary_id"], + reason: "missing_argument", + }, + ])( + "rejects $label through the shared owner", + async ({ selection, path, reason }) => { + await withCoverageMcp(async ({ client, session }) => { + const result = await client.callTool({ + name: "evaluate_reconstruction_coverage", + arguments: { coverage: currentFixtureCoverage(), ...selection }, + }); + expect(result.isError).toBe(true); + expect(parseMcpToolError(result)).toMatchObject({ + error: { + code: "invalid_request", + details: { + operation: "evaluate_reconstruction_coverage", + issues: [{ path, reason }], + }, + }, + }); + expect(session.exportEvidenceBundle().records).toEqual([]); + }); + }, + ); + it("reports ambiguity when multiple verifier results cover one claim", async () => { await withCoverageMcp(async ({ client }) => { const coverage = currentFixtureCoverage();