mirror of
https://github.com/morluto/rea
synced 2026-10-26 14:25:49 -04:00
4e28166626
* fix(cancellation): preserve caller abort reason in cancellation errors Several application, provider, and MCP boundaries constructed AnalysisCancelledError, ArtifactReaderFailure, or DetachedPgpSignatureError for an already-observed or newly-observed abort without passing the caller's signal.reason as the error cause. The typed result then reported cancellation with an undefined reason even though the exact object supplied to AbortController.abort(reason) was still available. Pass signal.reason as the cause at each producing cancellation site: JavaScript artifact reconstruction and file abort checks, derived MCP operation checkpoints, detached PGP verification, ASAR listing admission, Dart AOT inspection and its provider guard, evidence-file analysis views, binary layout and recorded crash admission, WASM inspection admission, and the Kuna shared assembly-index wait. Cancellation classification and cleanup behavior are unchanged. Fixes morluto/rea#5935 Fixes morluto/rea#5944 Fixes morluto/rea#6100 Fixes morluto/rea#6101 Fixes morluto/rea#6110 Fixes morluto/rea#6113 Fixes morluto/rea#6121 Fixes morluto/rea#6131 Fixes morluto/rea#6132 * test(cancellation): assert the exact caller reason identity Copied reasons satisfied toMatchObject, so the tests would pass even if the caller's abort reason were replaced by a structurally equal copy. Capture the rejection and assert the cause is the same object. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 35b247e9-c793-4b8f-979a-807c27112cf5 --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 35b247e9-c793-4b8f-979a-807c27112cf5
127 lines
4.2 KiB
TypeScript
127 lines
4.2 KiB
TypeScript
import { isAbsolute } from "node:path";
|
|
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
|
import type { BinaryLayoutPort } from "./BinaryLayoutPort.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 {
|
|
inspectBinaryLayoutInputSchema,
|
|
binaryLayoutSchema,
|
|
} from "../../domain/native/binaryLayout.js";
|
|
import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js";
|
|
import { err, ok, type Result } from "../../domain/result.js";
|
|
import { completeProviderEvidence } from "../ProviderOperationExecution.js";
|
|
|
|
const OPERATION = "inspect_binary_layout";
|
|
|
|
/** Shared CLI/MCP validation and Evidence composition for offline layout inspection. */
|
|
export class BinaryLayoutService {
|
|
constructor(readonly provider: BinaryLayoutPort) {}
|
|
|
|
close(): Promise<void> {
|
|
return this.provider.close?.() ?? Promise.resolve();
|
|
}
|
|
|
|
/** Bind complete observations to the caller-selected artifact before creating Evidence. */
|
|
async inspect(
|
|
rawInput: unknown,
|
|
options?: ExecutionOptions,
|
|
): Promise<Result<Evidence, AnalysisError>> {
|
|
return this.#inspect(rawInput, options, (input) =>
|
|
this.provider.inspect(input, options),
|
|
);
|
|
}
|
|
|
|
/** Complete the owned one-shot lifecycle before validating and publishing Evidence. */
|
|
inspectAndClose(
|
|
rawInput: unknown,
|
|
options?: ExecutionOptions,
|
|
): Promise<Result<Evidence, AnalysisError>> {
|
|
return this.#inspect(rawInput, options, (input) =>
|
|
this.provider.inspectAndClose(input, options),
|
|
);
|
|
}
|
|
|
|
async #inspect(
|
|
rawInput: unknown,
|
|
options: ExecutionOptions | undefined,
|
|
inspect: (
|
|
input: Parameters<BinaryLayoutPort["inspect"]>[0],
|
|
) => ReturnType<BinaryLayoutPort["inspect"]>,
|
|
): Promise<Result<Evidence, AnalysisError>> {
|
|
if (options?.signal?.aborted)
|
|
return err(
|
|
new AnalysisCancelledError(OPERATION, {
|
|
cause: options.signal.reason,
|
|
}),
|
|
);
|
|
const input = inspectBinaryLayoutInputSchema.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 inspect(input.data);
|
|
if (!inspected.ok) return inspected;
|
|
const diagnostics = binaryLayoutSchema.shape.diagnostics.safeParse(
|
|
inspected.value.diagnostics,
|
|
);
|
|
const outputOptions = diagnostics.success
|
|
? { capturedOutput: diagnostics.data }
|
|
: undefined;
|
|
const report = binaryLayoutSchema.safeParse(inspected.value);
|
|
if (!report.success || report.data.artifact.path !== input.data.path)
|
|
return err(
|
|
new AnalysisOutputError(
|
|
OPERATION,
|
|
"Binary layout provider returned malformed evidence or changed the selected artifact identity.",
|
|
outputOptions,
|
|
),
|
|
);
|
|
const value = report.data;
|
|
return completeProviderEvidence(
|
|
createEvidence(
|
|
{
|
|
path: value.artifact.path,
|
|
format: "elf",
|
|
architecture: "x86_64",
|
|
sha256: value.artifact.sha256,
|
|
},
|
|
this.provider.identity,
|
|
{
|
|
operation: OPERATION,
|
|
parameters: {
|
|
path: input.data.path,
|
|
...(input.data.pwntools_timeout_ms === undefined
|
|
? {}
|
|
: { pwntools_timeout_ms: input.data.pwntools_timeout_ms }),
|
|
},
|
|
result: value,
|
|
// The decoded report is the only representation; do not repeat it.
|
|
rawResult: null,
|
|
confidence: "observed",
|
|
limitations: value.limitations,
|
|
locations: [{ kind: "artifact-path", path: value.artifact.path }],
|
|
},
|
|
),
|
|
options?.signal,
|
|
outputOptions?.capturedOutput,
|
|
);
|
|
}
|
|
}
|