mirror of
https://github.com/morluto/rea.git
synced 2026-10-29 02:45:58 +00:00
151 lines
5.6 KiB
TypeScript
151 lines
5.6 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;
|
|
const finalized = new ProcessCaptureError(this.message, {
|
|
cleanupPrimary: this,
|
|
...(Object.hasOwn(this, "cause") ? { cause: this.cause } : {}),
|
|
...(this.userMessage === undefined
|
|
? {}
|
|
: { userMessage: this.userMessage }),
|
|
...(this.userCategory === undefined
|
|
? {}
|
|
: { userCategory: this.userCategory }),
|
|
reason: this.reason,
|
|
...(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]),
|
|
]);
|
|
if (secondary !== undefined) finalized.retainSecondaryFailure(secondary);
|
|
return finalized;
|
|
}
|
|
}
|
|
|
|
/** 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 = (
|
|
reason?: unknown,
|
|
): ProcessCaptureError =>
|
|
new ProcessCaptureError("process capture was cancelled", {
|
|
cause: reason,
|
|
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(signal.reason);
|
|
return cause;
|
|
};
|