fix(javascript): retain completed facts through cancellation (#1934)
This commit is contained in:
1 parent
0b2aa69531
commit
b6651cb99f
6 files changed
+465
-295
No files matched your search
@@ -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)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { constants } from "node:fs";
|
||||
import { access, lstat, realpath } from "node:fs/promises";
|
||||
import { setImmediate } from "node:timers/promises";
|
||||
|
||||
import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js";
|
||||
import {
|
||||
@@ -13,6 +14,7 @@ import type { JavaScriptSemanticGraph } from "../../domain/javascript/javascript
|
||||
import type { ElectronBoundarySummary } from "../../domain/javascript/javascriptApplicationAnalysis.js";
|
||||
import { parseOwnedJavaScriptApplicationAnalysisSteps } from "../../domain/javascript/javascriptApplicationAnalysis.js";
|
||||
import { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import { ArtifactOperationError } from "../../domain/artifactOperationError.js";
|
||||
import type { ArtifactInventorySnapshot } from "../../domain/artifactInventorySnapshot.js";
|
||||
import type { ArtifactInventoryPartialObservation } from "../../domain/artifactPartialObservation.js";
|
||||
import type { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js";
|
||||
@@ -68,19 +70,17 @@ export const reconstructJavaScriptArtifact = async (
|
||||
progress?: ProgressReporter,
|
||||
): Promise<JavaScriptArtifactReconstructionResult> =>
|
||||
scope.run(async () => {
|
||||
let effectiveSignal = signal;
|
||||
let effectiveProgress = progress;
|
||||
const reportPhase = async (
|
||||
phase: string,
|
||||
message: string,
|
||||
): Promise<void> => {
|
||||
await effectiveProgress?.report({
|
||||
await progress?.report({
|
||||
phase,
|
||||
completed: 0,
|
||||
total: 1,
|
||||
message,
|
||||
});
|
||||
await checkpointJavaScriptAnalysis(effectiveSignal);
|
||||
await checkpointJavaScriptAnalysis(signal);
|
||||
};
|
||||
const input = javascriptArtifactReconstructionInputSchema.parse(rawInput);
|
||||
abortIfNeeded(signal);
|
||||
@@ -182,14 +182,24 @@ export const reconstructJavaScriptArtifact = async (
|
||||
}
|
||||
}
|
||||
const analysis = outcome.analysis;
|
||||
const interruption = cleanupFailure ?? outcome.interruption;
|
||||
if (interruption !== null && interruption !== undefined) {
|
||||
effectiveSignal = undefined;
|
||||
effectiveProgress = undefined;
|
||||
}
|
||||
await checkpointJavaScriptAnalysis(effectiveSignal);
|
||||
abortIfNeeded(effectiveSignal);
|
||||
await reportPhase(
|
||||
let interruption = cleanupFailure ?? outcome.interruption;
|
||||
// Source facts are owned now. Finish their projection cooperatively so a
|
||||
// later cancellation can retain Evidence instead of discarding those facts.
|
||||
const checkpointCompletedAnalysis = async (): Promise<void> => {
|
||||
await setImmediate();
|
||||
if (interruption == null && signal?.aborted === true)
|
||||
interruption = new ArtifactOperationError(OPERATION, "cancelled");
|
||||
};
|
||||
const reportCompletedPhase = async (
|
||||
phase: string,
|
||||
message: string,
|
||||
): Promise<void> => {
|
||||
await checkpointCompletedAnalysis();
|
||||
if (interruption == null)
|
||||
await progress?.report({ phase, completed: 0, total: 1, message });
|
||||
await checkpointCompletedAnalysis();
|
||||
};
|
||||
await reportCompletedPhase(
|
||||
"build_javascript_application_graph",
|
||||
"Constructing application and Electron boundary relationships",
|
||||
);
|
||||
@@ -198,15 +208,14 @@ export const reconstructJavaScriptArtifact = async (
|
||||
files,
|
||||
analysis,
|
||||
);
|
||||
await reportPhase(
|
||||
await reportCompletedPhase(
|
||||
"seal_javascript_application_graph",
|
||||
"Sealing the validated application graph",
|
||||
);
|
||||
const graph = await completeJavaScriptAnalysisSteps(
|
||||
applicationGraphSteps,
|
||||
effectiveSignal,
|
||||
);
|
||||
await reportPhase(
|
||||
await reportCompletedPhase(
|
||||
"build_javascript_semantic_graph",
|
||||
"Binding and validating static semantic relationships",
|
||||
);
|
||||
@@ -214,15 +223,13 @@ export const reconstructJavaScriptArtifact = async (
|
||||
snapshot.manifest.root_sha256,
|
||||
graph,
|
||||
);
|
||||
await reportPhase(
|
||||
await reportCompletedPhase(
|
||||
"seal_javascript_semantic_graph",
|
||||
"Validating and sealing the semantic graph",
|
||||
);
|
||||
const semanticGraph = await completeJavaScriptAnalysisSteps(
|
||||
semanticGraphSteps,
|
||||
effectiveSignal,
|
||||
);
|
||||
await checkpointJavaScriptAnalysis(effectiveSignal);
|
||||
const semanticGraph =
|
||||
await completeJavaScriptAnalysisSteps(semanticGraphSteps);
|
||||
await checkpointCompletedAnalysis();
|
||||
const completed = {
|
||||
input_path: path,
|
||||
format,
|
||||
|
||||
Reference in new issue
Block a user