Files
rea/src/application/evm/EvmInterfaceService.ts
T
morluto 3b698645a0 fix(evidence): retain completed observations on late cancellation
Validate completed reports before cancellation projection, retain valid
Evidence and captured command diagnostics, and convert raw cleanup partials
at their application owner. Record partial Evidence consistently across MCP
handlers. Keep page inspection separate from unfinished bundle analysis.

Refs #4261
2026-10-16 06:46:25 +08:00

100 lines
3.4 KiB
TypeScript

import { isAbsolute } from "node:path";
import { z } from "zod";
import type { ExecutionOptions } from "../AnalysisProvider.js";
import type { EvmInterfacePort } from "./EvmInterfacePort.js";
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
import {
AnalysisCancelledError,
AnalysisInputError,
AnalysisOutputError,
} from "../../domain/analysisErrorCore.js";
import { createEvidence, type Evidence } from "../../domain/evidence.js";
import {
inspectEvmInterfaceInputSchema,
evmInterfaceSchema,
} from "../../domain/evm/evmInterface.js";
import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js";
import { err, ok, type Result } from "../../domain/result.js";
import { completeProviderEvidence } from "../ProviderOperationExecution.js";
const OPERATION = "inspect_evm_interface";
const diagnosticsSchema = z.object({
diagnostics: evmInterfaceSchema.shape.diagnostics,
});
/** Shared carrier validation and typed inline Evidence for CLI/MCP. */
export class EvmInterfaceService {
constructor(readonly provider: EvmInterfacePort) {}
close(): Promise<void> {
return this.provider.close?.() ?? Promise.resolve();
}
/** Preserve selected byte identity while clearly labeling recovered ABI candidates as inferences. */
async inspect(
rawInput: unknown,
options?: ExecutionOptions,
): Promise<Result<Evidence, AnalysisError>> {
if (options?.signal?.aborted)
return err(new AnalysisCancelledError(OPERATION));
const input = inspectEvmInterfaceInputSchema.safeParse(rawInput);
if (!input.success)
return err(
analysisInputErrorFromIssues(OPERATION, input.error.issues, rawInput, {
cause: input.error,
}),
);
if (!isAbsolute(input.data.path))
return err(
new AnalysisInputError(OPERATION, undefined, [
{
path: ["path"],
reason: "invalid_format",
message: "Expected an absolute filesystem path on this host.",
},
]),
);
const inspected = await this.provider.inspect(input.data, options);
if (!inspected.ok) return inspected;
const diagnostics = diagnosticsSchema.safeParse(inspected.value.result);
const outputOptions = diagnostics.success
? { capturedOutput: diagnostics.data.diagnostics }
: undefined;
const execution = inspected.value;
const report = evmInterfaceSchema.safeParse(execution.result);
if (
!report.success ||
report.data.artifact.path !== input.data.path ||
report.data.artifact.encoding !== input.data.encoding ||
execution.subject?.format !== "file" ||
execution.subject?.path !== input.data.path ||
execution.subject.sha256 !== report.data.artifact.sha256
)
return err(
new AnalysisOutputError(
OPERATION,
"Interface adapter returned malformed data or changed the selected carrier identity/encoding.",
outputOptions,
),
);
return completeProviderEvidence(
ok(
createEvidence(execution.subject, execution.provider, {
operation: OPERATION,
parameters: input.data,
result: report.data,
...(execution.rawResult === null
? {}
: { rawResult: execution.rawResult }),
confidence: "inferred",
locations: execution.locations,
limitations: execution.limitations,
}),
),
OPERATION,
options,
outputOptions,
);
}
}