mirror of
https://github.com/morluto/rea.git
synced 2026-10-31 20:06:14 +00:00
Refs #4502, #4506, #4508, #4518, #4522, #4526. Retain snapshot, endpoint, launch rollback and compiler cleanup owners. Join request finalization and cleanup cancellation. Preserve bounded compiler output and original byte counts through capture and Ghidra failures. Normalize execFile cancellation once at its producer. Validation includes real macOS Ghidra TCP MCP success workflows, controlled Node compiler/process fault fixtures, focused regressions and static checks. Native Swift compiler faults, Windows execution and stdio transport were not verified. Broader startup primary classification in #4517 and native adapter propagation remain unfinished.
148 lines
5.5 KiB
TypeScript
148 lines
5.5 KiB
TypeScript
import { DarwinProcessOwnershipInspectionError } from "../DarwinProcessRunTokenReader.js";
|
|
import {
|
|
AnalysisError,
|
|
type AnalysisErrorOptions,
|
|
} from "../../domain/analysisErrorBase.js";
|
|
import type {
|
|
PartialProcessCaptureObservation,
|
|
ProcessCaptureCleanupReport,
|
|
} from "../../domain/process/processCapture.js";
|
|
|
|
interface ProcessCaptureErrorOptions extends AnalysisErrorOptions {
|
|
readonly userMessage?: string;
|
|
readonly userCategory?: "cancelled";
|
|
readonly reason?: "capture_failed" | "cleanup_incomplete" | "cancelled";
|
|
readonly executionFailure?: string;
|
|
readonly partialObservation?: PartialProcessCaptureObservation;
|
|
readonly cleanupReport?: ProcessCaptureCleanupReport;
|
|
}
|
|
|
|
/** Typed application failure produced by controlled process capture. */
|
|
export class ProcessCaptureError extends AnalysisError {
|
|
readonly _tag = "ProcessCaptureError";
|
|
|
|
override readonly userMessage: string | undefined;
|
|
override readonly userCategory: "cancelled" | undefined;
|
|
readonly reason: NonNullable<ProcessCaptureErrorOptions["reason"]>;
|
|
override readonly executionFailure: string | undefined;
|
|
override readonly partialObservation:
|
|
| PartialProcessCaptureObservation
|
|
| undefined;
|
|
override readonly cleanupReport: ProcessCaptureCleanupReport | undefined;
|
|
|
|
constructor(message: string, options?: ProcessCaptureErrorOptions) {
|
|
super(message, options);
|
|
this.userMessage = options?.userMessage ?? message;
|
|
this.userCategory = options?.userCategory;
|
|
this.reason =
|
|
options?.reason ??
|
|
(options?.userCategory === "cancelled" ? "cancelled" : "capture_failed");
|
|
this.cleanupIncomplete ||= this.reason === "cleanup_incomplete";
|
|
this.executionFailure = options?.executionFailure ?? message;
|
|
this.partialObservation = options?.partialObservation;
|
|
this.cleanupReport = options?.cleanupReport;
|
|
}
|
|
|
|
/** Add final capture observations while preserving primary facts and cleanup ownership. */
|
|
withFinalization(
|
|
options: Pick<
|
|
ProcessCaptureErrorOptions,
|
|
"executionFailure" | "partialObservation" | "cleanupReport"
|
|
> & { readonly cleanupFailure?: AnalysisError },
|
|
): ProcessCaptureError {
|
|
const secondary = options.cleanupFailure;
|
|
const resources = [
|
|
...new Set([
|
|
...this.cleanupResources,
|
|
...(secondary?.cleanupResources ?? []),
|
|
]),
|
|
];
|
|
const executionFailure = this.executionFailure ?? options.executionFailure;
|
|
const partialObservation =
|
|
this.partialObservation ?? options.partialObservation;
|
|
const cleanupReport = this.cleanupReport ?? options.cleanupReport;
|
|
return new ProcessCaptureError(this.message, {
|
|
...(Object.hasOwn(this, "cause") ? { cause: this.cause } : {}),
|
|
...(this.userMessage === undefined
|
|
? {}
|
|
: { userMessage: this.userMessage }),
|
|
...(this.userCategory === undefined
|
|
? {}
|
|
: { userCategory: this.userCategory }),
|
|
reason: this.reason,
|
|
...(this.diagnostics === undefined
|
|
? {}
|
|
: { diagnostics: this.diagnostics }),
|
|
...(this.capturedOutput === undefined
|
|
? {}
|
|
: { capturedOutput: this.capturedOutput }),
|
|
...(secondary === undefined
|
|
? this.cleanup === undefined
|
|
? {}
|
|
: { cleanup: this.cleanup }
|
|
: { cleanup: { reason: secondary.message, resources } }),
|
|
...(executionFailure === undefined ? {} : { executionFailure }),
|
|
...(partialObservation === undefined ? {} : { partialObservation }),
|
|
...(cleanupReport === undefined ? {} : { cleanupReport }),
|
|
}).bindCleanupDependencies([
|
|
this,
|
|
...(secondary === undefined ? [] : [secondary]),
|
|
]);
|
|
}
|
|
}
|
|
|
|
/** Return one non-recursive, domain-neutral summary of an execution failure. */
|
|
export const describeProcessCaptureExecutionFailure = (
|
|
failure: unknown,
|
|
): string | undefined => {
|
|
if (failure instanceof Error) return failure.message || failure.name;
|
|
if (typeof failure === "string") return failure;
|
|
if (failure === undefined) return undefined;
|
|
return "Process capture failed with a non-error rejection.";
|
|
};
|
|
|
|
/** Caller-visible cancellation without exposing capture implementation state. */
|
|
export const processCaptureCancelled = (): ProcessCaptureError =>
|
|
new ProcessCaptureError("process capture was cancelled", {
|
|
userCategory: "cancelled",
|
|
reason: "cancelled",
|
|
userMessage: "Process capture was cancelled. Start it again when ready.",
|
|
});
|
|
|
|
/** Preserve typed process-inspection preflight failures at the capture boundary. */
|
|
export const normalizeCaptureFailure = (
|
|
cause: unknown,
|
|
signal: AbortSignal | undefined,
|
|
): unknown => {
|
|
if (cause instanceof ProcessCaptureError) return cause;
|
|
if (cause instanceof DarwinProcessOwnershipInspectionError) {
|
|
const normalized = new ProcessCaptureError(cause.message, {
|
|
cause,
|
|
...(cause.cancellation === undefined
|
|
? {}
|
|
: { userCategory: "cancelled" as const, reason: "cancelled" as const }),
|
|
...(cause.cleanupFailure === undefined
|
|
? {}
|
|
: {
|
|
cleanup: {
|
|
reason: cause.cleanupFailure.message,
|
|
resources: cause.cleanupFailure.cleanupResources,
|
|
},
|
|
}),
|
|
...(cause.capturedOutput === undefined
|
|
? {}
|
|
: { capturedOutput: cause.capturedOutput }),
|
|
});
|
|
return cause.cleanupFailure === undefined
|
|
? normalized
|
|
: normalized.bindCleanupDependencies([cause.cleanupFailure]);
|
|
}
|
|
if (
|
|
signal?.aborted === true &&
|
|
(cause === signal.reason ||
|
|
(cause instanceof Error && cause.name === "AbortError"))
|
|
)
|
|
return processCaptureCancelled();
|
|
return cause;
|
|
};
|