mirror of
https://github.com/morluto/rea.git
synced 2026-10-31 20:06:14 +00:00
fix(javascript): preserve analysis applicability after interruption (#4207)
Preserve applicable artifact processing phases and entered/unvisited status after interruption. Centralize JavaScript semantic applicability for full and incremental builders; retain native byte identity, actual JavaScript unknowns and structured-file provenance. Fixes #3948. Part of #4027. Validation:78 distinct focused cases across10files, full static/docs, and real Joplin timeout/default public CLI/MCP controls passed. Source pins, every attempt/outlier and compact resource/token projections are retained. No target/native provider execution or speedup claim. Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
This commit is contained in:
1 parent
663d07f02c
commit
85eac7b92b
18 files changed
+3717
-93
No files matched your search
@@ -22,6 +22,8 @@ import type {
|
||||
} from "../../domain/javascript/javascriptArtifactFiles.js";
|
||||
import type {
|
||||
AnalyzedJavaScriptArtifactFile,
|
||||
JavaScriptArtifactAnalysisFailure,
|
||||
JavaScriptArtifactAnalysisScope,
|
||||
JavaScriptArtifactAnalysis,
|
||||
JavaScriptModuleArtifactAnalysis,
|
||||
JavaScriptModuleSemanticIr,
|
||||
@@ -32,6 +34,7 @@ import type {
|
||||
JavaScriptSourceMapObservation,
|
||||
JavaScriptSourceMapOriginal,
|
||||
} from "./JavaScriptArtifactAnalysisTypes.js";
|
||||
import { javascriptArtifactAnalysisScopes } from "./JavaScriptArtifactAnalysisTypes.js";
|
||||
import { analyzeJavaScriptJsonModule } from "./JavaScriptJsonModules.js";
|
||||
import { completeJavaScriptAnalysisSteps } from "./JavaScriptAnalysisControl.js";
|
||||
import { htmlArtifactReferences } from "../../domain/javascript/htmlArtifactReferences.js";
|
||||
@@ -141,6 +144,7 @@ export const analyzeAndAdoptJavaScriptArtifactFiles = async (
|
||||
fileSet.files.length - (fileSet.source_selection?.unselected_files ?? 0);
|
||||
let selectedFileIndex = 0;
|
||||
for (const file of fileSet.files) {
|
||||
let analysisStarted = false;
|
||||
try {
|
||||
await setImmediate();
|
||||
if (classifyOutsideAnalysisScope(file, state, projection)) {
|
||||
@@ -155,6 +159,7 @@ export const analyzeAndAdoptJavaScriptArtifactFiles = async (
|
||||
if (signal?.aborted === true)
|
||||
throw new AnalysisCancelledError("analyze_javascript_application");
|
||||
await beforeFile(file, selectedFileIndex++, selectedFileCount);
|
||||
analysisStarted = true;
|
||||
if (file.kind !== "javascript" || !file.text.included) {
|
||||
const steps = analyzeArtifactFileSteps(file, {
|
||||
state,
|
||||
@@ -182,10 +187,12 @@ export const analyzeAndAdoptJavaScriptArtifactFiles = async (
|
||||
: error instanceof AnalysisTimeoutError
|
||||
? { worker_timeout_ms: error.timeoutMs }
|
||||
: null;
|
||||
const failure =
|
||||
const failure: JavaScriptArtifactAnalysisFailure | undefined =
|
||||
error === null
|
||||
? undefined
|
||||
: {
|
||||
scope: javascriptArtifactAnalysisScopes.javascript,
|
||||
status: "interrupted",
|
||||
reason: `${file.path}: ${error.message}`,
|
||||
error: projectAnalysisError(error),
|
||||
limits,
|
||||
@@ -245,15 +252,22 @@ export const analyzeAndAdoptJavaScriptArtifactFiles = async (
|
||||
? cause
|
||||
: null;
|
||||
if (interruption !== null) {
|
||||
const scope = javascriptArtifactAnalysisScopes[file.kind];
|
||||
state.files.push({
|
||||
file,
|
||||
javascript: null,
|
||||
semantic: null,
|
||||
analysis_failure: {
|
||||
reason: `${file.path}: ${interruption.message}`,
|
||||
error: projectAnalysisError(interruption),
|
||||
limits: null,
|
||||
},
|
||||
...(scope === null
|
||||
? {}
|
||||
: {
|
||||
analysis_failure: {
|
||||
scope,
|
||||
status: analysisStarted ? "interrupted" : "not-started",
|
||||
reason: `${file.path}: ${interruption.message}`,
|
||||
error: projectAnalysisError(interruption),
|
||||
limits: null,
|
||||
},
|
||||
}),
|
||||
});
|
||||
projection.recordUnavailableFile(
|
||||
file,
|
||||
@@ -277,25 +291,36 @@ export const analyzeAndAdoptJavaScriptArtifactFiles = async (
|
||||
? { worker_timeout_ms: interruption.timeoutMs }
|
||||
: null,
|
||||
};
|
||||
// This interruption has one cause. Share its immutable failure per phase
|
||||
// rather than copying it for every unvisited source in a large bundle.
|
||||
const failures = new Map<
|
||||
JavaScriptArtifactAnalysisScope,
|
||||
JavaScriptArtifactAnalysisFailure
|
||||
>();
|
||||
for (const file of fileSet.files) {
|
||||
if (analyzedPaths.has(file.path)) continue;
|
||||
if (classifyOutsideAnalysisScope(file, state, projection)) continue;
|
||||
const scope = javascriptArtifactAnalysisScopes[file.kind];
|
||||
let unavailable = scope === null ? undefined : failures.get(scope);
|
||||
if (scope !== null && unavailable === undefined) {
|
||||
unavailable = { ...failure, scope, status: "not-started" };
|
||||
failures.set(scope, unavailable);
|
||||
}
|
||||
state.files.push({
|
||||
file,
|
||||
javascript: null,
|
||||
semantic: null,
|
||||
analysis_failure: failure,
|
||||
...(unavailable === undefined ? {} : { analysis_failure: unavailable }),
|
||||
});
|
||||
if (file.kind === "javascript")
|
||||
projection.recordUnavailableFile(
|
||||
file,
|
||||
failure.reason,
|
||||
failure.limits,
|
||||
interruption instanceof AnalysisResourceConstraintError ||
|
||||
interruption instanceof AnalysisTimeoutError
|
||||
? "resource-limit"
|
||||
: "incomplete-module",
|
||||
);
|
||||
projection.recordUnavailableFile(
|
||||
file,
|
||||
failure.reason,
|
||||
failure.limits,
|
||||
interruption instanceof AnalysisResourceConstraintError ||
|
||||
interruption instanceof AnalysisTimeoutError
|
||||
? "resource-limit"
|
||||
: "incomplete-module",
|
||||
);
|
||||
}
|
||||
}
|
||||
return { analysis: finalizeArtifactAnalysis(state), interruption };
|
||||
@@ -423,13 +448,12 @@ const classifyOutsideAnalysisScope = <
|
||||
if (file.text.included || file.text.reason !== "outside-analysis-scope")
|
||||
return false;
|
||||
state.files.push({ file, javascript: null, semantic: null });
|
||||
if (file.kind === "javascript")
|
||||
projection?.recordUnavailableFile(
|
||||
file,
|
||||
"Source was not analyzed because it is outside the caller-selected source_paths.",
|
||||
null,
|
||||
"incomplete-module",
|
||||
);
|
||||
projection?.recordUnavailableFile(
|
||||
file,
|
||||
"Source was not analyzed because it is outside the caller-selected source_paths.",
|
||||
null,
|
||||
"incomplete-module",
|
||||
);
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
@@ -5,7 +5,10 @@ import type {
|
||||
} from "../../domain/javascript/javascriptStaticAnalysisTypes.js";
|
||||
import type { JavaScriptSemanticIr } from "../../domain/javascript/javascriptSemanticIr.js";
|
||||
import type { JavaScriptModuleSemanticIr } from "../../domain/javascript/javascriptModuleSemanticIr.js";
|
||||
import type { JavaScriptArtifactFile } from "../../domain/javascript/javascriptArtifactFiles.js";
|
||||
import type {
|
||||
JavaScriptArtifactFile,
|
||||
JavaScriptArtifactFileKind,
|
||||
} from "../../domain/javascript/javascriptArtifactFiles.js";
|
||||
import type { JsonValue } from "../../domain/jsonValue.js";
|
||||
|
||||
interface JavaScriptStructuredObservation {
|
||||
@@ -107,6 +110,47 @@ export type JavaScriptJsonModuleObservation = JavaScriptStructuredObservation &
|
||||
/** Module facts needed after file-local semantic projection has finished. */
|
||||
export type { JavaScriptModuleSemanticIr } from "../../domain/javascript/javascriptModuleSemanticIr.js";
|
||||
|
||||
/** Applicable processing of inventoried files; native bytes remain metadata-only. */
|
||||
export const javascriptArtifactAnalysisScopes = {
|
||||
javascript: {
|
||||
operation: "analyze-javascript-source",
|
||||
authority: "ast-static-analysis",
|
||||
},
|
||||
"package-json": {
|
||||
operation: "parse-package-json",
|
||||
authority: "artifact-bytes",
|
||||
},
|
||||
json: { operation: "parse-json-module", authority: "artifact-bytes" },
|
||||
html: {
|
||||
operation: "parse-html-script-references",
|
||||
authority: "artifact-bytes",
|
||||
},
|
||||
"source-map": {
|
||||
operation: "parse-local-source-map",
|
||||
authority: "artifact-bytes",
|
||||
},
|
||||
"native-addon": null,
|
||||
} as const satisfies Record<
|
||||
JavaScriptArtifactFileKind,
|
||||
{
|
||||
readonly operation: string;
|
||||
readonly authority: "artifact-bytes" | "ast-static-analysis";
|
||||
} | null
|
||||
>;
|
||||
|
||||
export type JavaScriptArtifactAnalysisScope = NonNullable<
|
||||
(typeof javascriptArtifactAnalysisScopes)[JavaScriptArtifactFileKind]
|
||||
>;
|
||||
|
||||
/** A processing gap preserves applicability and whether the phase was entered. */
|
||||
export interface JavaScriptArtifactAnalysisFailure {
|
||||
readonly scope: JavaScriptArtifactAnalysisScope;
|
||||
readonly status: "interrupted" | "not-started";
|
||||
readonly reason: string;
|
||||
readonly error: JsonValue;
|
||||
readonly limits: Readonly<Record<string, JsonValue>> | null;
|
||||
}
|
||||
|
||||
/** One relevant file plus optional AST-only JavaScript facts. */
|
||||
export interface AnalyzedJavaScriptArtifactFile<
|
||||
SemanticIr extends JavaScriptModuleSemanticIr = JavaScriptSemanticIr,
|
||||
@@ -117,11 +161,7 @@ export interface AnalyzedJavaScriptArtifactFile<
|
||||
readonly ir: SemanticIr;
|
||||
} | null;
|
||||
/** Operational incompleteness, distinct from a malformed source file. */
|
||||
readonly analysis_failure?: {
|
||||
readonly reason: string;
|
||||
readonly error: JsonValue;
|
||||
readonly limits: Readonly<Record<string, JsonValue>> | null;
|
||||
};
|
||||
readonly analysis_failure?: JavaScriptArtifactAnalysisFailure;
|
||||
/** Facts retained inline when their expanded graph exceeds available memory. */
|
||||
readonly application_projection_failure?: {
|
||||
readonly reason: string;
|
||||
|
||||
@@ -12,6 +12,7 @@ import type { JavaScriptSourceRange } from "../../domain/javascript/javascriptSt
|
||||
import type {
|
||||
JavaScriptHtmlInlineScriptsObservation,
|
||||
JavaScriptModuleArtifactAnalysis,
|
||||
JavaScriptArtifactAnalysisScope,
|
||||
} from "./JavaScriptArtifactAnalysisTypes.js";
|
||||
import type { JavaScriptUnanalyzedArtifactLookup } from "../../domain/javascript/javascriptArtifactFiles.js";
|
||||
import { HTML_INLINE_SCRIPT_LOCATION_LIMIT } from "../../domain/javascript/htmlArtifactReferences.js";
|
||||
@@ -25,7 +26,7 @@ import {
|
||||
artifactObservationEvidence,
|
||||
astObservationEvidence,
|
||||
staticInferenceEvidence,
|
||||
unavailableAstEvidence,
|
||||
unavailableStaticEvidence,
|
||||
} from "./JavaScriptArtifactGraphEvidence.js";
|
||||
import {
|
||||
artifactPathResolutionProperties,
|
||||
@@ -106,11 +107,12 @@ export interface RoleAssetLinkInput {
|
||||
readonly coverage?: JavaScriptArtifactGraphCoverage;
|
||||
}
|
||||
|
||||
/** Explicit package, JavaScript, or source-map parse gap. */
|
||||
/** An explicit processing/selection gap with the applicable phase's authority. */
|
||||
export interface UnavailableParseInput {
|
||||
readonly file: JavaScriptArtifactFile;
|
||||
readonly asset: ApplicationNode;
|
||||
readonly operation: string;
|
||||
readonly authority: JavaScriptArtifactAnalysisScope["authority"];
|
||||
readonly limitation: string;
|
||||
readonly failure?: JsonValue;
|
||||
readonly limits?: JavaScriptArtifactGraphCoverage["limits"];
|
||||
@@ -230,7 +232,8 @@ export const addUnavailableStaticParseScope = (
|
||||
? {}
|
||||
: { analysis_failure: input.failure }),
|
||||
},
|
||||
evidence: unavailableAstEvidence({
|
||||
evidence: unavailableStaticEvidence({
|
||||
authority: input.authority,
|
||||
sha256: input.file.sha256,
|
||||
path: input.file.path,
|
||||
operation: input.operation,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { ApplicationGraphEvidence } from "../../domain/javascript/javascriptApplicationEvidenceSchemas.js";
|
||||
import type { JavaScriptSourceRange } from "../../domain/javascript/javascriptStaticAnalysisTypes.js";
|
||||
import type { JavaScriptArtifactAnalysisScope } from "./JavaScriptArtifactAnalysisTypes.js";
|
||||
|
||||
const EXTRACTOR = {
|
||||
name: "rea-javascript-artifact-reconstruction",
|
||||
@@ -93,11 +94,14 @@ export const staticInferenceEvidence = (
|
||||
evidence_ids: [],
|
||||
});
|
||||
|
||||
/** Unavailable AST fact that preserves a parse, approval, or limit gap. */
|
||||
export const unavailableAstEvidence = (
|
||||
input: EvidenceInput & { readonly limitation: string },
|
||||
/** Unavailable static fact with the authority of its applicable processing phase. */
|
||||
export const unavailableStaticEvidence = (
|
||||
input: EvidenceInput & {
|
||||
readonly authority: JavaScriptArtifactAnalysisScope["authority"];
|
||||
readonly limitation: string;
|
||||
},
|
||||
): ApplicationGraphEvidence => ({
|
||||
authority: "ast-static-analysis",
|
||||
authority: input.authority,
|
||||
state: "unavailable",
|
||||
confidence: "unknown",
|
||||
artifact: artifactReference(input.sha256),
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
partialApplicationCoverage,
|
||||
} from "../../domain/javascript/javascriptApplicationEvidenceSchemas.js";
|
||||
import type { JavaScriptModuleArtifactAnalysis } from "./JavaScriptArtifactAnalysisTypes.js";
|
||||
import { javascriptArtifactAnalysisScopes } from "./JavaScriptArtifactAnalysisTypes.js";
|
||||
import { jsonValueSchema } from "../../domain/jsonValue.js";
|
||||
import { freezeJsonSnapshot } from "../../domain/immutableJson.js";
|
||||
import type { JavaScriptSourceRange } from "../../domain/javascript/javascriptStaticAnalysisTypes.js";
|
||||
@@ -211,11 +212,17 @@ export const addJavaScriptArtifactFiles = (
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (!file.text.included && file.text.reason === "outside-analysis-scope")
|
||||
const analysisScope = javascriptArtifactAnalysisScopes[file.kind];
|
||||
if (
|
||||
!file.text.included &&
|
||||
file.text.reason === "outside-analysis-scope" &&
|
||||
analysisScope !== null
|
||||
)
|
||||
addUnavailableStaticParseScope(context, {
|
||||
file,
|
||||
asset: target,
|
||||
operation: "select-javascript-sources",
|
||||
authority: analysisScope.authority,
|
||||
limitation:
|
||||
"Source is outside the caller-selected source_paths; it was inventoried but not analyzed.",
|
||||
});
|
||||
@@ -223,12 +230,13 @@ export const addJavaScriptArtifactFiles = (
|
||||
addUnavailableStaticParseScope(context, {
|
||||
file,
|
||||
asset: target,
|
||||
operation: "analyze-javascript-source",
|
||||
...analyzed.analysis_failure.scope,
|
||||
limitation: analyzed.analysis_failure.reason,
|
||||
failure: analyzed.analysis_failure.error,
|
||||
limits: javaScriptAnalysisCoverageLimits(
|
||||
analyzed.analysis_failure.limits,
|
||||
),
|
||||
details: { analysis_status: analyzed.analysis_failure.status },
|
||||
});
|
||||
else if (
|
||||
file.kind === "javascript" &&
|
||||
@@ -239,6 +247,7 @@ export const addJavaScriptArtifactFiles = (
|
||||
file,
|
||||
asset: target,
|
||||
operation: "parse-javascript",
|
||||
authority: javascriptArtifactAnalysisScopes.javascript.authority,
|
||||
limitation: file.text.included
|
||||
? "JavaScript syntax could not be parsed."
|
||||
: `JavaScript text was unavailable: ${file.text.reason}.`,
|
||||
@@ -248,6 +257,7 @@ export const addJavaScriptArtifactFiles = (
|
||||
file,
|
||||
asset: target,
|
||||
operation: "project-javascript-application",
|
||||
authority: javascriptArtifactAnalysisScopes.javascript.authority,
|
||||
limitation: analyzed.application_projection_failure.reason,
|
||||
failure: analyzed.application_projection_failure.error,
|
||||
limits: javaScriptAnalysisCoverageLimits(
|
||||
@@ -259,14 +269,14 @@ export const addJavaScriptArtifactFiles = (
|
||||
addUnavailableStaticParseScope(context, {
|
||||
file,
|
||||
asset: target,
|
||||
operation: "parse-package-json",
|
||||
...javascriptArtifactAnalysisScopes["package-json"],
|
||||
limitation: packageValue.limitation,
|
||||
});
|
||||
if (jsonValue !== undefined && jsonValue.status !== "included")
|
||||
addUnavailableStaticParseScope(context, {
|
||||
file,
|
||||
asset: target,
|
||||
operation: "parse-json-module",
|
||||
...javascriptArtifactAnalysisScopes.json,
|
||||
limitation: jsonValue.limitation,
|
||||
});
|
||||
const sourceMap = sourceMapsByPath.get(file.path);
|
||||
@@ -274,7 +284,7 @@ export const addJavaScriptArtifactFiles = (
|
||||
addUnavailableStaticParseScope(context, {
|
||||
file,
|
||||
asset: target,
|
||||
operation: "parse-local-source-map",
|
||||
...javascriptArtifactAnalysisScopes["source-map"],
|
||||
limitation: sourceMap.limitation,
|
||||
});
|
||||
const inlineScripts = inlineScriptsByPath.get(file.path);
|
||||
@@ -283,6 +293,7 @@ export const addJavaScriptArtifactFiles = (
|
||||
file,
|
||||
asset: target,
|
||||
operation: "parse-html-inline-scripts",
|
||||
authority: javascriptArtifactAnalysisScopes.javascript.authority,
|
||||
limitation: htmlInlineScriptLimitation(inlineScripts),
|
||||
limits:
|
||||
inlineScripts.script_count > inlineScripts.scripts.length
|
||||
|
||||
@@ -20,6 +20,7 @@ import type { ArtifactInventorySnapshot } from "../../domain/artifactInventorySn
|
||||
import type { ArtifactInventoryPartialObservation } from "../../domain/artifactPartialObservation.js";
|
||||
import type { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js";
|
||||
import { analyzeAndAdoptJavaScriptArtifactFiles } from "./JavaScriptArtifactAnalysis.js";
|
||||
import { javascriptArtifactAnalysisScopes } from "./JavaScriptArtifactAnalysisTypes.js";
|
||||
import { createJavaScriptSourceAnalysis } from "../../artifacts/javascript/JavaScriptSourceAnalysis.js";
|
||||
import { JAVASCRIPT_ANALYSIS_RESOURCE_DEFAULTS } from "../../domain/javascript/javascriptAnalysisResourceControls.js";
|
||||
import { createOwnedJavaScriptApplicationEvidenceCooperatively } from "./JavaScriptApplicationEvidence.js";
|
||||
@@ -137,7 +138,7 @@ export const reconstructJavaScriptArtifact = async (
|
||||
);
|
||||
await reportPhase(
|
||||
"parse_javascript_sources",
|
||||
`Parsing and projecting ${String(files.files.length - (files.source_selection?.unselected_files ?? 0))} selected application source files`,
|
||||
`Processing ${String(files.files.length - (files.source_selection?.unselected_files ?? 0))} selected application files`,
|
||||
);
|
||||
const semanticProjection = createJavaScriptSemanticGraphProjection({
|
||||
...(input.max_semantic_nodes === undefined
|
||||
@@ -168,11 +169,16 @@ export const reconstructJavaScriptArtifact = async (
|
||||
semanticProjection,
|
||||
async (file, completed, total) => {
|
||||
abortIfNeeded(signal);
|
||||
const ast =
|
||||
javascriptArtifactAnalysisScopes[file.kind]?.authority ===
|
||||
"ast-static-analysis";
|
||||
await progress?.report({
|
||||
phase: "parse_javascript_source",
|
||||
phase: ast
|
||||
? "parse_javascript_source"
|
||||
: "process_javascript_artifact_metadata",
|
||||
completed: 0,
|
||||
total: 1,
|
||||
message: `Parsing and projecting ${file.path} (${String(completed + 1)}/${String(total)})`,
|
||||
message: `${ast ? "Parsing and projecting" : "Processing metadata for"} ${file.path} (${String(completed + 1)}/${String(total)})`,
|
||||
});
|
||||
abortIfNeeded(signal);
|
||||
},
|
||||
|
||||
@@ -163,7 +163,7 @@ export interface JavaScriptSemanticGraphProjection {
|
||||
readonly adoptFileProjection: (
|
||||
projection: JavaScriptSemanticFileProjection,
|
||||
) => void;
|
||||
/** Preserve source-bound unknowns for an execution or result-retention failure. */
|
||||
/** Preserve JavaScript-source unknowns; other file kinds have no semantic module. */
|
||||
readonly recordUnavailableFile: (
|
||||
file: JavaScriptArtifactFile,
|
||||
reason: string,
|
||||
@@ -346,6 +346,9 @@ export const createJavaScriptSemanticGraphProjection = (
|
||||
if (projection.truncated) truncatedFiles += 1;
|
||||
},
|
||||
recordUnavailableFile: (file, reason, limits, unknownReason) => {
|
||||
// Both full and incremental construction enter here. Metadata/native
|
||||
// processing gaps belong to the application graph, never JavaScript IR.
|
||||
if (file.kind !== "javascript") return;
|
||||
const retainedLimits = javaScriptAnalysisCoverageLimits(limits);
|
||||
for (const limit of retainedLimits) {
|
||||
executionLimits.set(
|
||||
|
||||
Reference in new issue
Block a user