Files
reverse-engineering-anything/src/application/DirectAnalysisCleanup.ts
T
morlutoandCursor Agent 88cf498036 fix!: preserve results when source publication fails (#8444)
* fix: retain address context cleanup failures

* fix!: preserve address context input errors

BREAKING CHANGE: Strict clients that cached the previous full inspect_address_context output schema must refresh tools/list and accept the typed error field on unavailable facets.

* test: use session contract for address context

* fix!: preserve address context resource recovery

BREAKING CHANGE: Clients validating unavailable context facets against a cached strict full output schema must refresh tools/list to accept resource_constraint errors.

* fix: expose authenticated records refused by the evidence ledger (#8426)

* fix: preserve completed evidence on retention refusal

* fix: preserve context when unretained evidence exceeds delivery limits

* fix(mcp): recover oversized context from session evidence

* fix(evidence): preserve completed atomic write results

* fix: retain completed results when source publication fails

* test: cover bounded recovery with unknown retention

* fix(evidence): preserve recovery identity and finalization facts

* fix(evidence): clarify retention of omitted observations

* test(evidence): compare omission guidance with its owner

* fix(evidence): preserve completed composed workflows

* fix(evidence): clarify unretained recovery export

* test(evidence): cover mixed recovery guidance

* fix(evidence): preserve caller scope in secondary failures

* refactor(evidence): remove superseded cleanup projection

* test(evidence): align recovery checks with owners

* fix: preserve cleanup on partial recovery limits

* test: use owners for diagnostic wording

* fix: admit completed analysis before evidence construction

* fix: preserve selected paths when reusing evidence

* fix(cli): preserve caller path in unretained evidence

* fix(evidence): preserve recovery metadata and admission bounds

* refactor: consolidate evidence closure imports

* style(test): format CLI help default annotations (#9280)

* test(mcp): keep recovery checks focused on contract facts

* fix(evidence): preserve feature trace replay revision

* test: align evidence recovery checks with ownership

* test: align partial recovery and cleanup diagnostics

* fix(evidence): preserve nested partials during cleanup

* fix(evidence): respect outer recovery ownership

* fix(capture): discard rejected nested observations

* test(frida): assert inline evidence recovery

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-11-09 02:41:54 +08:00

194 lines
6.2 KiB
TypeScript

import {
sourcePublicationRecoveryOf,
type AnalysisError,
} from "../domain/analysisErrorBase.js";
import {
childFailureDiagnostics,
projectAnalysisError,
} from "../domain/analysisErrorProjection.js";
import {
analysisErrorUserMessage,
composedCallerGuidance,
} from "../domain/analysisErrorPresentation.js";
import { jsonValueSchema, type JsonValue } from "../domain/jsonValue.js";
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
import { err, type Result } from "../domain/result.js";
import type { AnalysisExecution } from "./AnalysisProvider.js";
import type { BinarySession } from "./binary/BinarySession.js";
import { analysisErrorWithCleanupFailure } from "../domain/analysisErrorCleanup.js";
import {
tryParseEvidence,
withEvidenceCallerInputPath,
} from "../domain/evidence.js";
/**
* Project a one-shot failure for its invocation, which has released its
* provider session and runtime settings by the time the caller reads it.
*/
export const projectInvocationFailure = (
error: AnalysisError,
): ReturnType<typeof projectAnalysisError> =>
projectAnalysisError(error, "invocation");
/** Add the actual outer request path to authenticated partial Evidence. */
export const projectDirectAnalysisFailure = (
error: AnalysisError,
callerInputPath: string,
): ReturnType<typeof projectAnalysisError> => {
const projection = projectInvocationFailure(error);
const partial = projection.details?.partial_observation;
const wrapped =
typeof partial === "object" &&
partial !== null &&
!Array.isArray(partial) &&
partial.kind === "unretained-evidence";
const observation = tryParseEvidence(wrapped ? partial.evidence : partial);
const rebound =
observation === undefined
? undefined
: withEvidenceCallerInputPath(observation, callerInputPath);
const completed = sourcePublicationRecoveryOf(error)?.completed;
const completedWithPath =
completed?.kind === "unretained-evidence" ||
completed?.kind === "inline-evidence"
? {
...completed,
evidence: withEvidenceCallerInputPath(
completed.evidence,
callerInputPath,
),
}
: completed?.kind === "unretained-observation"
? {
...completed,
observation: {
...completed.observation,
caller_input_path: callerInputPath,
},
}
: undefined;
if (rebound === undefined && completedWithPath === undefined)
return projection;
return {
...projection,
details: {
...projection.details,
...(rebound === undefined
? {}
: {
partial_observation: wrapped
? { ...partial, evidence: rebound }
: rebound,
}),
...(completedWithPath === undefined
? {}
: { completed_observation: completedWithPath }),
},
};
};
/** Await one-shot cleanup and retain either the outcome or both failures. */
export const withSessionCleanup = async <Value>(
session: BinarySession,
operation: () => Promise<Value>,
onCleanupFailure: (value: Value, error: AnalysisError) => Value,
): Promise<Value> => {
let value: Value;
try {
value = await operation();
} catch (cause: unknown) {
const closed = await session.close();
if (!closed.ok)
throw new AggregateError(
[cause, closed.error],
"Analysis and provider cleanup both failed",
{ cause },
);
throw cause;
}
const closed = await session.close();
return closed.ok ? value : onCleanupFailure(value, closed.error);
};
/** Project typed one-shot outcomes only after execution and cleanup settle. */
export const directAnalysisCleanupFailure = (
result: Result<JsonValue, AnalysisError>,
error: AnalysisError,
callerInputPath?: string,
): JsonValue => {
const project = (failure: AnalysisError) =>
callerInputPath === undefined
? projectInvocationFailure(failure)
: projectDirectAnalysisFailure(failure, callerInputPath);
const cleanup = project(error);
if (!result.ok) {
const primary = project(result.error);
const combined = analysisErrorWithCleanupFailure(result.error, error);
return {
error: "Analysis failed",
...primary,
code: "cleanup_incomplete",
retryable: false,
remediation: cleanup.remediation,
details: {
...primary.details,
// The domain composition owns the union of still-retained resources;
// neither projection may overwrite the other owner's cleanup state.
resources: [...combined.cleanupResources],
cleanup: "incomplete",
execution_failure: primary.details?.execution_failure ?? primary.code,
cleanup_error: cleanup,
},
};
}
return {
error: "Analysis failed",
...cleanup,
code: "cleanup_incomplete",
retryable: false,
details: {
...cleanup.details,
cleanup: "incomplete",
partial_observation: result.value,
},
};
};
/** Preserve managed observations or the original typed failure in diagnostics. */
export const managedAnalysisCleanupFailure = (
result: Result<AnalysisExecution, AnalysisError>,
error: AnalysisError,
): Result<AnalysisExecution, AnalysisError> => {
// A close rejection without a live cleanup state has unknown completion;
// an empty dependency set must not turn that rejection into confirmed cleanup.
if (!result.ok) {
const failure = analysisErrorWithCleanupFailure(result.error, error);
return err(
error.cleanupIncomplete
? failure.bindCleanupDependencies([result.error, error])
: failure,
);
}
const failure = new ProviderAdapterError(
error instanceof ProviderAdapterError ? error.providerId : "rea",
"close_binary",
{
cause: error,
...composedCallerGuidance({ remediation: error }),
cleanup: {
reason: analysisErrorUserMessage(error, "invocation"),
resources: error.cleanupResources,
},
...childFailureDiagnostics(
{ cleanup_error: { error } },
{ partial_observation: jsonValueSchema.parse(result.value) },
),
},
);
return err(
error.cleanupIncomplete
? failure.bindCleanupDependencies([error])
: failure,
);
};