mirror of
https://github.com/morluto/rea.git
synced 2026-11-06 23:06:18 +00:00
fix(kuna): preserve live cleanup in caller failures
Reuse canonical typed failure attribution instead of copying cleanup markers. Preserve captured facts, typed primary outcomes and opaque cancellation reasons; bind caller projections to the exact producer cleanup owner. Refs: #4693
This commit is contained in:
1 parent
5717ad24aa
commit
6eb55be4e8
4 files changed
+236
-97
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import type { JsonValue } from "./jsonValue.js";
|
||||
import { ProviderAdapterError } from "./providerAdapterError.js";
|
||||
import type { AnalysisPartialObservation } from "./analysisErrorBase.js";
|
||||
import type { AnalysisErrorOptions } from "./analysisErrorBase.js";
|
||||
|
||||
/** Provider resources could not be proven closed after bounded cleanup. */
|
||||
export class ProviderCleanupError extends ProviderAdapterError {
|
||||
@@ -13,20 +13,14 @@ export class ProviderCleanupError extends ProviderAdapterError {
|
||||
resources: readonly string[],
|
||||
diagnostics: Readonly<Record<string, JsonValue>>,
|
||||
/** Identify the operation owning these resources; binary callers retain their default. */
|
||||
options?: ErrorOptions & {
|
||||
options?: Omit<AnalysisErrorOptions, "diagnostics"> & {
|
||||
readonly operation?: string;
|
||||
readonly partialObservation?: AnalysisPartialObservation;
|
||||
readonly remediationAction?: string;
|
||||
},
|
||||
) {
|
||||
const {
|
||||
operation = "close_binary",
|
||||
partialObservation,
|
||||
...errorOptions
|
||||
} = options ?? {};
|
||||
const { operation = "close_binary", ...errorOptions } = options ?? {};
|
||||
super(providerId, operation, {
|
||||
...errorOptions,
|
||||
...(partialObservation === undefined ? {} : { partialObservation }),
|
||||
diagnostics,
|
||||
});
|
||||
this.setCleanupResources(resources);
|
||||
|
||||
+11
-86
@@ -1,16 +1,6 @@
|
||||
import type {
|
||||
AnalysisError,
|
||||
AnalysisErrorOptions,
|
||||
} from "../domain/analysisErrorBase.js";
|
||||
import {
|
||||
AnalysisArtifactChangedError,
|
||||
AnalysisCancelledError,
|
||||
AnalysisOutputError,
|
||||
AnalysisResourceConstraintError,
|
||||
AnalysisTimeoutError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
|
||||
import { ProviderCleanupError } from "../domain/providerCleanupError.js";
|
||||
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import { analysisClientFailureForOperation } from "../providers/AnalysisClientFailure.js";
|
||||
import { KUNA_PROVIDER_ID } from "./KunaRelease.js";
|
||||
|
||||
/** Keep one producer failure immutable while each waiter receives its context. */
|
||||
export const kunaQueryFailureForCaller = (
|
||||
@@ -23,77 +13,12 @@ export const kunaQueryFailureForCaller = (
|
||||
source.operation === operation
|
||||
)
|
||||
return source;
|
||||
const diagnostics = {
|
||||
...source.diagnostics,
|
||||
producer_operation: source.operation,
|
||||
};
|
||||
const options: AnalysisErrorOptions & {
|
||||
userMessage?: string;
|
||||
remediationAction?: string;
|
||||
} = {
|
||||
cause: source,
|
||||
diagnostics,
|
||||
...(source.capturedOutput === undefined
|
||||
? {}
|
||||
: { capturedOutput: source.capturedOutput }),
|
||||
...(source.cleanup === undefined ? {} : { cleanup: source.cleanup }),
|
||||
...(source.partialObservation === undefined
|
||||
? {}
|
||||
: { partialObservation: source.partialObservation }),
|
||||
...(source.userMessage === undefined
|
||||
? {}
|
||||
: { userMessage: source.userMessage }),
|
||||
...("remediationAction" in source &&
|
||||
typeof source.remediationAction === "string"
|
||||
? { remediationAction: source.remediationAction }
|
||||
: {}),
|
||||
};
|
||||
// The command owner keeps the original cleanup failure and its supervisor.
|
||||
// This contextual diagnostic retains that source as its cause and every resource.
|
||||
if (source instanceof ProviderCleanupError)
|
||||
return new ProviderCleanupError(
|
||||
source.providerId,
|
||||
source.cleanupResources,
|
||||
diagnostics,
|
||||
{
|
||||
operation,
|
||||
cause: source,
|
||||
...(source.partialObservation === undefined
|
||||
? {}
|
||||
: { partialObservation: source.partialObservation }),
|
||||
},
|
||||
);
|
||||
if (source instanceof AnalysisTimeoutError)
|
||||
return new AnalysisTimeoutError(operation, source.timeoutMs, options);
|
||||
if (source instanceof AnalysisResourceConstraintError)
|
||||
return new AnalysisResourceConstraintError(
|
||||
operation,
|
||||
source.resource,
|
||||
source.reason,
|
||||
source.reportedLimits,
|
||||
{
|
||||
...options,
|
||||
...(source.retryable === undefined
|
||||
? {}
|
||||
: { retryable: source.retryable }),
|
||||
},
|
||||
);
|
||||
if (source instanceof AnalysisOutputError)
|
||||
return new AnalysisOutputError(operation, source.reason, {
|
||||
...options,
|
||||
cleanupOnly: source.cleanupOnly,
|
||||
});
|
||||
if (source instanceof ProviderAdapterError)
|
||||
return new ProviderAdapterError(source.providerId, operation, options);
|
||||
if (source instanceof AnalysisCancelledError)
|
||||
return new AnalysisCancelledError(operation, options);
|
||||
if (source instanceof AnalysisArtifactChangedError)
|
||||
return new AnalysisArtifactChangedError(
|
||||
operation,
|
||||
source.path,
|
||||
source.reason,
|
||||
options,
|
||||
);
|
||||
// Preserve unfamiliar error variants rather than flattening their semantics.
|
||||
return source;
|
||||
return analysisClientFailureForOperation(
|
||||
source,
|
||||
KUNA_PROVIDER_ID,
|
||||
operation,
|
||||
{
|
||||
producer_operation: source.operation,
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -13,8 +13,12 @@ import {
|
||||
AnalysisAccessDeniedError,
|
||||
AnalysisArtifactChangedError,
|
||||
AnalysisResourceConstraintError,
|
||||
AnalysisOutputError,
|
||||
AnalysisTimeoutError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
|
||||
import { ProviderCleanupError } from "../domain/providerCleanupError.js";
|
||||
import type { JsonValue } from "../domain/jsonValue.js";
|
||||
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
|
||||
import { bindArtifactFileCleanup } from "../artifacts/ArtifactFileOperation.js";
|
||||
|
||||
@@ -86,10 +90,14 @@ export const analysisClientFailureForOperation = (
|
||||
cause: unknown,
|
||||
providerId: string,
|
||||
operation: string,
|
||||
callerDiagnostics?: Readonly<Record<string, JsonValue>>,
|
||||
): AnalysisError => {
|
||||
if (!(cause instanceof AnalysisError)) {
|
||||
const failure = new ProviderAdapterError(providerId, operation, {
|
||||
cause,
|
||||
...(callerDiagnostics === undefined
|
||||
? {}
|
||||
: { diagnostics: callerDiagnostics }),
|
||||
...(cause instanceof ArtifactReaderFailure && cause.cleanup !== undefined
|
||||
? { cleanup: cause.cleanup }
|
||||
: {}),
|
||||
@@ -106,10 +114,24 @@ export const analysisClientFailureForOperation = (
|
||||
!(cause instanceof AnalysisCapabilityUnavailableError) &&
|
||||
!(cause instanceof AnalysisAccessDeniedError) &&
|
||||
!(cause instanceof AnalysisArtifactChangedError) &&
|
||||
!(cause instanceof AnalysisTimeoutError) &&
|
||||
!(cause instanceof AnalysisOutputError) &&
|
||||
!(cause instanceof AnalysisResourceConstraintError)
|
||||
)
|
||||
return cause;
|
||||
const context = analysisClientFailureContext(cause, providerId, operation);
|
||||
const sourceContext = analysisClientFailureContext(
|
||||
cause,
|
||||
providerId,
|
||||
operation,
|
||||
);
|
||||
const context = {
|
||||
...sourceContext,
|
||||
...(callerDiagnostics === undefined
|
||||
? {}
|
||||
: {
|
||||
diagnostics: { ...sourceContext.diagnostics, ...callerDiagnostics },
|
||||
}),
|
||||
};
|
||||
let result: AnalysisError;
|
||||
if (cause instanceof HopperRemoteError)
|
||||
result = new HopperRemoteError(cause.code, cause.safeMessage, {
|
||||
@@ -142,6 +164,41 @@ export const analysisClientFailureForOperation = (
|
||||
? {}
|
||||
: { launcherFailure: cause.launcherFailure }),
|
||||
});
|
||||
else if (cause instanceof ProviderCleanupError) {
|
||||
const { diagnostics, ...outcomeContext } = context;
|
||||
result = new ProviderCleanupError(
|
||||
cause.providerId,
|
||||
cause.cleanupResources,
|
||||
diagnostics ?? {},
|
||||
{
|
||||
...outcomeContext,
|
||||
operation,
|
||||
...(cause.remediationAction === undefined
|
||||
? {}
|
||||
: { remediationAction: cause.remediationAction }),
|
||||
},
|
||||
);
|
||||
} else if (cause instanceof AnalysisTimeoutError)
|
||||
result = new AnalysisTimeoutError(operation, cause.timeoutMs, {
|
||||
...context,
|
||||
...(cause.userMessage === undefined
|
||||
? {}
|
||||
: { userMessage: cause.userMessage }),
|
||||
...(cause.remediationAction === undefined
|
||||
? {}
|
||||
: { remediationAction: cause.remediationAction }),
|
||||
});
|
||||
else if (cause instanceof AnalysisOutputError)
|
||||
result = new AnalysisOutputError(operation, cause.reason, {
|
||||
...context,
|
||||
cleanupOnly: cause.cleanupOnly,
|
||||
...(cause.userMessage === undefined
|
||||
? {}
|
||||
: { userMessage: cause.userMessage }),
|
||||
...(cause.remediationAction === undefined
|
||||
? {}
|
||||
: { remediationAction: cause.remediationAction }),
|
||||
});
|
||||
else if (cause instanceof AnalysisCancelledError)
|
||||
result = new AnalysisCancelledError(operation, {
|
||||
...context,
|
||||
@@ -202,5 +259,5 @@ export const analysisClientFailureForOperation = (
|
||||
});
|
||||
return cause.cleanupIncomplete
|
||||
? result.bindCleanupDependencies([cause])
|
||||
: result;
|
||||
: result.resolveCleanup();
|
||||
};
|
||||
Reference in new issue
Block a user