diff --git a/src/application/InvestigationProviders.ts b/src/application/InvestigationProviders.ts index 510111d929..0758d86dbe 100644 --- a/src/application/InvestigationProviders.ts +++ b/src/application/InvestigationProviders.ts @@ -3,7 +3,6 @@ import { createAnalysisProfile, type AnalysisProfileCommitment, } from "../domain/analysisProfile.js"; - /** Provider identity for deterministic composed analysis workflows. */ export const REA_WORKFLOW_PROVIDER = { id: "rea-workflow", @@ -112,20 +111,6 @@ export const JAVASCRIPT_APPLICATION_WORKFLOW_PROVIDER = { version: "1", } as const; -/** Provider identity for deterministic cross-artifact comparisons. */ -export const ARTIFACT_COMPARISON_PROVIDER = { - id: "rea-artifact-comparison", - name: "REA artifact comparison", - version: "1", -} as const; - -/** Provider identity for deterministic cross-function comparisons. */ -export const FUNCTION_COMPARISON_PROVIDER = { - id: "rea-function-comparison", - name: "REA function comparison", - version: "1", -} as const; - /** Provider identity for deterministic Evidence bundle comparisons. */ export const BUNDLE_COMPARISON_PROVIDER = { id: "rea-bundle-comparison", diff --git a/src/application/native/NativeFunctionMatchingService.ts b/src/application/native/NativeFunctionMatchingService.ts index deff5d57d5..c99d2a51b2 100644 --- a/src/application/native/NativeFunctionMatchingService.ts +++ b/src/application/native/NativeFunctionMatchingService.ts @@ -25,7 +25,7 @@ import { type EvidenceLookup, } from "../EvidenceInputResolver.js"; import type { ExecutionOptions } from "../AnalysisProvider.js"; -import { FUNCTION_COMPARISON_PROVIDER } from "../InvestigationProviders.js"; +import { FUNCTION_COMPARISON_PROVIDER } from "../../domain/comparisonEvidenceIdentity.js"; const OPERATION = "match_native_functions"; export interface NativeFunctionMatchingResult { diff --git a/src/application/wasm/WasmInterfaceComparisonService.ts b/src/application/wasm/WasmInterfaceComparisonService.ts index 32e1c80865..d25a9f1edf 100644 --- a/src/application/wasm/WasmInterfaceComparisonService.ts +++ b/src/application/wasm/WasmInterfaceComparisonService.ts @@ -3,6 +3,7 @@ import { wasmInterfaceEntryRepresentationBytes, } from "../../domain/wasm/wasmInterface.js"; import { setImmediate } from "node:timers/promises"; +import { ARTIFACT_COMPARISON_PROVIDER } from "../../domain/comparisonEvidenceIdentity.js"; import { compareWasmInterfacesInputSchema, wasmInterfaceComparisonSchema, @@ -26,7 +27,6 @@ import { fixedAllowanceOptions } from "../../domain/fixedAllowanceRemediation.js import { AnalysisError } from "../../domain/analysisErrorBase.js"; import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js"; import { err, ok, type Result } from "../../domain/result.js"; -import { ARTIFACT_COMPARISON_PROVIDER } from "../InvestigationProviders.js"; import { resolvePairedEvidenceRequest, type EvidenceLookup, diff --git a/src/cli/nativeFunctionMatchingCommands.ts b/src/cli/nativeFunctionMatchingCommands.ts index cc43069bd6..20e656024c 100644 --- a/src/cli/nativeFunctionMatchingCommands.ts +++ b/src/cli/nativeFunctionMatchingCommands.ts @@ -3,6 +3,7 @@ import type { Logger } from "pino"; import { matchNativeFunctionsFromBundles } from "../application/native/NativeFunctionMatchingService.js"; import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js"; import { matchNativeFunctionsInputSchema } from "../contracts/native/functionMatchingContracts.js"; +import { FUNCTION_COMPARISON_PROVIDER } from "../domain/comparisonEvidenceIdentity.js"; import { CLI_COMMANDS } from "../cliCommandNames.js"; import { parseCliJsonInput } from "../cliJsonInput.js"; import { logCliCommand } from "../cliLogging.js"; @@ -66,7 +67,7 @@ export const registerNativeFunctionMatchingCommands = ( ), ); const delivery = { - providerId: "rea-function-comparison", + providerId: FUNCTION_COMPARISON_PROVIDER.id, operation: "match_native_functions", signal, detail: parsed.data.detail, diff --git a/src/contracts/investigationExamples.ts b/src/contracts/investigationExamples.ts index 96258225c9..3ed326f7a7 100644 --- a/src/contracts/investigationExamples.ts +++ b/src/contracts/investigationExamples.ts @@ -1,5 +1,6 @@ import { createEvidence } from "../domain/evidence.js"; import { jsonValueSchema } from "../domain/jsonValue.js"; +import { FUNCTION_COMPARISON_PROVIDER } from "../domain/comparisonEvidenceIdentity.js"; import { compareProcessCaptures } from "../domain/process/processComparison.js"; import { parseProcessCapture } from "../domain/process/processCaptureParsing.js"; import { FUNCTION_COMPARISON_EXAMPLE } from "./functionComparisonExample.js"; @@ -13,11 +14,7 @@ const comparison = compareFunctions( export const FUNCTION_COMPARISON_EVIDENCE = createEvidence( undefined, - { - id: "rea-function-comparison", - name: "REA function comparison", - version: "1", - }, + FUNCTION_COMPARISON_PROVIDER, { predicateType: "rea.function-comparison", operation: "compare_functions", diff --git a/src/domain/changedBehavior.ts b/src/domain/changedBehavior.ts index 3d4062e835..399b63d4c8 100644 --- a/src/domain/changedBehavior.ts +++ b/src/domain/changedBehavior.ts @@ -11,11 +11,11 @@ import { comparisonSourceEvidenceIds, } from "./comparisonSourceEvidence.js"; import { evidenceSchema, parseEvidence, type Evidence } from "./evidence.js"; -import { functionComparisonResultSchema } from "./functionComparisonSchemas.js"; import { - PROCESS_PROVIDER, - isProcessEvidenceProvider, -} from "./process/processEvidenceProvider.js"; + hasComparisonEvidenceIdentity, + isComparisonEvidenceOperation, +} from "./comparisonEvidenceIdentity.js"; +import { functionComparisonResultSchema } from "./functionComparisonSchemas.js"; import { comparisonStatusSchema, deriveProcessComparisonStatus, @@ -111,30 +111,6 @@ type ParsedComparison = readonly result: z.infer; }; -const EXPECTED_PROVIDERS = { - compare_process_captures: { - predicates: ["rea.process-comparison"], - ...PROCESS_PROVIDER, - }, - compare_artifacts: { - predicates: ["rea.artifact-comparison"], - id: "rea-artifact-comparison", - name: "REA artifact comparison", - version: "1", - }, - compare_functions: { - predicates: ["rea.function-comparison"], - id: "rea-function-comparison", - name: "REA function comparison", - version: "1", - }, -} as const; - -const acceptsPredicate = ( - expected: readonly string[], - actual: string, -): boolean => expected.some((predicate) => predicate === actual); - /** Find observed runtime changes and separately report static change candidates. */ interface FoundChangedBehavior { readonly result: ChangedBehaviorResult; @@ -202,29 +178,12 @@ export const findChangedBehavior = (input: unknown): FoundChangedBehavior => { const parseComparisonEvidence = (input: unknown): ParsedComparison => { const evidence = parseEvidence(input); - if (!(evidence.operation in EXPECTED_PROVIDERS)) + if (!isComparisonEvidenceOperation(evidence.operation)) throw new TypeError( "Changed-behavior analysis requires process, artifact, or function comparison Evidence", ); - const operation = z - .enum([ - "compare_process_captures", - "compare_artifacts", - "compare_functions", - ]) - .parse(evidence.operation); - const expected = EXPECTED_PROVIDERS[operation]; - if ( - !acceptsPredicate(expected.predicates, evidence.predicate_type) || - (operation === "compare_process_captures" - ? !isProcessEvidenceProvider(evidence.provider) - : evidence.provider.id !== expected.id || - evidence.provider.name !== expected.name || - evidence.provider.version !== expected.version) || - evidence.confidence !== "derived" || - evidence.authority !== "analyst-inference" || - evidence.subject !== null - ) + const operation = evidence.operation; + if (!hasComparisonEvidenceIdentity(evidence)) throw new TypeError( "Comparison Evidence operation, predicate, and provider disagree", ); diff --git a/src/domain/comparisonEvidenceIdentity.ts b/src/domain/comparisonEvidenceIdentity.ts new file mode 100644 index 0000000000..cb9fd7fc62 --- /dev/null +++ b/src/domain/comparisonEvidenceIdentity.ts @@ -0,0 +1,64 @@ +import type { Evidence } from "./evidence.js"; +import { + PROCESS_PROVIDER, + isProcessEvidenceProvider, +} from "./process/processEvidenceProvider.js"; + +/** Canonical provider identity for deterministic artifact comparisons. */ +export const ARTIFACT_COMPARISON_PROVIDER = { + id: "rea-artifact-comparison", + name: "REA artifact comparison", + version: "1", +} as const; + +/** Canonical provider identity for deterministic function comparisons. */ +export const FUNCTION_COMPARISON_PROVIDER = { + id: "rea-function-comparison", + name: "REA function comparison", + version: "1", +} as const; + +const COMPARISON_EVIDENCE_IDENTITIES = { + compare_artifacts: { + predicate: "rea.artifact-comparison", + provider: ARTIFACT_COMPARISON_PROVIDER, + }, + compare_functions: { + predicate: "rea.function-comparison", + provider: FUNCTION_COMPARISON_PROVIDER, + }, + compare_process_captures: { + predicate: "rea.process-comparison", + provider: PROCESS_PROVIDER, + }, +} as const; + +type ComparisonEvidenceOperation = + keyof typeof COMPARISON_EVIDENCE_IDENTITIES; + +/** Whether an operation is one of the comparison workflows this domain owns. */ +export const isComparisonEvidenceOperation = ( + operation: string, +): operation is ComparisonEvidenceOperation => + Object.hasOwn(COMPARISON_EVIDENCE_IDENTITIES, operation); + +/** Check shared Evidence identity without imposing a consumer's link policy. */ +export const hasComparisonEvidenceIdentity = ( + evidence: Evidence, +): boolean => { + if (!isComparisonEvidenceOperation(evidence.operation)) return false; + const expected = COMPARISON_EVIDENCE_IDENTITIES[evidence.operation]; + const providerMatches = + evidence.operation === "compare_process_captures" + ? isProcessEvidenceProvider(evidence.provider) + : evidence.provider.id === expected.provider.id && + evidence.provider.name === expected.provider.name && + evidence.provider.version === expected.provider.version; + return ( + evidence.predicate_type === expected.predicate && + providerMatches && + evidence.confidence === "derived" && + evidence.authority === "analyst-inference" && + evidence.subject === null + ); +}; diff --git a/src/domain/reconstructionVerification.ts b/src/domain/reconstructionVerification.ts index d59e9a1205..59a456424f 100644 --- a/src/domain/reconstructionVerification.ts +++ b/src/domain/reconstructionVerification.ts @@ -12,12 +12,9 @@ import { assertComparisonFindingLinks, } from "./comparisonSourceEvidence.js"; import { parseEvidence, type Evidence } from "./evidence.js"; +import { hasComparisonEvidenceIdentity } from "./comparisonEvidenceIdentity.js"; import { parseEvidenceBundle } from "./evidenceBundle.js"; import { functionComparisonResultSchema } from "./functionComparisonSchemas.js"; -import { - PROCESS_PROVIDER, - isProcessEvidenceProvider, -} from "./process/processEvidenceProvider.js"; import { functionDossierSchema } from "./hopperValues.js"; import { assessFunctionCallCoverage } from "./functionCallCoverage.js"; import { processCaptureComparisonSchema } from "./process/processComparison.js"; @@ -38,24 +35,10 @@ import { } from "./reconstructionUnknowns.js"; import type { ResidualUnknown } from "./residualUnknown.js"; -const providers = { - behavioral: { - operation: "compare_process_captures", - predicates: ["rea.process-comparison"], - ...PROCESS_PROVIDER, - }, - "structural-function": { - operation: "compare_functions", - predicates: ["rea.function-comparison"], - id: "rea-function-comparison", - name: "REA function comparison", - }, - "structural-artifact": { - operation: "compare_artifacts", - predicates: ["rea.artifact-comparison"], - id: "rea-artifact-comparison", - name: "REA artifact comparison", - }, +const comparisonOperationsByClaimKind = { + behavioral: "compare_process_captures", + "structural-function": "compare_functions", + "structural-artifact": "compare_artifacts", } as const; interface FunctionSourceFacts { @@ -64,11 +47,6 @@ interface FunctionSourceFacts { readonly callCoverageDiagnostics: readonly string[]; } -const acceptsPredicate = ( - expected: readonly string[], - actual: string, -): boolean => expected.some((predicate) => predicate === actual); - /** Verify only explicitly declared claims; no global equivalence is inferred. */ export const verifyReconstruction = ( specificationInput: unknown, @@ -234,18 +212,10 @@ const readValidatedSourceFacts = ( const validateComparisonIdentity = (claim: Claim, evidence: Evidence): void => { parseEvidence(evidence); - const expected = providers[claim.kind]; + const expectedOperation = comparisonOperationsByClaimKind[claim.kind]; if ( - evidence.operation !== expected.operation || - !acceptsPredicate(expected.predicates, evidence.predicate_type) || - (claim.kind === "behavioral" - ? !isProcessEvidenceProvider(evidence.provider) - : evidence.provider.id !== expected.id || - evidence.provider.name !== expected.name || - evidence.provider.version !== "1") || - evidence.confidence !== "derived" || - evidence.authority !== "analyst-inference" || - evidence.subject !== null + evidence.operation !== expectedOperation || + !hasComparisonEvidenceIdentity(evidence) ) throw new TypeError("Comparison Evidence identity or authority disagrees"); }; diff --git a/src/domain/staticRuntimeCorrelation.ts b/src/domain/staticRuntimeCorrelation.ts index 0362569a7f..94a8f08311 100644 --- a/src/domain/staticRuntimeCorrelation.ts +++ b/src/domain/staticRuntimeCorrelation.ts @@ -11,11 +11,8 @@ import { assertComparisonFindingLinks, } from "./comparisonSourceEvidence.js"; import { evidenceSchema, parseEvidence, type Evidence } from "./evidence.js"; +import { hasComparisonEvidenceIdentity } from "./comparisonEvidenceIdentity.js"; import { functionComparisonResultSchema } from "./functionComparisonSchemas.js"; -import { - PROCESS_PROVIDER, - isProcessEvidenceProvider, -} from "./process/processEvidenceProvider.js"; import type { JsonValue } from "./jsonValue.js"; import { comparisonStatusSchema, @@ -145,26 +142,6 @@ type Mapping = z.infer; type CorrelationItem = z.infer; type ComparisonStatus = z.infer; -type ComparisonIdentity = readonly [string | readonly string[], string, string]; - -const EXPECTED: Readonly> = { - compare_artifacts: [ - "rea.artifact-comparison", - "rea-artifact-comparison", - "REA artifact comparison", - ], - compare_functions: [ - "rea.function-comparison", - "rea-function-comparison", - "REA function comparison", - ], - compare_process_captures: [ - "rea.process-comparison", - PROCESS_PROVIDER.id, - PROCESS_PROVIDER.name, - ], -}; - /** Correlate only caller-declared static/runtime hypotheses. */ export const correlateStaticAndRuntime = ( input: unknown, @@ -381,21 +358,7 @@ const parseRuntime = (input: unknown): Evidence => { }; const assertComparisonIdentity = (evidence: Evidence): void => { - const expected = EXPECTED[evidence.operation]; - if ( - expected === undefined || - !(Array.isArray(expected[0]) - ? expected[0].includes(evidence.predicate_type) - : evidence.predicate_type === expected[0]) || - (evidence.operation === "compare_process_captures" - ? !isProcessEvidenceProvider(evidence.provider) - : evidence.provider.id !== expected[1] || - evidence.provider.name !== expected[2] || - evidence.provider.version !== "1") || - evidence.confidence !== "derived" || - evidence.authority !== "analyst-inference" || - evidence.subject !== null - ) + if (!hasComparisonEvidenceIdentity(evidence)) throw new TypeError( "Comparison Evidence operation, predicate, provider, or citations disagree", ); diff --git a/src/server/registerArtifactComparisonTool.ts b/src/server/registerArtifactComparisonTool.ts index 3e0661f7c4..9f62daec11 100644 --- a/src/server/registerArtifactComparisonTool.ts +++ b/src/server/registerArtifactComparisonTool.ts @@ -3,16 +3,17 @@ import type { EvidenceMcpServer } from "./EvidenceMcpServer.js"; import type { BinarySessionPort } from "../application/binary/BinarySessionPort.js"; import { toolContract } from "../contracts/toolContracts.js"; import { compareArtifacts } from "../domain/artifactComparison.js"; +import { ARTIFACT_COMPARISON_PROVIDER } from "../domain/comparisonEvidenceIdentity.js"; import { createEvidence, parseEvidence } from "../domain/evidence.js"; import { jsonValueSchema } from "../domain/jsonValue.js"; import { resolveArtifactComparisonEvidence } from "../application/artifacts/ArtifactComparisonEvidence.js"; import type { RecordUnknownInput } from "../domain/residualUnknown.js"; import { recordDerivedEvidence } from "./recordDerivedEvidence.js"; import { runDerivedOperation } from "./runDerivedOperation.js"; -import { ARTIFACT_COMPARISON_PROVIDER } from "../application/InvestigationProviders.js"; import { toolRegistrationOptions } from "./toolRegistrationOptions.js"; import { withAdmittedAnalysis } from "./analysisAdmission.js"; import { runAdmittedToolOperation } from "./admittedToolOperation.js"; +import { recordSessionEvidenceSources } from "./sessionEvidence.js"; /** Register Evidence-backed deterministic artifact comparison. */ export const registerArtifactComparisonTool = ( @@ -58,12 +59,12 @@ export const registerArtifactComparisonTool = ( if (!computed.ok) return server.delivery.toCallToolResult(computed, contract); const { left, right, comparison } = computed.value; - const sources = [left, right]; - for (const source of sources) { - const recordedSource = session.recordEvidence(source); - if (!recordedSource.ok) - return server.delivery.toCallToolResult(recordedSource, contract); - } + const recordedSources = recordSessionEvidenceSources( + (source) => session.recordEvidence(source), + [left, right], + ); + if (!recordedSources.ok) + return server.delivery.toCallToolResult(recordedSources, contract); const leftEvidenceIds = [left.evidence_id]; const rightEvidenceIds = [right.evidence_id]; const evidence = createEvidence( diff --git a/src/server/registerFunctionComparisonTool.ts b/src/server/registerFunctionComparisonTool.ts index 15e14d5cdd..c20eb92c08 100644 --- a/src/server/registerFunctionComparisonTool.ts +++ b/src/server/registerFunctionComparisonTool.ts @@ -8,6 +8,7 @@ import { parseEvidence, type Evidence, } from "../domain/evidence.js"; +import { FUNCTION_COMPARISON_PROVIDER } from "../domain/comparisonEvidenceIdentity.js"; import { EvidenceIntegrityError } from "../domain/evidenceErrors.js"; import { err } from "../domain/result.js"; import { jsonValueSchema } from "../domain/jsonValue.js"; @@ -15,7 +16,6 @@ import type { RecordUnknownInput } from "../domain/residualUnknown.js"; import { recordDerivedEvidence } from "./recordDerivedEvidence.js"; import { recordSessionEvidenceSources } from "./sessionEvidence.js"; import { runDerivedOperation } from "./runDerivedOperation.js"; -import { FUNCTION_COMPARISON_PROVIDER } from "../application/InvestigationProviders.js"; import { toolRegistrationOptions } from "./toolRegistrationOptions.js"; import { withAdmittedAnalysis } from "./analysisAdmission.js"; import { runAdmittedToolOperation } from "./admittedToolOperation.js";