fix(javascript): retain completed facts through cancellation (#1934)

This commit is contained in:
morluto
2026-10-12 02:39:52 +08:00
committed by GitHub
parent 0b2aa69531
commit b6651cb99f
6 changed files with 465 additions and 295 deletions
@@ -4,6 +4,7 @@ import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js";
import type { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js";
import {
analyzeJavaScriptApplicationInputSchema,
type JavaScriptApplicationAnalysisResult,
parseOwnedJavaScriptApplicationAnalysisSteps,
} from "../../domain/javascript/javascriptApplicationAnalysis.js";
import { AnalysisOutputError } from "../../domain/analysisErrorCore.js";
@@ -20,7 +21,10 @@ import {
checkpointJavaScriptAnalysis,
completeJavaScriptAnalysisSteps,
} from "./JavaScriptAnalysisControl.js";
import { reconstructJavaScriptArtifact } from "./JavaScriptArtifactReconstruction.js";
import {
reconstructJavaScriptArtifact,
type JavaScriptArtifactReconstructionResult,
} from "./JavaScriptArtifactReconstruction.js";
import { JAVASCRIPT_APPLICATION_PROVIDER } from "../InvestigationProviders.js";
const OPERATION = "analyze_javascript_application" as const;
@@ -45,6 +49,9 @@ export const analyzeJavaScriptApplicationValidated = async (
scope: ArtifactResourceScope,
options: ExecutionOptions = {},
): Promise<Result<Evidence, AnalysisError>> => {
let reconstructed: JavaScriptArtifactReconstructionResult | undefined;
let result: JavaScriptApplicationAnalysisResult | undefined;
let evidence: Evidence | undefined;
try {
assertJavaScriptAnalysisActive(options.signal);
await options.progress?.report({
@@ -54,7 +61,7 @@ export const analyzeJavaScriptApplicationValidated = async (
message: "Inventorying and parsing application artifacts",
});
await checkpointJavaScriptAnalysis(options.signal);
const reconstructed = await reconstructJavaScriptArtifact(
reconstructed = await reconstructJavaScriptArtifact(
{
input_path: input.input_path,
format: input.format,
@@ -78,7 +85,7 @@ export const analyzeJavaScriptApplicationValidated = async (
message: "Validating result metadata and cross-graph bindings",
});
await checkpointJavaScriptAnalysis(options.signal);
const result = await completeJavaScriptAnalysisSteps(
result = await completeJavaScriptAnalysisSteps(
parseOwnedJavaScriptApplicationAnalysisSteps({
...application,
summary,
@@ -93,13 +100,12 @@ export const analyzeJavaScriptApplicationValidated = async (
message: "Creating and hashing application analysis Evidence",
});
await checkpointJavaScriptAnalysis(options.signal);
const evidence =
await createOwnedJavaScriptApplicationEvidenceCooperatively(
input,
result,
options.signal,
options.progress,
);
evidence = await createOwnedJavaScriptApplicationEvidenceCooperatively(
input,
result,
options.signal,
options.progress,
);
await checkpointJavaScriptAnalysis(options.signal);
await options.progress?.report({
phase: "analyze_javascript_application",
@@ -111,6 +117,38 @@ export const analyzeJavaScriptApplicationValidated = async (
assertJavaScriptAnalysisActive(options.signal);
return ok(evidence);
} catch (cause: unknown) {
if (
cause instanceof ArtifactReaderFailure &&
cause.reason === "cancelled" &&
reconstructed !== undefined
) {
try {
if (result === undefined) {
const { electron_summary: summary, ...application } = reconstructed;
result = await completeJavaScriptAnalysisSteps(
parseOwnedJavaScriptApplicationAnalysisSteps({
...application,
summary,
limitations: reconstructed.graph.limitations,
}),
);
}
evidence ??=
await createOwnedJavaScriptApplicationEvidenceCooperatively(
input,
result,
);
return err(
artifactFailureToOperationError(cause).retainPartialObservation(
evidence,
),
);
} catch (completionCause: unknown) {
// A validation or Evidence failure while finishing the partial result
// remains the primary failure instead of being hidden by cancellation.
cause = completionCause;
}
}
// Preserve execution constraints and their completed partial Evidence.
if (cause instanceof AnalysisError) return err(cause);
if (cause instanceof ArtifactReaderFailure)