refactor(application): own the finalization precedence in runOwned (#10645)

* 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
This commit is contained in:
morluto authored and GitHub committed 2026-11-15 10:24:25 +08:00
1 parent 61fd6336bf
commit 2e1e6593f2
5 files changed
+704 -147

No files matched your search

+6 -1
View File
@@ -459,7 +459,12 @@ failed request's physical, cancellation and cleanup facts.
remain attached to their owning envelope while cleanup is pending and after
confirmed release. CLI adapters should reuse
[`finishAnalysisCommand`](https://github.com/morluto/rea/blob/main/src/cli/finishAnalysisCommand.ts) where its contract
applies rather than duplicate close/finally handling.
applies rather than duplicate close/finally handling. It projects
[`runOwned`](https://github.com/morluto/rea/blob/main/src/application/runOwned.ts),
which owns this precedence for owned work: 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.
- Give descriptor-backed reads an owner before opening the file. A rejected
close must retain the exact handle and preserve any earlier failure.
Carry [`openRegularFile`](https://github.com/morluto/rea/blob/main/src/filesystem/RegularFile.ts)'s
@@ -113,7 +113,6 @@
"src/browser/modules/NativeModuleResolver.ts": 3,
"src/captures/TsharkCaptureProvider.ts": 2,
"src/cli/environmentCommands.ts": 2,
"src/cli/finishAnalysisCommand.ts": 3,
"src/cliJsonInput.ts": 1,
"src/desktop/BuildComputerLongRuns.ts": 5,
"src/desktop/DesktopBuildDoctorFacts.ts": 5,
+329
View File
@@ -0,0 +1,329 @@
import { describe, expect, it } from "vitest";
import {
AnalysisCancelledError,
AnalysisProtocolError,
} from "../domain/analysisErrorCore.js";
import {
primaryAnalysisError,
type AnalysisError,
type AnalysisPartialObservation,
} from "../domain/analysisErrorBase.js";
import { analysisErrorWithCleanupFailure } from "../domain/analysisErrorCleanup.js";
import { ProviderCleanupError } from "../domain/providerCleanupError.js";
import { err, ok, type Result } from "../domain/result.js";
import { finalizedResult, runOwned } from "./runOwned.js";
const value: AnalysisPartialObservation = {
kind: "retained-evidence",
evidence_id: "evidence-1",
};
const reason = { caller: "stop" };
const completedWork = async (): Promise<
Result<AnalysisPartialObservation, AnalysisError>
> => ok(value);
/** A close that aborts the caller's signal, so cancellation lands during final cleanup. */
const closeThenAbort =
(controller: AbortController, failure?: unknown) => async () => {
controller.abort(reason);
if (failure !== undefined) throw failure;
};
const options = (controller: AbortController, close: () => Promise<void>) => ({
providerId: "fixture",
operation: "fixture_operation",
signal: controller.signal,
close,
});
describe("runOwned precedence", () => {
it("completes when cleanup is released and no cancellation arrives", async () => {
const controller = new AbortController();
const finalized = await runOwned(
completedWork,
options(controller, async () => undefined),
);
expect(finalized).toEqual({ kind: "completed", value });
});
it("cancels a completed value when cancellation arrives during final cleanup before commit", async () => {
const controller = new AbortController();
const finalized = await runOwned(
completedWork,
options(controller, closeThenAbort(controller)),
);
expect(finalized.kind).toBe("cancelled");
const result = finalizedResult(finalized);
if (result.ok) throw new Error("expected cancellation");
expect(result.error).toBeInstanceOf(AnalysisCancelledError);
expect(result.error.cause).toBe(reason);
expect(result.error.partialObservation).toBe(value);
});
it("keeps a committed value when cancellation arrives after the commit", async () => {
const controller = new AbortController();
const commits: unknown[] = [];
const finalized = await runOwned(completedWork, {
...options(controller, closeThenAbort(controller)),
commit: async (committed) => {
commits.push(committed);
},
});
expect(commits).toEqual([value]);
expect(finalized).toEqual({
kind: "completed",
value,
lateCancellation: reason,
});
});
it("does not commit after cancellation and reports it before commit", async () => {
const controller = new AbortController();
controller.abort(reason);
let committed = false;
const finalized = await runOwned(completedWork, {
...options(controller, async () => undefined),
commit: async () => {
committed = true;
},
});
expect(committed).toBe(false);
expect(finalized.kind).toBe("cancelled");
});
it("keeps a recorded failure primary when cancellation arrives during final cleanup", async () => {
const controller = new AbortController();
const failure = new AnalysisProtocolError("fixture failure");
const finalized = await runOwned(
async () => err(failure),
options(controller, closeThenAbort(controller)),
);
expect(finalized.kind).toBe("failed");
const result = finalizedResult(finalized);
if (result.ok) throw new Error("expected failure");
expect(primaryAnalysisError(result.error)).toBe(failure);
expect(failure.secondaryFailures).toEqual([
expect.objectContaining({
_tag: "AnalysisCancelledError",
cause: reason,
}),
]);
});
it("reports a completed value with unconfirmed cleanup as cleanup_incomplete keeping the value", async () => {
const controller = new AbortController();
const finalized = await runOwned(
completedWork,
options(controller, async () => {
throw new Error("close failed");
}),
);
expect(finalized.kind).toBe("completed-cleanup-retained");
const result = finalizedResult(finalized);
if (result.ok) throw new Error("expected cleanup failure");
expect(result.error).toBeInstanceOf(ProviderCleanupError);
expect(result.error.cleanupIncomplete).toBe(true);
expect(result.error.partialObservation).toBe(value);
});
it("keeps the primary failure's identity when cleanup also fails", async () => {
const controller = new AbortController();
const failure = new AnalysisProtocolError("fixture failure");
const cleanup = new ProviderCleanupError("fixture", ["fixture-run"], {
reason: "still running",
});
const finalized = await runOwned(
async () => err(failure),
options(controller, async () => {
throw cleanup;
}),
);
expect(finalized).toMatchObject({
kind: "failed",
cleanup: { kind: "retained", failure: cleanup },
});
const result = finalizedResult(finalized);
if (result.ok) throw new Error("expected failure");
expect(primaryAnalysisError(result.error)).toBe(failure);
expect(result.error.cleanupIncomplete).toBe(true);
});
it("types a thrown work failure as a provider adapter failure", async () => {
const controller = new AbortController();
const finalized = await runOwned(
async () => {
throw new Error("unexpected");
},
options(controller, async () => undefined),
);
const result = finalizedResult(finalized);
if (result.ok) throw new Error("expected failure");
expect(result.error._tag).toBe("ProviderAdapterError");
});
});
it("classifies producer cancellation even when cleanup also fails", async () => {
const controller = new AbortController();
const cancellation = new AnalysisCancelledError("fixture_operation", {
cause: reason,
});
const finalized = await runOwned(async () => err(cancellation), {
...options(controller, async () => {
throw new Error("still running");
}),
});
expect(finalized.kind).toBe("cancelled");
if (finalized.kind !== "cancelled") throw new Error("expected cancellation");
expect(finalized.cleanup.kind).toBe("retained");
expect(primaryAnalysisError(finalized.error)).toBe(cancellation);
});
it("classifies a producer composite whose primary is a cancellation as cancelled", async () => {
const controller = new AbortController();
const cancellation = new AnalysisCancelledError("fixture_operation", {
cause: reason,
});
const cleanup = new ProviderCleanupError("fixture", ["owned-process"], {
reason: "pending",
});
const composite = analysisErrorWithCleanupFailure(
cancellation,
cleanup,
"fixture_operation",
).bindCleanupDependencies([cancellation, cleanup]);
const finalized = await runOwned(
async () => err(composite),
options(controller, async () => {
cleanup.resolveCleanup();
}),
);
expect(finalized).toMatchObject({
kind: "cancelled",
error: composite,
cleanup: { kind: "released" },
});
});
it("keeps a failure with a later cancellation classified as failed", async () => {
const controller = new AbortController();
const failure = new AnalysisProtocolError("fixture failure");
const finalized = await runOwned(
async () => err(failure),
options(controller, closeThenAbort(controller)),
);
expect(finalized.kind).toBe("failed");
expect(failure.secondaryFailures).toHaveLength(1);
});
it("keeps producer cleanup retained when close does not resolve it", async () => {
const controller = new AbortController();
const failure = new ProviderCleanupError("fixture", ["owned-process"], {
reason: "pending",
});
const finalized = await runOwned(
async () => err(failure),
options(controller, async () => undefined),
);
expect(finalized).toMatchObject({
kind: "failed",
error: failure,
cleanup: { kind: "retained", failure },
});
});
it("retains completed work when publication fails", async () => {
const finalized = await runOwned(completedWork, {
...options(new AbortController(), async () => undefined),
commit: async () => {
throw new Error("publication failed");
},
});
const result = finalizedResult(finalized);
if (result.ok) throw new Error("expected publication failure");
expect(result.error.partialObservation).toBe(value);
});
it("publishes recovered completion after cleanup resolves", async () => {
const controller = new AbortController();
const failure = new ProviderCleanupError("fixture", ["owned-process"], {
reason: "pending",
});
const published: AnalysisPartialObservation[] = [];
const finalized = await runOwned(async () => err(failure), {
...options(controller, async () => {
failure.resolveCleanup();
}),
recoverCompleted: (_error, observed) =>
observed.cleanupResolvedByClose ? { value } : undefined,
commit: async (completed) => {
published.push(completed);
},
});
expect(published).toEqual([value]);
expect(finalized).toEqual({ kind: "completed", value });
});
it("keeps a recovered value when cancellation arrives during its publication", async () => {
const controller = new AbortController();
const failure = new ProviderCleanupError("fixture", ["owned-process"], {
reason: "pending",
});
const finalized = await runOwned(async () => err(failure), {
...options(controller, async () => {
failure.resolveCleanup();
}),
recoverCompleted: () => ({ value }),
commit: async () => {
controller.abort(reason);
},
});
expect(finalized).toEqual({
kind: "completed",
value,
lateCancellation: reason,
});
});
it("keeps a cancellation that arrives while a recovered publication fails", async () => {
const controller = new AbortController();
const failure = new ProviderCleanupError("fixture", ["owned-process"], {
reason: "pending",
});
const publication = new AnalysisProtocolError("publication failed");
const finalized = await runOwned(async () => err(failure), {
...options(controller, async () => {
failure.resolveCleanup();
}),
recoverCompleted: () => ({ value }),
commit: async () => {
controller.abort(reason);
throw publication;
},
});
expect(finalized.kind).toBe("failed");
const result = finalizedResult(finalized);
if (result.ok) throw new Error("expected publication failure");
expect(primaryAnalysisError(result.error)).toBe(publication);
expect(publication.secondaryFailures).toEqual([
expect.objectContaining({ _tag: "AnalysisCancelledError", cause: reason }),
]);
});
it("retains a recovered value when its publication fails", async () => {
const failure = new ProviderCleanupError("fixture", ["owned-process"], {
reason: "pending",
});
const finalized = await runOwned(async () => err(failure), {
...options(new AbortController(), async () => {
failure.resolveCleanup();
}),
recoverCompleted: () => ({ value }),
commit: async () => {
throw new Error("publication failed");
},
});
const result = finalizedResult(finalized);
if (result.ok) throw new Error("expected publication failure");
expect(result.error.partialObservation).toBe(value);
});
+280
View File
@@ -0,0 +1,280 @@
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
import {
AnalysisError,
primaryAnalysisError,
type AnalysisPartialObservation,
} from "../domain/analysisErrorBase.js";
import { analysisErrorWithCleanupFailure } from "../domain/analysisErrorCleanup.js";
import { analysisErrorWithCancellation } from "../domain/analysisErrorCancellation.js";
import {
AnalysisCancelledError,
cancelledBy,
isAborted,
} from "../domain/analysisErrorCore.js";
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
import { ProviderCleanupError } from "../domain/providerCleanupError.js";
import type { JsonValue } from "../domain/jsonValue.js";
import type { Result } from "../domain/result.js";
/** What closing the work's owner established. */
export type CleanupOutcome =
| { readonly kind: "released" }
| { readonly kind: "retained"; readonly failure: AnalysisError };
/**
* Work, owner cleanup and cancellation composed by one precedence rule:
* - a recorded failure stays primary; a later cancellation is attached to it;
* - cancellation observed before commit turns a completed value into a
* cancellation that keeps the value as its partial observation;
* - cancellation observed after commit keeps the completed value;
* - unconfirmed cleanup never replaces the primary. A completed value whose
* cleanup is unconfirmed is a `cleanup_incomplete` error that keeps the
* value as its partial observation (the documented one-shot contract).
*/
export type Finalized<Value> =
| {
readonly kind: "completed";
readonly value: Value;
/** A cancellation observed after the commit cutoff. */
readonly lateCancellation?: unknown;
}
| {
readonly kind: "completed-cleanup-retained";
readonly value: Value;
readonly error: AnalysisError;
}
| {
readonly kind: "cancelled";
readonly error: AnalysisError;
readonly cleanup: CleanupOutcome;
}
| {
readonly kind: "failed";
readonly error: AnalysisError;
readonly cleanup: CleanupOutcome;
};
/** A completed value recovered from a failure whose only cause was cleanup. */
export interface RecoveredCompletion<Value> {
readonly value: Value;
/** Diagnostics a later pre-commit cancellation carries, such as the resolved cleanup history. */
readonly cancellationDiagnostics?: Readonly<Record<string, JsonValue>>;
}
export interface RunOwnedOptions<Value> {
readonly providerId: string;
readonly operation: string;
readonly signal: AbortSignal;
/** Releases everything the work acquired; awaited before the signal is read. */
readonly close: () => Promise<void>;
/** Names the owned resources when a non-REA close failure names none. */
readonly fallbackResource?: string;
/**
* Publishes a completed value. Cancellation observed before it runs cancels
* the work; cancellation observed after it keeps the committed value.
*/
readonly commit?: (value: Value) => Promise<void>;
/** Observe the primary outcome before close may change its cleanup status. */
readonly inspectPrimary?: (primary: Result<Value, AnalysisError>) => void;
/** Recover a completed value from a primary failure after close settled. */
readonly recoverCompleted?: (
primary: AnalysisError,
observed: {
/** The primary reported unconfirmed cleanup before close and not after. */
readonly cleanupResolvedByClose: boolean;
readonly cleanup: CleanupOutcome;
},
) => RecoveredCompletion<Value> | undefined;
}
const closeFailure = (
cause: unknown,
options: Pick<
RunOwnedOptions<unknown>,
"providerId" | "operation" | "fallbackResource"
>,
): AnalysisError => {
if (cause instanceof AnalysisError) return cause;
const observation =
cause instanceof ArtifactReaderFailure
? ArtifactReaderFailure.cleanupObservation(
cause,
options.fallbackResource ?? `${options.operation} resources`,
)
: {
reason: cause instanceof Error ? cause.message : String(cause),
resources: [],
};
return new ProviderCleanupError(
options.providerId,
observation.resources,
{ reason: observation.reason },
{ cause, operation: options.operation },
);
};
/** Run owned work, close its owner, and apply the finalization precedence once. */
export const runOwned = async <Value extends AnalysisPartialObservation>(
work: () => Promise<Result<Value, AnalysisError>>,
options: RunOwnedOptions<Value>,
): Promise<Finalized<Value>> => {
let primary: Result<Value, AnalysisError>;
try {
primary = await work();
} catch (cause) {
primary = {
ok: false,
error:
cause instanceof AnalysisError
? cause
: new ProviderAdapterError(options.providerId, options.operation, {
cause,
}),
};
}
let committed = false;
const commit = async (
value: Value,
): Promise<Result<Value, AnalysisError>> => {
try {
await options.commit!(value);
committed = true;
return { ok: true, value };
} catch (cause) {
return {
ok: false,
error:
cause instanceof AnalysisError
? cause.retainPartialObservation(value)
: new ProviderAdapterError(options.providerId, options.operation, {
cause,
partialObservation: value,
}),
};
}
};
if (primary.ok && options.commit !== undefined && !options.signal.aborted)
primary = await commit(primary.value);
options.inspectPrimary?.(primary);
const hadIncompleteCleanup = !primary.ok && primary.error.cleanupIncomplete;
let cleanup: CleanupOutcome = { kind: "released" };
try {
// Owners may resolve the primary's retained cleanup during close; only
// they can confirm it, so its status is read again afterwards.
await options.close();
} catch (cause) {
cleanup = { kind: "retained", failure: closeFailure(cause, options) };
}
const signal = options.signal;
let outcome:
| {
readonly ok: true;
readonly value: Value;
readonly diagnostics?: Readonly<Record<string, JsonValue>>;
}
| { readonly ok: false; readonly error: AnalysisError } = primary;
if (!primary.ok) {
if (isAborted(signal))
analysisErrorWithCancellation(
primary.error,
cancelledBy(options.operation, signal),
);
const recovered = options.recoverCompleted?.(primary.error, {
cleanupResolvedByClose:
hadIncompleteCleanup && !primary.error.cleanupIncomplete,
cleanup,
});
if (recovered !== undefined)
outcome = {
ok: true,
value: recovered.value,
...(recovered.cancellationDiagnostics === undefined
? {}
: { diagnostics: recovered.cancellationDiagnostics }),
};
}
if (
outcome.ok &&
!primary.ok &&
options.commit !== undefined &&
!signal.aborted
) {
const publication = await commit(outcome.value);
if (!publication.ok) {
// Publication is the last awaited step; a cancellation during it stays
// attached to the publication failure without replacing it.
outcome = publication;
if (isAborted(signal))
analysisErrorWithCancellation(
publication.error,
cancelledBy(options.operation, signal),
);
}
}
if (
cleanup.kind === "released" &&
!outcome.ok &&
outcome.error.cleanupIncomplete
)
cleanup = { kind: "retained", failure: outcome.error };
// The kind follows the primary failure, so a producer's composite whose
// primary is a cancellation is cancelled, and a failure that only retains a
// later cancellation as a secondary stays failed.
let cancelled =
!outcome.ok &&
primaryAnalysisError(outcome.error) instanceof AnalysisCancelledError;
if (outcome.ok && isAborted(signal)) {
if (committed && cleanup.kind === "released")
return {
kind: "completed",
value: outcome.value,
lateCancellation: signal.reason,
};
if (!committed) {
cancelled = true;
outcome = {
ok: false,
error: cancelledBy(options.operation, signal, {
partialObservation: outcome.value,
...(outcome.diagnostics === undefined
? {}
: { diagnostics: outcome.diagnostics }),
}),
};
}
}
if (cleanup.kind === "retained") {
if (outcome.ok)
return {
kind: "completed-cleanup-retained",
value: outcome.value,
error: cleanup.failure.retainPartialObservation(outcome.value),
};
return {
kind: cancelled ? "cancelled" : "failed",
error:
cleanup.failure === outcome.error
? outcome.error
: analysisErrorWithCleanupFailure(
outcome.error,
cleanup.failure,
options.operation,
).bindCleanupDependencies([outcome.error, cleanup.failure]),
cleanup,
};
}
if (outcome.ok) return { kind: "completed", value: outcome.value };
return {
kind: cancelled ? "cancelled" : "failed",
error: outcome.error,
cleanup,
};
};
/** The single Result a caller sees for a finalized outcome. */
export const finalizedResult = <Value>(
finalized: Finalized<Value>,
): Result<Value, AnalysisError> =>
finalized.kind === "completed"
? { ok: true, value: finalized.value }
: { ok: false, error: finalized.error };
+89 -145
View File
@@ -1,16 +1,16 @@
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
import {
finalizedResult,
runOwned,
type RecoveredCompletion,
} from "../application/runOwned.js";
import type {
AnalysisError,
type AnalysisPartialObservation,
AnalysisPartialObservation,
} from "../domain/analysisErrorBase.js";
import { analysisErrorWithCleanupFailure } from "../domain/analysisErrorCleanup.js";
import { analysisErrorWithCancellation } from "../domain/analysisErrorCancellation.js";
import { AnalysisCancelledError } from "../domain/analysisErrorCore.js";
import {
projectAnalysisError,
projectAnalysisErrorWithoutCapturedOutput,
} from "../domain/analysisErrorProjection.js";
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
import { ProviderCleanupError } from "../domain/providerCleanupError.js";
import { err, ok, type Result } from "../domain/result.js";
import { dispatchQueuedInterrupts } from "../process/queuedInterrupts.js";
@@ -43,69 +43,77 @@ export const finishAnalysisCommand = async <
// reads this result, so guidance needing a continuing session is omitted.
const projectFailure = (error: AnalysisError) =>
projectAnalysisError(options.redactFailure?.(error) ?? error, "invocation");
let result: Result<Value, AnalysisError>;
let cleanup: AnalysisError | undefined;
try {
result = await run();
} catch (cause) {
result = {
ok: false,
error:
cause instanceof AnalysisError
? cause
: new ProviderAdapterError(providerId, canonicalOperation, { cause }),
};
}
// Some one-shot commands promise the original failure snapshot even when
// close retries and mutates the producer's live cleanup status.
const initialFailure =
!result.ok && options.failureProjection === "initial"
? projectFailure(result.error)
: undefined;
const hadIncompleteCleanup = !result.ok && result.error.cleanupIncomplete;
const initialDiagnosticFailure =
initialFailure !== undefined &&
!result.ok &&
result.error.capturedOutput !== undefined
? projectAnalysisErrorWithoutCapturedOutput(
options.redactFailure?.(result.error) ?? result.error,
"invocation",
)
: initialFailure;
try {
// Resource owners may resolve their retained errors during close. This
// adapter cannot infer resolution of a producer's earlier cleanup failure.
await service.close();
} catch (cause) {
if (cause instanceof AnalysisError) cleanup = cause;
else {
const observation =
cause instanceof ArtifactReaderFailure
? ArtifactReaderFailure.cleanupObservation(
cause,
fallbackResource ?? `${canonicalOperation} resources`,
)
: {
reason: cause instanceof Error ? cause.message : String(cause),
resources: [],
};
cleanup = new ProviderCleanupError(
providerId,
observation.resources,
{ reason: observation.reason },
{ cause, operation: canonicalOperation },
);
}
}
await dispatchQueuedInterrupts();
if (!result.ok && signal.aborted)
analysisErrorWithCancellation(
result.error,
new AnalysisCancelledError(canonicalOperation, { cause: signal.reason }),
);
if (initialFailure !== undefined && cleanup === undefined && !result.ok) {
// Close may mutate cleanup state; preserve the snapshot and add later failures.
const secondary = projectFailure(result.error).details?.secondary_failures;
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
@@ -118,83 +126,19 @@ export const finishAnalysisCommand = async <
},
);
}
if (
initialFailure === undefined &&
cleanup === undefined &&
!result.ok &&
hadIncompleteCleanup &&
!result.error.cleanupIncomplete
) {
const recovered = options.recoverResolvedCleanup?.(result.error);
if (recovered !== undefined) result = ok(recovered);
}
if (
initialFailure === undefined &&
!result.ok &&
result.error instanceof ProviderCleanupError &&
!result.error.cleanupIncomplete &&
result.error.cleanupPrimary === undefined &&
result.error.completedObservation !== undefined
) {
const completed = options.recoverCompletedObservation?.(
result.error.completedObservation,
);
if (completed !== undefined) {
const recoveredCleanup = result.error;
result = signal.aborted
? {
ok: false,
error: new AnalysisCancelledError(canonicalOperation, {
cause: signal.reason,
partialObservation: completed,
diagnostics: {
cleanup_history: projectFailure(recoveredCleanup),
},
}),
}
: { ok: true, value: completed };
}
}
if (result.ok && signal.aborted)
result = {
ok: false,
error: new AnalysisCancelledError(canonicalOperation, {
cause: signal.reason,
partialObservation: result.value,
}),
};
if (cleanup !== undefined) {
if (result.ok) cleanup.retainPartialObservation(result.value);
result = {
ok: false,
error: result.ok
? cleanup
: analysisErrorWithCleanupFailure(
result.error,
cleanup,
canonicalOperation,
).bindCleanupDependencies([result.error, cleanup]),
};
}
if (result.ok) return ok(result.value);
const failure = projectFailure(result.error);
const diagnostics = failure.details?.diagnostics;
return err(
initialFailure === undefined || cleanup === undefined
? failure
: {
...failure,
details: {
...failure.details,
diagnostics: {
...(typeof diagnostics === "object" &&
diagnostics !== null &&
!Array.isArray(diagnostics)
? diagnostics
: {}),
primary_error: initialDiagnosticFailure ?? initialFailure,
},
},
},
);
return err({
...failure,
details: {
...failure.details,
diagnostics: {
...(typeof diagnostics === "object" &&
diagnostics !== null &&
!Array.isArray(diagnostics)
? diagnostics
: {}),
primary_error: initialDiagnosticFailure ?? initialFailure,
},
},
});
};