Files
reverse-engineering-anything/src/domain/analysisErrorCore.ts
T
Rishikesh Gowda K K 9044b6fb36 fix(kuna): mark project-dependent operations unavailable on export failure (#8748)
* fix(kuna): mark project-dependent operations unavailable on export failure

* fix(providers): preserve sibling operations in capability error copies

* fix(kuna): propagate sibling unavailability on export fallback

* fix(binary): preserve observed sibling recovery instructions across fallback observations

* fix(binary): build sibling unavailability from structured reasons without rewriting display text

* test(kuna): synchronize fallback test on settled export workspace retirement

* test(kuna): release and join export synchronization barrier across exit paths

* test(kuna): synchronize fallback test on observed export process exit

* test(kuna): gate fallback completion on settled export cache build

* test(kuna): preserve build promise chain and restore spy after teardown
2026-11-06 17:22:02 +08:00

317 lines
11 KiB
TypeScript

import type { JsonValue } from "./jsonValue.js";
import type { FixedAllowance } from "./fixedAllowanceRemediation.js";
import {
AnalysisError,
type AnalysisErrorOptions,
} from "./analysisErrorBase.js";
/** A readable supplied artifact lies outside this operation's supported target formats. */
export class AnalysisUnsupportedTargetError extends AnalysisError {
readonly _tag = "AnalysisUnsupportedTargetError";
/** Refusal-specific recovery, when the producer knows an available workflow. */
readonly remediationAction: string | undefined;
constructor(
readonly operation: string,
readonly path: string,
readonly reason: string,
options?: AnalysisErrorOptions & { readonly remediationAction?: string },
) {
super(`Unsupported target for ${operation} at ${path}: ${reason}`, options);
this.remediationAction = options?.remediationAction;
}
}
/** An analysis or transport resource failure, distinct from malformed input or unsupported coverage. */
export const ANALYSIS_RESOURCE_KINDS = [
"memory",
"cpu",
"file-size",
"transport",
"capacity",
] as const;
/** A measured resource constraint or bounded admission refusal. */
export class AnalysisResourceConstraintError extends AnalysisError {
readonly _tag = "AnalysisResourceConstraintError";
/**
* The one recovery this refusal carries. A fixed allowance and a separate
* remediation sentence do not co-occur; retryability belongs to that same
* object.
*/
readonly recovery: ResourceConstraintRecovery;
constructor(
readonly operation: string,
readonly resource: (typeof ANALYSIS_RESOURCE_KINDS)[number],
readonly reason: string,
readonly reportedLimits: Readonly<Record<string, JsonValue>> | null,
options?: AnalysisErrorOptions & ResourceConstraintRecovery,
) {
super(`Resource constraint during ${operation}: ${reason}`, options);
this.recovery = resourceConstraintRecoveryFrom(options);
}
/** Projection of `recovery`. Absent when recovery names a fixed allowance. */
get remediationAction(): string | undefined {
return this.recovery.remediationAction;
}
/**
* Projection of `recovery`. The fixed format, decoder, or capture allowance
* that refused the request. Worker limits and host resources do not raise
* it, so recovery derives from this allowance rather than from `resource`.
*/
get fixedAllowance(): FixedAllowance | undefined {
return this.recovery.fixedAllowance;
}
/**
* Projection of `recovery`. Whether retrying the same request can succeed.
* A caller-selected bound is deterministic for the same input, so it is not
* retryable even when its resource kind would otherwise be treated as
* admission capacity.
*/
get retryable(): boolean | undefined {
return this.recovery.retryable;
}
}
const resourceConstraintRecoveryFrom = (
options: ResourceConstraintRecovery | undefined,
): ResourceConstraintRecovery => {
if (options?.fixedAllowance !== undefined)
return {
fixedAllowance: options.fixedAllowance,
...(options.retryable === undefined
? {}
: { retryable: options.retryable }),
};
return {
...(options?.remediationAction === undefined
? {}
: { remediationAction: options.remediationAction }),
...(options?.retryable === undefined
? {}
: { retryable: options.retryable }),
};
};
/** Carry one refusal's recovery to an error re-attributed or redacted elsewhere. */
export const resourceConstraintRecovery = (
error: AnalysisResourceConstraintError,
text: (value: string) => string = (value) => value,
): ResourceConstraintRecovery => {
const recovery = error.recovery;
return recovery.fixedAllowance !== undefined
? {
fixedAllowance: {
name: text(recovery.fixedAllowance.name),
kind: recovery.fixedAllowance.kind,
},
...(recovery.retryable === undefined
? {}
: { retryable: recovery.retryable }),
}
: {
...(recovery.remediationAction === undefined
? {}
: { remediationAction: text(recovery.remediationAction) }),
...(recovery.retryable === undefined
? {}
: { retryable: recovery.retryable }),
};
};
/** Producer-specific recovery, or a fixed allowance whose recovery is derived. */
export type ResourceConstraintRecovery =
| {
readonly remediationAction?: string;
readonly fixedAllowance?: never;
readonly retryable?: boolean;
}
| {
readonly fixedAllowance: FixedAllowance;
readonly remediationAction?: never;
readonly retryable?: boolean;
};
/** Provider-neutral invalid analysis input or output at an application boundary. */
export class AnalysisProtocolError extends AnalysisError {
readonly _tag = "AnalysisProtocolError";
}
/** Caller input failed provider-neutral application parsing. */
export class AnalysisInputError extends AnalysisError {
readonly _tag = "AnalysisInputError";
override readonly userMessage: string | undefined;
/** Refusal-specific correction, when the generic input-contract advice cannot fix it. */
readonly remediationAction: string | undefined;
/**
* The refused values satisfy the advertised input contract; the selected
* target lacks the requested fact for them. Re-reading the schema cannot
* correct such a refusal, so it carries no input-contract recovery.
*/
readonly conformsToInputContract: boolean;
constructor(
readonly operation: string,
options?: AnalysisErrorOptions & {
readonly userMessage?: string;
readonly remediationAction?: string;
readonly conformsToInputContract?: boolean;
},
readonly issues: readonly AnalysisInputIssue[] = [],
) {
super(`Invalid analysis input for ${operation}`, options);
this.userMessage = options?.userMessage;
this.remediationAction = options?.remediationAction;
this.conformsToInputContract = options?.conformsToInputContract ?? false;
}
}
/** Secret-safe correction metadata for one rejected caller argument. */
export interface AnalysisInputIssue {
readonly path: readonly (string | number)[];
readonly reason:
| "unknown_argument"
| "missing_argument"
| "invalid_type"
| "out_of_range"
| "invalid_value"
| "invalid_format";
/** Schema-authored correction guidance for cross-field or custom checks. */
readonly message?: string;
readonly expected?: JsonValue;
/** Producer-examined facts about the refused value, such as its location and shape. */
readonly observed?: JsonValue;
readonly minimum?: number;
readonly maximum?: number;
}
/** Refusal-specific context a producer projects with an output failure. */
export interface AnalysisOutputErrorOptions extends AnalysisErrorOptions {
/** Caller-facing explanation for a deterministic, diagnosable refusal. */
readonly userMessage?: string;
readonly remediationAction?: string;
readonly cleanupOnly?: boolean;
}
/** Provider output failed provider-neutral application parsing. */
export class AnalysisOutputError extends AnalysisError {
readonly _tag = "AnalysisOutputError";
override readonly userMessage: string | undefined;
readonly remediationAction: string | undefined;
readonly cleanupOnly: boolean;
constructor(
readonly operation: string,
readonly reason: string,
options?: AnalysisOutputErrorOptions,
) {
super(`Invalid analysis output for ${operation}: ${reason}`, options);
this.userMessage = options?.userMessage;
this.remediationAction = options?.remediationAction;
this.cleanupOnly = options?.cleanupOnly ?? false;
}
}
/** Selected provider cannot execute a declared analysis operation. */
export class AnalysisCapabilityUnavailableError extends AnalysisError {
readonly _tag = "AnalysisCapabilityUnavailableError";
override readonly userMessage: string | undefined;
readonly remediationAction: string | undefined;
readonly siblingOperations: readonly string[] | undefined;
readonly siblingReasons: Readonly<Record<string, string>> | undefined;
/** Incomplete cleanup must settle before the operation can be retried. */
readonly retryable: boolean;
constructor(
readonly providerId: string,
readonly operation: string,
readonly reason: string,
options?: AnalysisErrorOptions & {
readonly userMessage?: string;
readonly remediationAction?: string;
readonly siblingOperations?: readonly string[];
readonly siblingReasons?: Readonly<Record<string, string>>;
readonly retryable?: boolean;
},
) {
super(
`Provider ${providerId} cannot execute ${operation}: ${reason}`,
options,
);
this.userMessage = options?.userMessage;
this.remediationAction = options?.remediationAction;
this.siblingOperations = options?.siblingOperations;
this.siblingReasons = options?.siblingReasons;
this.retryable = options?.retryable === true && !this.cleanupIncomplete;
}
}
/** Caller cancellation won before provider-neutral work completed. */
export class AnalysisCancelledError extends AnalysisError {
readonly _tag = "AnalysisCancelledError";
constructor(
readonly operation: string,
options?: AnalysisErrorOptions,
) {
super(`Analysis operation was cancelled: ${operation}`, options);
}
}
/** Provider-neutral operation exceeded its declared execution deadline. */
export class AnalysisTimeoutError extends AnalysisError {
readonly _tag = "AnalysisTimeoutError";
override readonly userMessage: string | undefined;
readonly remediationAction: string | undefined;
constructor(
readonly operation: string,
readonly timeoutMs: number,
options?: AnalysisErrorOptions & {
/** Provider-specific explanation of which deadline elapsed. */
readonly userMessage?: string;
/** Provider-specific setting or action that changes that deadline. */
readonly remediationAction?: string;
},
) {
super(
`Analysis operation timed out after ${String(timeoutMs)}ms: ${operation}`,
options,
);
this.userMessage = options?.userMessage;
this.remediationAction = options?.remediationAction;
}
}
/** Host filesystem permissions denied the selected read; this is not malformed caller input. */
export class AnalysisAccessDeniedError extends AnalysisError {
readonly _tag = "AnalysisAccessDeniedError";
constructor(
readonly operation: string,
readonly path: string,
readonly systemCode: "EACCES" | "EPERM",
options?: AnalysisErrorOptions,
) {
super(
`Host filesystem read access denied (${systemCode}) for ${path} during ${operation}`,
options,
);
}
}
/** Selected artifact acquisition observed a change, so no stable identity can be reported. */
export class AnalysisArtifactChangedError extends AnalysisError {
readonly _tag = "AnalysisArtifactChangedError";
constructor(
readonly operation: string,
readonly path: string,
readonly reason: string,
options?: AnalysisErrorOptions,
) {
super(`Selected artifact changed during acquisition: ${reason}`, options);
}
}