Add a read-only app resource session shared by CLI and MCP. Preserve resource-root provenance without inventing executable identity, reject binary-only snapshots and operations, and retain failed cleanup owners across switches and retries. Keep filesystem admission failures typed and preserve cancellation ordering from observed cleanup facts. Document resource routing in the packaged investigation skill. BREAKING CHANGE: resource-only observations may have a null Evidence subject and null executable target digests. Clients must use resource_root and reported per-resource provenance instead. Refs #5589
70 lines
2.5 KiB
TypeScript
70 lines
2.5 KiB
TypeScript
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
|
import type { FirmwareAnalysisPort } from "./FirmwareAnalysisPort.js";
|
|
import {
|
|
firmwareRequestSchema,
|
|
type FirmwareOperation,
|
|
} from "../../domain/firmware/firmwareAnalysis.js";
|
|
import {
|
|
AnalysisCancelledError,
|
|
AnalysisInputError,
|
|
AnalysisOutputError,
|
|
} from "../../domain/analysisErrorCore.js";
|
|
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
|
import { createEvidence, type Evidence } from "../../domain/evidence.js";
|
|
import { jsonObjectSchema } from "../../domain/jsonValue.js";
|
|
import { projectInputIssues } from "../../domain/inputIssueProjection.js";
|
|
import { err, type Result } from "../../domain/result.js";
|
|
import { completeProviderEvidence } from "../ProviderOperationExecution.js";
|
|
|
|
/** Shared CLI/MCP validation and Evidence composition for arbitrary firmware files. */
|
|
export class FirmwareAnalysisService {
|
|
constructor(readonly provider: FirmwareAnalysisPort) {}
|
|
|
|
close(): Promise<void> {
|
|
return this.provider.close?.() ?? Promise.resolve();
|
|
}
|
|
|
|
/** Execute an explicit inspection or extraction and preserve its producing identity. */
|
|
async execute(
|
|
operation: FirmwareOperation,
|
|
input: unknown,
|
|
options?: ExecutionOptions,
|
|
): Promise<Result<Evidence, AnalysisError>> {
|
|
if (options?.signal?.aborted === true)
|
|
return err(
|
|
new AnalysisCancelledError(operation, { cause: options.signal.reason }),
|
|
);
|
|
const parsed = firmwareRequestSchema.safeParse({ operation, input });
|
|
if (!parsed.success)
|
|
return err(
|
|
new AnalysisInputError(
|
|
operation,
|
|
{ cause: parsed.error },
|
|
projectInputIssues(parsed.error.issues, { operation, input }),
|
|
),
|
|
);
|
|
const executed = await this.provider.execute(parsed.data, options);
|
|
if (!executed.ok) return executed;
|
|
const execution = executed.value;
|
|
if (execution.subject == null)
|
|
return err(
|
|
new AnalysisOutputError(
|
|
operation,
|
|
"Firmware execution did not bind its source artifact",
|
|
),
|
|
);
|
|
return completeProviderEvidence(
|
|
createEvidence(execution.subject, execution.provider, {
|
|
operation,
|
|
parameters: jsonObjectSchema.parse(parsed.data.input),
|
|
result: execution.result,
|
|
rawResult: execution.rawResult,
|
|
locations: execution.locations,
|
|
limitations: execution.limitations,
|
|
confidence: "observed",
|
|
}),
|
|
options?.signal,
|
|
);
|
|
}
|
|
}
|