mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 11:13:21 +00:00
* refactor(application): own the finalization precedence in runOwned runOwned runs owned work, closes its owner and applies the documented precedence once: a recorded failure stays primary, cancellation before commit keeps the completed value as a partial observation, cancellation after commit keeps the value, and unconfirmed cleanup reports cleanup_incomplete while keeping the completed value. finishAnalysisCommand now projects its Finalized outcome; its 31 callers are unchanged. * fix(application): preserve finalization outcomes * fix(application): classify finalization by the primary error and keep cancellation through recovered publication A producer composite whose primary is a cancellation finalized as failed because the kind read only the top-level error class; it now follows primaryAnalysisError. A cancellation that arrived while a recovered value's publication failed was dropped because the signal was read before that last await; it is now attached to the publication failure as a secondary. * fix(cli): keep the initial failure snapshot whenever close itself succeeded
145 lines
5.0 KiB
TypeScript
145 lines
5.0 KiB
TypeScript
import {
|
|
finalizedResult,
|
|
runOwned,
|
|
type RecoveredCompletion,
|
|
} from "../application/runOwned.js";
|
|
import type {
|
|
AnalysisError,
|
|
AnalysisPartialObservation,
|
|
} from "../domain/analysisErrorBase.js";
|
|
import {
|
|
projectAnalysisError,
|
|
projectAnalysisErrorWithoutCapturedOutput,
|
|
} from "../domain/analysisErrorProjection.js";
|
|
import { ProviderCleanupError } from "../domain/providerCleanupError.js";
|
|
import { err, ok, type Result } from "../domain/result.js";
|
|
import { dispatchQueuedInterrupts } from "../process/queuedInterrupts.js";
|
|
|
|
/** Finish a one-shot CLI operation while retaining primary results through close. */
|
|
export const finishAnalysisCommand = async <
|
|
Value extends AnalysisPartialObservation,
|
|
>(
|
|
run: () => Promise<Result<Value, AnalysisError>>,
|
|
service: { close(): Promise<void> },
|
|
providerId: string,
|
|
canonicalOperation: string,
|
|
signal: AbortSignal,
|
|
fallbackResource?: string,
|
|
options: {
|
|
readonly failureProjection?: "initial" | "final";
|
|
/** Apply invocation-specific exclusions after all owned cleanup is composed. */
|
|
readonly redactFailure?: (error: AnalysisError) => AnalysisError;
|
|
/** Validate producer-attested completed output against this command's exact result contract. */
|
|
readonly recoverCompletedObservation?: (
|
|
observation: AnalysisPartialObservation,
|
|
) => Value | undefined;
|
|
/** Recover a producer-specific completed result after its cleanup is confirmed. */
|
|
readonly recoverResolvedCleanup?: (
|
|
error: AnalysisError,
|
|
) => Value | undefined;
|
|
} = {},
|
|
): Promise<Result<Value, ReturnType<typeof projectAnalysisError>>> => {
|
|
// The command releases its provider resources and runtime before the caller
|
|
// reads this result, so guidance needing a continuing session is omitted.
|
|
const projectFailure = (error: AnalysisError) =>
|
|
projectAnalysisError(options.redactFailure?.(error) ?? error, "invocation");
|
|
// Some one-shot commands promise the original failure snapshot even when
|
|
// close retries and mutates the producer's live cleanup status.
|
|
let initialFailure: ReturnType<typeof projectAnalysisError> | undefined;
|
|
let initialDiagnosticFailure: typeof initialFailure;
|
|
let closeFailed = false;
|
|
const finalized = await runOwned(run, {
|
|
providerId,
|
|
operation: canonicalOperation,
|
|
signal,
|
|
...(fallbackResource === undefined ? {} : { fallbackResource }),
|
|
close: async () => {
|
|
try {
|
|
await service.close();
|
|
} catch (cause) {
|
|
closeFailed = true;
|
|
throw cause;
|
|
} finally {
|
|
await dispatchQueuedInterrupts();
|
|
}
|
|
},
|
|
inspectPrimary: (primary) => {
|
|
if (primary.ok || options.failureProjection !== "initial") return;
|
|
initialFailure = projectFailure(primary.error);
|
|
initialDiagnosticFailure =
|
|
primary.error.capturedOutput === undefined
|
|
? initialFailure
|
|
: projectAnalysisErrorWithoutCapturedOutput(
|
|
options.redactFailure?.(primary.error) ?? primary.error,
|
|
"invocation",
|
|
);
|
|
},
|
|
recoverCompleted: (
|
|
primary,
|
|
observed,
|
|
): RecoveredCompletion<Value> | undefined => {
|
|
if (initialFailure !== undefined) return undefined;
|
|
if (
|
|
observed.cleanupResolvedByClose &&
|
|
observed.cleanup.kind === "released"
|
|
) {
|
|
const recovered = options.recoverResolvedCleanup?.(primary);
|
|
if (recovered !== undefined) return { value: recovered };
|
|
}
|
|
if (
|
|
primary instanceof ProviderCleanupError &&
|
|
!primary.cleanupIncomplete &&
|
|
primary.cleanupPrimary === undefined &&
|
|
primary.completedObservation !== undefined
|
|
) {
|
|
const completed = options.recoverCompletedObservation?.(
|
|
primary.completedObservation,
|
|
);
|
|
if (completed !== undefined)
|
|
return {
|
|
value: completed,
|
|
cancellationDiagnostics: {
|
|
cleanup_history: projectFailure(primary),
|
|
},
|
|
};
|
|
}
|
|
return undefined;
|
|
},
|
|
});
|
|
const result = finalizedResult(finalized);
|
|
if (result.ok) return ok(result.value);
|
|
const failure = projectFailure(result.error);
|
|
if (initialFailure === undefined) return err(failure);
|
|
if (!closeFailed) {
|
|
// Close may mutate cleanup state, including resolving the cleanup of a
|
|
// cancelled primary; preserve the snapshot and add later failures.
|
|
const secondary = failure.details?.secondary_failures;
|
|
return err(
|
|
secondary === undefined
|
|
? initialFailure
|
|
: {
|
|
...initialFailure,
|
|
details: {
|
|
...initialFailure.details,
|
|
secondary_failures: secondary,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
const diagnostics = failure.details?.diagnostics;
|
|
return err({
|
|
...failure,
|
|
details: {
|
|
...failure.details,
|
|
diagnostics: {
|
|
...(typeof diagnostics === "object" &&
|
|
diagnostics !== null &&
|
|
!Array.isArray(diagnostics)
|
|
? diagnostics
|
|
: {}),
|
|
primary_error: initialDiagnosticFailure ?? initialFailure,
|
|
},
|
|
},
|
|
});
|
|
};
|