Files
reverse-engineering-anything/src/application/AnalysisContextQueries.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

431 lines
15 KiB
TypeScript

import type {
AnalysisExecution,
AnalysisOperation,
AnalysisOperationPort,
} from "./AnalysisProvider.js";
import type { AnalysisError } from "../domain/analysisErrorBase.js";
import {
AnalysisCancelledError,
AnalysisCapabilityUnavailableError,
AnalysisInputError,
AnalysisOutputError,
AnalysisResourceConstraintError,
} from "../domain/analysisErrorCore.js";
import {
omitUnretainedEvidenceFromError,
projectAnalysisError,
unretainedEvidenceCandidates,
type AnalysisErrorProjection,
} from "../domain/analysisErrorProjection.js";
import { analysisErrorWithCleanupFailure } from "../domain/analysisErrorCleanup.js";
import { analysisErrorWithCancellation } from "../domain/analysisErrorCancellation.js";
import { EvidenceIntegrityError } from "../domain/evidenceErrors.js";
import type { JsonValue } from "../domain/jsonValue.js";
import { analysisBookmarkSchema } from "../domain/native/nativeInventory.js";
import { parseContainingProcedureResolution } from "../domain/native/nativeObservation.js";
import { nativeAddressCoordinateInputSchema } from "../domain/native/nativeAddress.js";
import { resolveAnalysisDocument } from "./AnalysisDocument.js";
import { estimateJsonStorage } from "./JsonAuthenticationHeadroom.js";
import { err, ok, type Result } from "../domain/result.js";
type Facet =
| { readonly state: "available"; readonly value: JsonValue }
| {
readonly state: "unavailable";
readonly reason: string;
readonly remediation: string;
readonly error?: AnalysisErrorProjection;
};
type AddressContext = {
readonly address: string;
readonly document: string;
readonly name: Facet;
readonly procedure: Facet;
readonly comment: Facet;
readonly inline_comment: Facet;
readonly bookmarks: Facet;
};
/** Facets inspected after an explicit or provider-resolved document is known. */
export const ADDRESS_CONTEXT_FACET_OPERATIONS = [
"address_name",
"resolve_containing_procedure",
"comment",
"inline_comment",
"list_bookmarks",
] as const satisfies readonly AnalysisOperation[];
const parameters = (
document: string | undefined,
address?: string,
): Readonly<Record<string, JsonValue>> => ({
...(document === undefined ? {} : { document }),
...(address === undefined ? {} : { address }),
});
const executionOptions = (
signal: AbortSignal | undefined,
): { readonly signal?: AbortSignal } =>
signal === undefined ? {} : { signal };
/** Compose the provider's volatile selection state into one coherent query. */
export const getNavigationContext = async (
analysis: AnalysisOperationPort,
input: { readonly document?: string | undefined },
signal?: AbortSignal,
): Promise<Result<JsonValue, AnalysisError>> => {
const document =
input.document ??
(await analysis.execute("current_document", {}, executionOptions(signal)));
if (typeof document !== "string" && !document.ok) return document;
const documentName =
typeof document === "string" ? document : document.value.result;
if (typeof documentName !== "string")
return err(
new AnalysisOutputError(
"current_document",
"expected a document identity string",
),
);
const documentArgs = parameters(documentName);
const address = await analysis.execute(
"current_address",
documentArgs,
executionOptions(signal),
);
if (!address.ok) return address;
if (typeof address.value.result !== "string")
return err(
new AnalysisOutputError("current_address", "expected an address string"),
);
const procedure = await analysis.execute(
"resolve_containing_procedure",
parameters(documentName, address.value.result),
executionOptions(signal),
);
if (!procedure.ok) return procedure;
const resolution = parseContainingProcedureResolution(procedure.value.result);
if (!resolution.ok) return resolution;
return ok({
document: documentName,
address: address.value.result,
procedure: resolution.value.found ? resolution.value.procedure.name : null,
});
};
/** Preserve other facets and smaller partials if unretained Evidence cannot fit. */
export function* omitContextEvidenceForDelivery(
context: AddressContext,
limit: {
readonly constraint: "receive-buffer" | "string-length";
readonly bytesAtLeast: number;
},
budgetBytes: number,
): Generator<AddressContext> {
const names = [
"name",
"procedure",
"comment",
"inline_comment",
"bookmarks",
] as const;
const candidates = names.flatMap((name) => {
const facet = context[name];
if (facet.state !== "unavailable" || facet.error === undefined) return [];
return [...unretainedEvidenceCandidates(facet.error)].map((candidate) => {
const size = estimateJsonStorage(candidate.value, {
maximumValueBytes: budgetBytes,
includeSerialized: false,
}).valueBytes;
return { name, candidate, size };
});
});
candidates.sort((left, right) => right.size - left.size);
let current = context;
for (const { name, candidate } of candidates) {
const facet = current[name];
if (facet.state !== "unavailable" || facet.error === undefined) continue;
const compact = omitUnretainedEvidenceFromError(
facet.error,
limit,
candidate,
);
if (compact === undefined) continue;
current = {
...current,
[name]: {
...facet,
remediation: compact.remediation.action,
error: compact,
},
};
yield current;
}
}
/** Inspect bounded provider-neutral facets at one explicit reproducible address. */
export const inspectAddressContext = async (
analysis: AnalysisOperationPort,
input: { readonly address: string; readonly document?: string | undefined },
signal?: AbortSignal,
): Promise<Result<AddressContext, AnalysisError>> => {
const document = await resolveAnalysisDocument(
analysis,
input.document,
signal,
);
if (!document.ok) return document;
const args = parameters(document.value, input.address);
const operations = ADDRESS_CONTEXT_FACET_OPERATIONS;
const completed: Result<AnalysisExecution, AnalysisError>[] = [];
if (signal?.aborted === true)
return err(
new AnalysisCancelledError("inspect_address_context", {
cause: signal.reason,
}),
);
let signalCancellation: AnalysisCancelledError | undefined;
const cancelled = () => {
signalCancellation = new AnalysisCancelledError("inspect_address_context", {
cause: signal?.reason,
});
completed.push(err(signalCancellation));
};
signal?.addEventListener("abort", cancelled, { once: true });
let results: Result<AnalysisExecution, AnalysisError>[];
try {
results = await Promise.all(
operations.map(async (operation) => {
const result = await analysis.execute(
operation,
operation === "list_bookmarks" ? parameters(document.value) : args,
executionOptions(signal),
);
completed.push(result);
return result;
}),
);
} finally {
signal?.removeEventListener("abort", cancelled);
}
const fatal = contextFailure(completed, signalCancellation);
if (fatal !== undefined) return err(fatal);
const name = facet(results[0], operations[0]);
const procedureResult = results[1];
const resolution = procedureResult?.ok
? parseContainingProcedureResolution(procedureResult.value.result)
: procedureResult;
const procedure: Facet = resolution?.ok
? { state: "available", value: resolution.value }
: unavailableFacet(resolution?.error, operations[1]);
const entry =
resolution?.ok && resolution.value.found
? resolution.value.procedure.address
: undefined;
const comment = commentFacet(results[2], operations[2], entry);
const inlineComment = commentFacet(results[3], operations[3], entry);
const bookmarks = facet(results[4], operations[4]);
return ok({
address: input.address,
document: document.value,
name,
procedure,
comment,
inline_comment: inlineComment,
bookmarks:
bookmarks.state === "available"
? matchingBookmarks(
bookmarks.value,
resolution?.ok ? resolution.value.query_address : input.address,
)
: bookmarks,
});
};
const commentFacet = (
result: Result<AnalysisExecution, AnalysisError> | undefined,
operation: "comment" | "inline_comment",
entry: string | undefined,
): Facet => {
if (
result?.ok === false &&
result.error instanceof AnalysisCapabilityUnavailableError &&
!result.error.retryable &&
result.error.remediationAction === undefined &&
result.error.continuingCallerGuidance?.remediationAction === undefined &&
entry !== undefined
) {
return {
state: "unavailable",
reason: result.error.message,
remediation: `If analyze_function is available for this same document, call it with ${JSON.stringify({ procedure: entry })}; use its summary's parent_evidence_id with inspect_analysis_view's native comments facet. Check facet availability, pagination, and the exact requested address; an omitted comment does not establish absence.`,
};
}
return facet(result, operation);
};
const facet = (
result: Result<AnalysisExecution, AnalysisError> | undefined,
operation: AnalysisOperation,
): Facet =>
result?.ok === true
? { state: "available", value: result.value.result }
: unavailableFacet(result?.error, operation);
const unavailableFacet = (
error: AnalysisError | undefined,
operation: AnalysisOperation,
): Facet => {
if (error instanceof AnalysisCapabilityUnavailableError) {
const projected = projectAnalysisError(error);
return {
state: "unavailable",
reason: error.userMessage ?? error.message,
remediation:
error.retryable ||
error.remediationAction !== undefined ||
error.fallbackRemediationAction !== undefined ||
error.continuingCallerGuidance?.remediationAction !== undefined
? projected.remediation.action
: "Review provider availability in binary_session with detail complete and select another supported provider if appropriate. After switching, if a name is known and search_procedures is available, use it to find candidates and verify their identity and coordinates.",
};
}
if (
error instanceof AnalysisInputError ||
error instanceof AnalysisResourceConstraintError ||
(error instanceof EvidenceIntegrityError &&
error.sourcePublicationRecovery !== undefined)
) {
const projected = projectAnalysisError(error);
if (
projected.code === "invalid_request" ||
projected.code === "resource_constraint" ||
projected.code === "evidence_integrity_mismatch"
)
return {
state: "unavailable",
reason: projected.message,
remediation: projected.remediation.action,
error: projected,
};
}
return {
state: "unavailable",
reason: error?.message ?? `${operation} returned no result`,
remediation: `Call ${operation} directly to inspect provider availability and diagnostics.`,
};
};
const isRequestLevelFailure = (error: AnalysisError): boolean =>
error._tag === "AnalysisCancelledError" ||
error._tag === "AnalysisTimeoutError" ||
error._tag === "NoBinaryOpenError" ||
(error.sourcePublicationRecovery !== undefined &&
!(error instanceof AnalysisInputError) &&
!(error instanceof AnalysisResourceConstraintError) &&
!(error instanceof EvidenceIntegrityError));
/** Wait for all facets and retain every unresolved cleanup owner. */
const contextFailure = (
results: readonly Result<AnalysisExecution, AnalysisError>[],
signalCancellation?: AnalysisCancelledError,
): AnalysisError | undefined => {
const failures = [
...new Set(results.flatMap((result) => (result.ok ? [] : [result.error]))),
];
// A provider's cancellation for this exact signal carries its own diagnostic
// and cleanup facts. Attribute that returned error to the abort event rather
// than replacing it with our generic observation of the same signal.
const marker =
signalCancellation === undefined
? -1
: failures.indexOf(signalCancellation);
if (marker >= 0) {
const reported = failures.findIndex(
(failure, index) =>
index > marker &&
failure instanceof AnalysisCancelledError &&
failure.cause === signalCancellation?.cause,
);
if (reported >= 0) {
failures[marker] = failures[reported]!;
failures.splice(reported, 1);
}
}
const first = failures.find(
(failure) => isRequestLevelFailure(failure) || failure.cleanupIncomplete,
);
if (first === undefined) return undefined;
let combined = first;
for (const failure of failures) {
if (failure === first) continue;
if (failure instanceof AnalysisCancelledError)
combined = analysisErrorWithCancellation(combined, failure);
if (failure.cleanupIncomplete) {
const previous = combined;
combined = analysisErrorWithCleanupFailure(
previous,
failure,
"inspect_address_context",
).bindCleanupDependencies([previous, failure]);
} else if (
!(failure instanceof AnalysisCancelledError) &&
(isRequestLevelFailure(failure) ||
failure.sourcePublicationRecovery !== undefined)
) {
combined.retainSecondaryFailure(failure);
}
}
return combined;
};
const matchingBookmarks = (value: JsonValue, address: string): Facet => {
if (!Array.isArray(value))
return {
state: "unavailable",
reason: "list_bookmarks returned malformed output; expected an array.",
remediation:
"Retry list_bookmarks directly and report the provider output shape if it remains malformed.",
};
const parsed = analysisBookmarkSchema.array().safeParse(value);
if (!parsed.success)
return {
state: "unavailable",
reason:
"list_bookmarks returned malformed entries; expected an address string and a string or null name.",
remediation:
"Retry list_bookmarks directly and report the provider output shape if it remains malformed.",
};
const target = nativeAddressCoordinateInputSchema.safeParse(address);
const matching = [];
for (const bookmark of parsed.data) {
if (bookmark.address === address) {
matching.push(bookmark);
continue;
}
const candidate = nativeAddressCoordinateInputSchema.safeParse(
bookmark.address,
);
if (
!target.success ||
!candidate.success ||
(target.data.offset === candidate.data.offset &&
target.data.space !== candidate.data.space)
)
return {
state: "unavailable",
reason:
"Bookmark address identity could not be compared with the selected address.",
remediation:
"Call list_bookmarks directly to inspect the provider addresses.",
};
if (
target.data.offset === candidate.data.offset &&
target.data.space === candidate.data.space
)
matching.push(bookmark);
}
return { state: "available", value: matching };
};