mirror of
https://github.com/morluto/rea.git
synced 2026-11-12 01:41:21 +00:00
* docs(desktop): clarify gzip scrub limits * fix(setup): own desktop build process cleanup * fix(doctor): compare desktop settings with resolved defaults * fix(desktop): refuse a build runtime whose export takes no task ends Several tasks pass --task-end to the build computer's export, so a runtime packed before it now fails at the start of the capture with the repack fix, as a runtime without synthetic seeds already did, rather than at export. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * style(desktop): format the noisy-record draft files with the pinned oxfmt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(desktop): give the root-array draft test its own describe The records describe grew past the 150-line lint limit with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(doctor): inspect client-recorded desktop runners * fix(doctor): reuse matching runner probes * fix(setup): propagate cancellation to desktop actions * fix(desktop): rewrite a download's launch keys written with spaces around = The desktop entry parser accepts `Exec = notes %F` and an indented `TryExec =notes`, so the install script now matches those keys the same way instead of leaving the original program in place. Adds the feature's tasks.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(desktop): keep why a measured run failed in its solve record A solve that returned an outcome dropped the runner's error, so a run that failed to deploy, solve or verify was retried with no reason recorded. The record, the measure result and difficulty.json now carry it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(setup): stop Hopper on cancel, never report a cancelled setup ready, and keep a failed action's client setting - The Hopper install gets setup's signal: Linux already honoured it, and the macOS installer now stops its downloads and stops before mounting or copying. - runSetup checks the signal after the final doctor and close, so a cancellation there raises instead of returning ready. - A failed pack or agentenv install no longer writes its archive or runner to client configs, so the setting the client already had stays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(setup): keep the completed setup result in a cancellation after cleanup A cancellation during the final doctor or host close carried only the applied actions, dropping each client's result and backup path and the doctor report. It now carries the whole result as setup_result, as doctor's cancellation carries its report. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(desktop): move launch execution to boundary suite * fix(desktop): redact build schema failure output * refactor(desktop): normalize schema failure once * fix(desktop): omit unsafe schema parser snippets * fix(desktop): preserve draft change and selected path evidence * test(desktop): group retained dconf note regressions * fix(desktop): respect archive entry format in draft checks * test(desktop): separate unowned-file draft note cases * fix(desktop): preserve explicitly selected journal documents * fix(xml): avoid scanning selected ancestors per tag * fix(desktop): exclude covered text runs from draft counts * fix: preserve text when drafting desktop checks * fix: restore nested desktop state aliases * refactor: share UUID recognition across draft checks * fix: retain multiline XML attribute context * fix: retain relationship identifiers in task drafts * docs: clarify desktop draft identifier handling * test(desktop): type formatting run cases as pairs * fix(desktop): carry document format into selector evaluation * test(desktop): separate markup boundary regression group * fix(desktop): propagate formats through draft selectors * fix(desktop): switch selector format within archive entries * test(desktop): remove duplicate seed filename case * fix(setup): retain diagnostics when cancellation overlaps failure * refactor(setup): keep command cancellation at its owner * test(setup): verify cancellation at the application boundary * test(desktop): pass JSON format in exported index parity * docs(desktop): clarify task checks before scratch export * fix(desktop): respell selected paths in file URLs * test(desktop): group captured document spelling cases * test(desktop): run installer scripts at boundary * style(desktop): remove trailing blank line * fix(desktop): bound draft preview serialization * fix(desktop): restore selected state aliases * fix(desktop): respect entry format in draft noise * test(desktop): match exported alias evidence at its JSON boundary * fix(desktop): retain app state logs in exported seeds * fix: align desktop numeric seed comparisons * test(desktop): keep one draft exclusion fixture import * test(desktop): retain log eligibility across draft test groups * fix(doctor): resolve empty registered desktop runner * style: fix desktop launch test formatting * style: format native command path schema * fix(desktop): map selected external launch paths * style(desktop): format seed document regressions * test(desktop): group launch URL regressions * fix(desktop): omit redundant nested state aliases * fix(desktop): compare added free text as a whole * fix(desktop): check recompressed seeds as shipped * docs(desktop): clarify recovered seed format * test(desktop): group shipped gzip seed checks * fix: retain quoted identifier text in task drafts * fix: recognize Unicode XML in desktop state readers * fix(desktop): bound nested draft checks * fix(desktop): explain missing arrays in truncated views * fix(desktop): find XML type references in archives * test(desktop): remove duplicated capture assertions * fix(desktop): retain nested omitted text boundaries * style(desktop): format draft value imports * test(desktop): group model and draft value checks * fix(desktop): map captured launch paths once * refactor: remove unused whole-text guard state * refactor: stop passing unused text children * test: align free-text checks with whole-note policy * fix: validate recompressed desktop archive seeds * style(desktop): format archive seed regression * fix(desktop): retain placed-item sibling guards * fix(desktop): preserve archive paths and bound doctor reads * fix(doctor): refuse non-files before descriptor admission * fix(setup): track remaining cleanup resources across retries * test(desktop): specify XML format in heap pairing fixture * fix(setup): skip Hopper launch after cancelled copy * fix: preserve task measurement limits and cleanup * fix(desktop): retain reported solve sandbox cleanup * test(desktop): check retained log seed admission * fix(doctor): query agentenv for model endpoint * fix(doctor): preserve uncertainty for empty endpoint override * fix(desktop): redact runtime schema errors * fix(desktop): normalize captured file URLs * fix(desktop): report seeded files by capture path * fix(desktop): retain startup readiness failure stage * fix(desktop): retain JSON date checks * fix(desktop): preserve archive formatting checks * fix(desktop): preserve explicitly selected journal files * fix(desktop): report build command transport limits * docs(desktop): clarify unreadable dconf versions * test(desktop): remove duplicate history regression * fix(desktop): align XML names with exported reader * fix(desktop): create selected state alias targets * fix(desktop): inspect supported runtime archives * fix(desktop): keep ordinary text checks exact * fix(desktop): verify registered build host paths * fix(desktop): retain record identity in draft checks * test(desktop): preserve text reference numbers * fix(desktop): bound download install input reads * fix(desktop): match placed notes by whole text * test(desktop): match multiple task export results * fix(desktop): retain failed measurement file closes * fix(setup): retain completed actions on cancellation * test(setup): retain cancelled installer result * fix(desktop): scrub XML independently of reader compatibility * fix(desktop): preserve file aliases and reader diagnostics * style(desktop): format review fixes * fix(desktop): inspect archives with registered runner * test(desktop): verify registered archive interpreter * style(desktop): format capture review fixes * style(desktop): format review changes * feat(desktop): target agentenv-framework 0.9.1464 Desktop bundles, setup's --install-agentenv and the doctor's exact-version check now target 0.9.1464, which keeps an exported app's data/get call, holds the computer fixture above boot dialogs, leaves an unresolved setting's build unverified rather than failed, and mounts lock packages instead of copying them into the image. The web environment lane stays on 0.9.1431. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(desktop): retain per-solve sandbox cleanup roots * test(desktop): expect the solve cleanup helper among the shipped agentenv helpers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(desktop): clean up a stopped solve's sandboxes through agentenv 0.9.1482 agentenv-framework 0.9.1482 lists the local sandboxes under a root (local_sandbox_ids) and rebuilds one killed while it wrote its port map for teardown. The solve cleanup helper now terminates each sandbox agentenv lists, keeping its Docker and host-process checks, and reports anything else left under the root. Desktop bundles, setup's --install-agentenv and the doctor's version check target 0.9.1482. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(desktop): verify a killed solve's cleanup once its sandbox root is empty A solve's sandboxes are all local under its private root, and agentenv now keeps a sandbox's folder until its teardown is confirmed. So when the helper ends without a report and REA's cleanup empties that root, the cleanup is verified: a cancelled solve ends as a cancellation and a solve past its deadline as a failed run, rather than as cleanup the measure cannot vouch for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(desktop): target agentenv-framework 0.9.1485 0.9.1485 keeps a local sandbox's folder until its teardown is confirmed, which the solve cleanup's verified result relies on. (0.9.1484 failed to publish.) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(desktop): record a solve stopped before it reports as failed, not as a refused request A helper killed from outside or stopped at its deadline left no report, so the measure refused with invalid input and told the caller to fix its model endpoint. A helper a signal ended now gives a failed solve of unknown cost, which the measure records and stops on; one that exits on its own without a report still ends the measure as a solve that could not run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(desktop): check verifier behavior after selector changes * test(windows): verify rejected writes during cleanup by effect * fix(desktop): preserve selected alias target kinds in exports * fix(cli): retain caller capture paths in environment builds * fix(ghidra): classify oversized endpoint records consistently * test: align model and Evidence expectations with current contracts * fix(desktop): reconcile upstream sandbox cleanup recovery * fix(docs): link cleanup qualification as repository evidence --------- Co-authored-by: morluto <76467478+morluto@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
548 lines
21 KiB
TypeScript
548 lines
21 KiB
TypeScript
import type { PartialTaskMeasurement } from "./desktop/taskDifficulty.js";
|
|
import type { AppleIpsPartialObservation } from "./native/appleIps.js";
|
|
import type { PlaywrightTrace } from "./playwrightTrace/playwrightTrace.js";
|
|
import type { GraphqlContract } from "./apiContract/graphqlContract.js";
|
|
import type { SavedAnalysisSnapshotPartialObservation } from "./savedAnalysisSnapshot.js";
|
|
import type { AnalysisSettingLimit } from "./analysisSettings.js";
|
|
import type { JsonValue } from "./jsonValue.js";
|
|
import type { AnalysisCleanupRetry } from "./analysisErrorRecovery.js";
|
|
import type { EnvironmentBundlePublication } from "./environmentBundleExport.js";
|
|
|
|
/** Stable tags exposed by safe analysis-error projections. */
|
|
const ANALYSIS_ERROR_TAGS = [
|
|
"AnalysisProtocolError",
|
|
"AnalysisInputError",
|
|
"AnalysisAccessDeniedError",
|
|
"AnalysisArtifactChangedError",
|
|
"AnalysisOutputError",
|
|
"AnalysisCapabilityUnavailableError",
|
|
"AnalysisUnsupportedTargetError",
|
|
"AnalysisCancelledError",
|
|
"AnalysisTimeoutError",
|
|
"AnalysisResourceConstraintError",
|
|
"ProviderSelectionError",
|
|
"ProviderAdapterError",
|
|
"BrowserObservationError",
|
|
"ArtifactOperationError",
|
|
"ProcessCaptureError",
|
|
"EvidenceIntegrityError",
|
|
"EvidenceFileError",
|
|
"UnknownRegistryError",
|
|
"HopperTimeoutError",
|
|
"HopperCancelledError",
|
|
"HopperProtocolError",
|
|
"HopperRemoteError",
|
|
"HopperProcessError",
|
|
"HopperStartError",
|
|
"ConfigurationError",
|
|
"NoBinaryOpenError",
|
|
"BinaryTargetError",
|
|
] as const;
|
|
|
|
/** Stable tag for an expected analysis failure. */
|
|
export type AnalysisErrorTag = (typeof ANALYSIS_ERROR_TAGS)[number];
|
|
|
|
/** Retained command output associated with a typed failure; truncation stays explicit. */
|
|
export interface AnalysisCapturedOutput {
|
|
readonly stdout: string;
|
|
readonly stderr: string;
|
|
readonly truncated: boolean;
|
|
readonly stdout_bytes?: number;
|
|
readonly stderr_bytes?: number;
|
|
readonly exit_code?: number | null;
|
|
readonly signal?: string | null;
|
|
}
|
|
|
|
/** Cleanup uncertainty attached to the original typed analysis failure. */
|
|
export interface AnalysisCleanupObservation {
|
|
readonly reason: string;
|
|
readonly resources: readonly string[];
|
|
}
|
|
|
|
/** Publication stopped after a workflow produced its Evidence. Counts describe
|
|
* question occurrences, not distinct Unknown identities. */
|
|
export interface WorkflowUnknownPublication {
|
|
readonly evidence_id: string;
|
|
readonly evidence_retention: "retained" | "unverified";
|
|
readonly total_questions: number;
|
|
readonly created_questions: number;
|
|
readonly existing_questions: number;
|
|
readonly next_question_index: number;
|
|
}
|
|
|
|
/**
|
|
* How long the adapter delivering a failure keeps REA's runtime and provider
|
|
* session for its caller. An MCP connection continues: later calls reuse the
|
|
* session and can update runtime settings. A one-shot invocation, such as a
|
|
* CLI command, releases its provider session and exits before its caller can
|
|
* act; only a new invocation's launch settings or arguments apply.
|
|
*/
|
|
export type AnalysisCallerLifetime = "continuing" | "invocation";
|
|
|
|
/**
|
|
* Producer guidance that holds only for a continuing caller, kept apart from
|
|
* the request's outcome. The error's own message and remediation must remain
|
|
* true after an invocation has released the provider session.
|
|
*/
|
|
export interface AnalysisContinuingCallerGuidance {
|
|
/** Appended to the outcome, such as that the provider session remains usable. */
|
|
readonly userMessage?: string;
|
|
/** Replaces the remediation, such as a same-session runtime setting update. */
|
|
readonly remediationAction?: string;
|
|
}
|
|
|
|
/** Optional provider-neutral context that must survive typed error projection. */
|
|
export interface AnalysisErrorOptions extends ErrorOptions {
|
|
/** Typed primary retained when a later lifecycle failure composes this error. */
|
|
readonly cleanupPrimary?: AnalysisError | undefined;
|
|
/** Producer-owned, caller-safe facts retained through every typed projection. */
|
|
readonly diagnostics?: Readonly<Record<string, JsonValue>> | undefined;
|
|
readonly limit?: AnalysisSettingLimit | undefined;
|
|
readonly continuingCallerGuidance?:
|
|
| AnalysisContinuingCallerGuidance
|
|
| undefined;
|
|
/**
|
|
* The same diagnostics recorded for an invocation caller, present only when
|
|
* an embedded child failure projects differently for that lifetime.
|
|
*/
|
|
readonly invocationDiagnostics?:
|
|
| Readonly<Record<string, JsonValue>>
|
|
| undefined;
|
|
readonly capturedOutput?: AnalysisCapturedOutput | undefined;
|
|
readonly cleanup?: AnalysisCleanupObservation | undefined;
|
|
readonly partialObservation?: AnalysisPartialObservation | undefined;
|
|
}
|
|
|
|
/** Exact cleanup owner that can attribute its currently unresolved resources. */
|
|
export interface AnalysisCleanupResourceOwner {
|
|
readonly cleanupResources: readonly string[] | undefined;
|
|
}
|
|
|
|
/** Result completed before its source Evidence could be published. */
|
|
export type SourcePublicationCompletion =
|
|
| {
|
|
readonly kind: "unretained-evidence";
|
|
readonly source_closure: "not-established";
|
|
readonly evidence: Evidence;
|
|
}
|
|
| {
|
|
readonly kind: "retained-evidence";
|
|
readonly source_closure: "not-established";
|
|
readonly evidence_id: string;
|
|
}
|
|
| {
|
|
readonly kind: "unretained-observation";
|
|
readonly source_closure: "not-established";
|
|
readonly observation: Readonly<Record<string, JsonValue>>;
|
|
}
|
|
| {
|
|
readonly kind: "inline-evidence";
|
|
readonly retention: "one-shot-session" | "unknown";
|
|
readonly source_closure: "not-established";
|
|
readonly evidence: Evidence;
|
|
}
|
|
| {
|
|
readonly kind: "omitted";
|
|
readonly source_closure: "not-established";
|
|
readonly reason:
|
|
| "primary-authentication-failed"
|
|
| "insufficient-authentication-headroom"
|
|
| "recovery-input-invalid";
|
|
};
|
|
|
|
export interface SourcePublicationRecovery {
|
|
readonly source: AnalysisPartialObservation | undefined;
|
|
readonly completed: SourcePublicationCompletion;
|
|
}
|
|
|
|
/** Base class for expected analysis, provider, and session failures. */
|
|
export abstract class AnalysisError extends Error {
|
|
limit: Readonly<AnalysisSettingLimit> | undefined;
|
|
/** Bind the accepted setting at its producing boundary. */
|
|
bindLimit(limit: AnalysisSettingLimit): this {
|
|
this.limit = Object.freeze({ ...limit });
|
|
return this;
|
|
}
|
|
readonly #cleanupPrimary: AnalysisError | undefined;
|
|
// Undefined means unbound; a bound empty set records confirmed completion.
|
|
#cleanupDependencies: Set<AnalysisError> | undefined;
|
|
#cleanupResourceProjection:
|
|
| { readonly cleanupResources: readonly string[] }
|
|
| undefined;
|
|
#cleanupDependents: WeakRef<AnalysisError>[] = [];
|
|
readonly diagnostics: Readonly<Record<string, JsonValue>> | undefined;
|
|
readonly continuingCallerGuidance:
|
|
| Readonly<AnalysisContinuingCallerGuidance>
|
|
| undefined;
|
|
readonly invocationDiagnostics:
|
|
| Readonly<Record<string, JsonValue>>
|
|
| undefined;
|
|
#capturedOutput: AnalysisCapturedOutput | undefined;
|
|
get capturedOutput(): AnalysisCapturedOutput | undefined {
|
|
return this.#capturedOutput;
|
|
}
|
|
readonly cleanup: AnalysisCleanupObservation | undefined;
|
|
cleanupIncomplete: boolean;
|
|
#ownedCleanupResources: readonly string[];
|
|
#cleanupResourceOwner: AnalysisCleanupResourceOwner | undefined;
|
|
#cleanupCompletionOwner: AnalysisCleanupResourceOwner | undefined;
|
|
#cleanupRetry: AnalysisCleanupRetry | undefined;
|
|
#secondaryFailures: AnalysisError[] = [];
|
|
constructor(message: string, options?: AnalysisErrorOptions) {
|
|
super(message, options);
|
|
this.#cleanupPrimary = options?.cleanupPrimary;
|
|
if (options?.limit !== undefined) this.bindLimit(options.limit);
|
|
this.diagnostics =
|
|
options?.diagnostics === undefined
|
|
? undefined
|
|
: structuredClone(options.diagnostics);
|
|
this.continuingCallerGuidance =
|
|
options?.continuingCallerGuidance === undefined
|
|
? undefined
|
|
: Object.freeze({ ...options.continuingCallerGuidance });
|
|
this.invocationDiagnostics =
|
|
options?.invocationDiagnostics === undefined
|
|
? undefined
|
|
: structuredClone(options.invocationDiagnostics);
|
|
this.#capturedOutput =
|
|
options?.capturedOutput === undefined
|
|
? undefined
|
|
: { ...options.capturedOutput };
|
|
this.cleanup = options?.cleanup;
|
|
this.cleanupIncomplete = options?.cleanup !== undefined;
|
|
this.#ownedCleanupResources = [...(options?.cleanup?.resources ?? [])];
|
|
this.partialObservation = options?.partialObservation;
|
|
}
|
|
abstract readonly _tag: AnalysisErrorTag;
|
|
readonly userMessage: string | undefined = undefined;
|
|
readonly userCategory: "cancelled" | undefined = undefined;
|
|
readonly executionFailure: string | undefined = undefined;
|
|
partialObservation: AnalysisPartialObservation | undefined;
|
|
sourcePublicationRecovery: SourcePublicationRecovery | undefined;
|
|
workflowUnknownPublication: WorkflowUnknownPublication | undefined;
|
|
|
|
/** Keep the failed source and completed result with their error owner. */
|
|
retainSourcePublication(completed: SourcePublicationCompletion): this {
|
|
this.sourcePublicationRecovery = {
|
|
source: this.partialObservation,
|
|
completed,
|
|
};
|
|
this.partialObservation = undefined;
|
|
return this;
|
|
}
|
|
|
|
get cleanupPrimary(): AnalysisError | undefined {
|
|
return this.#cleanupPrimary;
|
|
}
|
|
/** Later typed failures retained without changing the primary error cause. */
|
|
get secondaryFailures(): readonly AnalysisError[] {
|
|
return [...this.#secondaryFailures];
|
|
}
|
|
readonly cleanupReport: ProcessCaptureCleanupReport | undefined = undefined;
|
|
|
|
/** Live dependencies own current resource attribution; cleanup keeps history. */
|
|
get cleanupResources(): readonly string[] {
|
|
if (this.#cleanupDependencies === undefined)
|
|
return (
|
|
this.#cleanupResourceOwner?.cleanupResources ??
|
|
this.#ownedCleanupResources
|
|
);
|
|
const pending = [...this.#cleanupDependencies].filter(
|
|
(dependency) => dependency.cleanupIncomplete,
|
|
);
|
|
if (pending.length === 0) return [];
|
|
if (this.#cleanupResourceProjection !== undefined)
|
|
return this.#cleanupResourceProjection.cleanupResources;
|
|
return [
|
|
...new Set(pending.flatMap((dependency) => dependency.cleanupResources)),
|
|
];
|
|
}
|
|
|
|
/** Whether cleanup follows an explicit owner or dependent errors. */
|
|
get hasCleanupOwner(): boolean {
|
|
return (
|
|
this.#cleanupResourceOwner !== undefined ||
|
|
this.#cleanupCompletionOwner !== undefined ||
|
|
this.#cleanupDependencies !== undefined
|
|
);
|
|
}
|
|
|
|
/** Bind attribution only to the producer that owns the complete cleanup set. */
|
|
bindCleanupResourceOwner(owner: AnalysisCleanupResourceOwner): this {
|
|
this.#cleanupResourceOwner = owner;
|
|
return this;
|
|
}
|
|
|
|
/** Observe exact completion without replacing the producer's resource facts. */
|
|
bindCleanupCompletionOwner(owner: AnalysisCleanupResourceOwner): this {
|
|
this.#cleanupCompletionOwner = owner;
|
|
return this;
|
|
}
|
|
|
|
/** Caller retry for the live cleanup; undefined once cleanup completes. */
|
|
get cleanupRetry(): AnalysisCleanupRetry | undefined {
|
|
return this.cleanupIncomplete ? this.#cleanupRetry : undefined;
|
|
}
|
|
|
|
/** Bind only from the owner retaining every reported resource for this retry. */
|
|
bindCleanupRetry(retry: AnalysisCleanupRetry): this {
|
|
this.#cleanupRetry = Object.freeze({ ...retry });
|
|
return this;
|
|
}
|
|
|
|
/** Initialize resource facts supplied by a more specific error producer. */
|
|
protected setCleanupResources(resources: readonly string[]): void {
|
|
this.#ownedCleanupResources = [...resources];
|
|
}
|
|
|
|
/** Retain diagnostic facts without replacing the original typed error. */
|
|
retainDiagnostics(diagnostics: Readonly<Record<string, JsonValue>>): this {
|
|
Object.assign(this, {
|
|
diagnostics: structuredClone({ ...this.diagnostics, ...diagnostics }),
|
|
...(this.invocationDiagnostics === undefined
|
|
? {}
|
|
: {
|
|
invocationDiagnostics: structuredClone({
|
|
...this.invocationDiagnostics,
|
|
...diagnostics,
|
|
}),
|
|
}),
|
|
});
|
|
return this;
|
|
}
|
|
|
|
/** Retain facts on every primary that cleanup settlement can reveal. */
|
|
retainPrimaryDiagnostics(
|
|
diagnostics: Readonly<Record<string, JsonValue>>,
|
|
): this {
|
|
for (
|
|
let error: AnalysisError | undefined = this;
|
|
error !== undefined;
|
|
error = error.cleanupPrimary
|
|
)
|
|
error.retainDiagnostics(diagnostics);
|
|
return this;
|
|
}
|
|
|
|
/** Diagnostics recorded for a caller with this lifetime. */
|
|
diagnosticsFor(
|
|
callerLifetime: AnalysisCallerLifetime,
|
|
): Readonly<Record<string, JsonValue>> | undefined {
|
|
return callerLifetime === "invocation"
|
|
? (this.invocationDiagnostics ?? this.diagnostics)
|
|
: this.diagnostics;
|
|
}
|
|
|
|
retainSecondaryFailure(
|
|
failure: AnalysisError,
|
|
diagnostics?: Readonly<Record<string, JsonValue>>,
|
|
): this {
|
|
// Secondary resources remain owned by their cleanup lifecycle; preserve
|
|
// the primary error's live cleanup status and original output.
|
|
if (diagnostics !== undefined) this.retainDiagnostics(diagnostics);
|
|
this.#secondaryFailures.push(failure);
|
|
return this;
|
|
}
|
|
|
|
/** Keep a composed error incomplete until all explicitly owned source errors resolve. */
|
|
bindCleanupDependencies(
|
|
dependencies: readonly AnalysisError[],
|
|
resourceProjection?: { readonly cleanupResources: readonly string[] },
|
|
): this {
|
|
const pending = (this.#cleanupDependencies ??= new Set<AnalysisError>());
|
|
if (resourceProjection !== undefined)
|
|
this.#cleanupResourceProjection = resourceProjection;
|
|
for (const dependency of dependencies) {
|
|
if (!dependency.cleanupIncomplete || pending.has(dependency)) continue;
|
|
pending.add(dependency);
|
|
dependency.#cleanupDependents = [
|
|
...dependency.#cleanupDependents.filter(
|
|
(reference) => reference.deref() !== undefined,
|
|
),
|
|
new WeakRef(this),
|
|
];
|
|
}
|
|
if (pending.size === 0) this.resolveCleanup();
|
|
return this;
|
|
}
|
|
|
|
/** Clear live cleanup status only after its owning resource confirms retry success. */
|
|
resolveCleanup(): this {
|
|
if (
|
|
[...(this.#cleanupDependencies ?? [])].some(
|
|
(dependency) => dependency.cleanupIncomplete,
|
|
)
|
|
)
|
|
return this;
|
|
this.#cleanupDependencies?.clear();
|
|
this.cleanupIncomplete = false;
|
|
for (const reference of this.#cleanupDependents)
|
|
reference.deref()?.resolveCleanup();
|
|
this.#cleanupDependents = [];
|
|
return this;
|
|
}
|
|
|
|
/** Retain a completed observation when a later step fails. */
|
|
retainPartialObservation(observation: AnalysisPartialObservation): this {
|
|
this.partialObservation ??= observation;
|
|
return this;
|
|
}
|
|
|
|
/** Keep command output observed before this error without replacing the owner. */
|
|
retainCapturedOutput(output: AnalysisCapturedOutput): this {
|
|
this.#capturedOutput ??= { ...output };
|
|
return this;
|
|
}
|
|
|
|
/** Preserve the exact committed prefix when automatic question publication stops. */
|
|
retainWorkflowUnknownPublication(
|
|
publication: WorkflowUnknownPublication,
|
|
): this {
|
|
this.workflowUnknownPublication ??= Object.freeze({ ...publication });
|
|
return this;
|
|
}
|
|
}
|
|
|
|
/** Confirmed cleanup reveals the typed primary; unresolved ownership stays visible. */
|
|
export const resolvedAnalysisError = (error: AnalysisError): AnalysisError => {
|
|
while (!error.cleanupIncomplete && error.cleanupPrimary !== undefined)
|
|
error = error.cleanupPrimary;
|
|
return error;
|
|
};
|
|
|
|
/** Original operation semantics remain distinct from the live cleanup envelope. */
|
|
export const primaryAnalysisError = (error: AnalysisError): AnalysisError => {
|
|
while (error.cleanupPrimary !== undefined) error = error.cleanupPrimary;
|
|
return error;
|
|
};
|
|
|
|
/** Find the completed publication outcome owned anywhere in a cleanup chain. */
|
|
export const sourcePublicationRecoveryOf = (
|
|
error: AnalysisError,
|
|
): SourcePublicationRecovery | undefined => {
|
|
for (
|
|
let owner: AnalysisError | undefined = error;
|
|
owner !== undefined;
|
|
owner = owner.cleanupPrimary
|
|
)
|
|
if (owner.sourcePublicationRecovery !== undefined)
|
|
return owner.sourcePublicationRecovery;
|
|
return undefined;
|
|
};
|
|
import type {
|
|
PartialProcessCaptureObservation,
|
|
ProcessCaptureCleanupReport,
|
|
} from "./process/processCapture.js";
|
|
import type { FileOffsetPartialObservation } from "./native/fileOffsetPartialObservation.js";
|
|
import type { NativeCallPartialObservation } from "./native/nativeCallPartialObservation.js";
|
|
import type { HopperCallerReconciliationPartial } from "./native/hopperCallerReconciliation.js";
|
|
import type { ElectronActivePartialObservation } from "./javascript/electronActiveObservation.js";
|
|
import type { JavaScriptRuntimeObservation } from "./javascript/javascriptRuntimeObservation.js";
|
|
import type {
|
|
ArtifactInventoryPartialObservation,
|
|
ArtifactExtractionPartialObservation,
|
|
} from "./artifactPartialObservation.js";
|
|
import type { WebScriptExportPartialObservation } from "./webScriptExport.js";
|
|
import type { JavaScriptRecoveryPartialObservation } from "./javascript/javascriptRecovery.js";
|
|
import type { BrowserScenarioPartialObservation } from "./browserScenarioCapture.js";
|
|
import type { Evidence } from "./evidence.js";
|
|
import type { ElectronPagePartialObservation } from "./javascript/electronObservation.js";
|
|
import type { AndroidPartialObservation } from "./android/androidPartialObservation.js";
|
|
import type { FirmwarePartialObservation } from "./firmware/firmwareAnalysis.js";
|
|
import type {
|
|
AdbActivityStartPartialObservation,
|
|
AdbAcquiredFilePartialObservation,
|
|
AdbPackagePullPartialObservation,
|
|
AdbDisplayPartialObservation,
|
|
} from "./adb/adbDeviceAnalysis.js";
|
|
import type {
|
|
InspectMacho,
|
|
InspectSignature,
|
|
} from "./native/nativeInspection.js";
|
|
import type { NativeUiObservationResult } from "./native/nativeUiObservation.js";
|
|
import type { BinaryLayout } from "./native/binaryLayout.js";
|
|
import type {
|
|
RecordedCrash,
|
|
RecordedCrashPartialObservation,
|
|
} from "./native/recordedCrash.js";
|
|
import type { WebPageInspection } from "./browserObservationSchemas.js";
|
|
import type {
|
|
WebBundleAcquisitionPartialObservation,
|
|
WebBundleAnalysis,
|
|
WebSourceMapFetchPartialObservation,
|
|
} from "./webBundleAnalysis.js";
|
|
import type { WebExecutionObservation } from "./webExecution.js";
|
|
import type { WebSourceMapReport } from "./webSourceLocation.js";
|
|
import type { WebEventListenersObservation } from "./webEventListeners.js";
|
|
|
|
import type { NativeLoadImagePartialObservation } from "./native/nativeLoadImage.js";
|
|
import type { NativeAotSnapshotObservation } from "./native/nativeAotSnapshotObservation.js";
|
|
import type { FileSnapshotPartialObservation } from "./fileSnapshotObservation.js";
|
|
|
|
import type { WebModuleResolutionObservation } from "./webModuleTrace.js";
|
|
import type { WebNetworkCapture } from "./webNetworkCapture.js";
|
|
import type { FridaDeviceList, FridaProcessList } from "./fridaDiscovery.js";
|
|
import type { CutterBridgeDiscovery } from "./cutterDiscovery.js";
|
|
|
|
import type { WebTraceObservation } from "./webTraceObservation.js";
|
|
import type { WebArtifactAcquisition } from "./webArtifactAcquisition.js";
|
|
|
|
/** Provider-neutral evidence collected before a typed analysis failure. */
|
|
export type AnalysisPartialObservation =
|
|
| PartialTaskMeasurement
|
|
| {
|
|
readonly kind: "omitted-observation";
|
|
readonly reason:
|
|
| "insufficient-authentication-headroom"
|
|
| "recovery-input-invalid";
|
|
}
|
|
| { readonly kind: "unretained-evidence"; readonly evidence: Evidence }
|
|
| { readonly kind: "retained-evidence"; readonly evidence_id: string }
|
|
| GraphqlContract
|
|
| PlaywrightTrace
|
|
| SavedAnalysisSnapshotPartialObservation
|
|
| WebArtifactAcquisition
|
|
| WebTraceObservation
|
|
| CutterBridgeDiscovery
|
|
| FridaDeviceList
|
|
| FridaProcessList
|
|
| WebModuleResolutionObservation
|
|
| WebNetworkCapture
|
|
| NativeLoadImagePartialObservation
|
|
| NativeAotSnapshotObservation
|
|
| FileSnapshotPartialObservation
|
|
| PartialProcessCaptureObservation
|
|
| NativeCallPartialObservation
|
|
| HopperCallerReconciliationPartial
|
|
| FileOffsetPartialObservation
|
|
| ElectronActivePartialObservation
|
|
| BrowserScenarioPartialObservation
|
|
| WebPageInspection
|
|
| WebBundleAnalysis
|
|
| WebBundleAcquisitionPartialObservation
|
|
| WebSourceMapFetchPartialObservation
|
|
| WebSourceMapReport
|
|
| WebExecutionObservation
|
|
| WebEventListenersObservation
|
|
| JavaScriptRuntimeObservation
|
|
| ArtifactInventoryPartialObservation
|
|
| ArtifactExtractionPartialObservation
|
|
| WebScriptExportPartialObservation
|
|
| EnvironmentBundlePublication
|
|
| JavaScriptRecoveryPartialObservation
|
|
| Evidence
|
|
| BinaryLayout
|
|
| RecordedCrash
|
|
| AppleIpsPartialObservation
|
|
| RecordedCrashPartialObservation
|
|
| AndroidPartialObservation
|
|
| FirmwarePartialObservation
|
|
| NativeUiObservationResult
|
|
| InspectSignature
|
|
| InspectMacho
|
|
| ElectronPagePartialObservation
|
|
| AdbActivityStartPartialObservation
|
|
| AdbAcquiredFilePartialObservation
|
|
| AdbPackagePullPartialObservation
|
|
| AdbDisplayPartialObservation;
|