refactor(javascript): own coverage facts and reuse block prefixes

Separate syntax, semantic uncertainty, examination limits and observed Electron applicability at the static producer, then preserve them through worker, graph and retained/inline summary views. Derive the established parse_status from the same facts and leave historical measurements unknown. Give semantic resource-limit classifications one schema owner.

Replace repeated block-prefix searches and copies with collector-scoped syntax indexes. Keep invocation environments separate, charge indexing/replay to the existing work budget and yield during prefix work. Remove the superseded synchronous collector entrypoint.

Validated producer, worker, CLI/MCP, historical and partial-coverage regressions, plus actual cancellation during indexing. Bounded authored 128/256-call controls preserved all prior facts while replacing repeated search/copy work with one index pass; this is not a throughput or agent-token benchmark.
This commit is contained in:
morluto
2026-10-25 09:45:48 +08:00
parent c8269ca497
commit f62a2baad8
21 changed files with 898 additions and 125 deletions
@@ -195,6 +195,27 @@ For supplied passive browser or Electron observations, use
[runtime reconciliation](javascript-runtime-reconciliation.md). It compares
existing Evidence without rerunning static analysis or contacting CDP.
Each newly analyzed JavaScript file retains `analysis_coverage` in its graph
observation. `syntax` distinguishes complete parsing, recovered syntax errors,
failed parsing and unexamined source. `semantics` records the number of observed
unknown findings and typed uncertainty, including returned-reference widening
and unavailable type-only compiler options. `examination` reports static semantic
resource limits and Electron wrapper expansion limits independently. A completed
pass does not establish complete runtime semantics. Route examination and result
capacity remain in the separate route inventory.
Electron applicability is `observed` when a static Electron module reference is
retained, and otherwise `unknown`: a source without such a reference does not
prove that the application is inapplicable. Sources excluded by `source_paths`
retain `not-examined` with a reason and null semantic counts. Historical Evidence
without this facet remains unmeasured. The existing `parse_status` remains its
conservative overall summary; finer facts do not relabel earlier partial results.
Summary delivery counts these facts under `summary.coverage.static_sources`.
Read exact per-file measurements with `inspect_analysis_view`, selecting
`kind: "javascript-facts"` and `property_keys: ["analysis_coverage"]`, or inspect
the module item. Both views use retained Evidence and preserve source provenance.
## ASAR integrity
ASAR inventory checks Electron integrity metadata for embedded archive entries
@@ -130,6 +130,11 @@ describe("skipped JavaScript parse reporting", () => {
const context = contextFor(file, analysisFor([skippedEntry(file)]));
addJavaScriptArtifactFiles(context);
expect(fileObservation(context).parse_status).toBe("skipped");
expect(fileObservation(context).analysis_coverage).toMatchObject({
syntax: { status: "not-examined", error_count: null },
semantics: { unknown_findings: null },
examination: { status: "not-examined" },
});
});
it("keeps parse_status null for a JavaScript file never attempted", () => {
@@ -157,6 +162,21 @@ describe("skipped JavaScript parse reporting", () => {
const context = contextFor(file, analysisFor([entry]));
addJavaScriptArtifactFiles(context);
expect(fileObservation(context).parse_status).toBe(javascript.parse_status);
expect(fileObservation(context).analysis_coverage).toEqual(
javascript.analysis_coverage,
);
});
it("does not reconstruct coverage from a historical overall summary", () => {
const file = skippedFile();
const { analysis_coverage: _coverage, ...javascript } =
analyzeJavaScriptStaticSource(file.text.included ? file.text.value : "");
const context = contextFor(
file,
analysisFor([{ file, javascript, semantic: null }]),
);
addJavaScriptArtifactFiles(context);
expect(fileObservation(context)).not.toHaveProperty("analysis_coverage");
});
});
@@ -7,16 +7,19 @@ import {
partialApplicationCoverage,
} from "../../domain/javascript/javascriptApplicationEvidenceSchemas.js";
import type { JavaScriptArtifactAnalysis } from "./JavaScriptArtifactAnalysisTypes.js";
import { javascriptArtifactAnalysisScopes } from "./JavaScriptArtifactAnalysisTypes.js";
import {
javascriptArtifactAnalysisScopes,
isSkippedJavaScriptAnalysis,
} from "./JavaScriptArtifactAnalysisTypes.js";
import { jsonValueSchema } from "../../domain/jsonValue.js";
import { freezeJsonSnapshot } from "../../domain/immutableJson.js";
import type { JavaScriptSourceRange } from "../../domain/javascript/javascriptStaticAnalysisTypes.js";
import { unexaminedJavaScriptStaticCoverage } from "../../domain/javascript/javascriptStaticCoverage.js";
import type {
JavaScriptArtifactContainer,
JavaScriptArtifactFile,
} from "../../domain/javascript/javascriptArtifactFiles.js";
import type { JavaScriptArtifactGraphAccumulator } from "./JavaScriptArtifactGraphAccumulator.js";
import { isSkippedJavaScriptAnalysis } from "./JavaScriptArtifactAnalysisTypes.js";
import {
addArtifactContainsEdge,
applicationManifestPath,
@@ -648,6 +651,21 @@ const createFileTarget = (
text_status: file.text.included ? "included" : file.text.reason,
parse_status:
javascript?.parse_status ?? (skipped ? "skipped" : null),
...(javascript?.analysis_coverage !== undefined
? { analysis_coverage: javascript.analysis_coverage }
: javascript === null && file.kind === "javascript"
? {
analysis_coverage: unexaminedJavaScriptStaticCoverage(
!file.text.included
? file.text.reason === "outside-analysis-scope"
? "Source is outside the caller-selected source_paths."
: `JavaScript text was unavailable: ${file.text.reason}.`
: skipped
? "JavaScript analysis did not complete."
: "JavaScript source analysis was not performed.",
),
}
: {}),
json_parse_status: json?.status ?? null,
json_top_level_keys: json?.top_level_keys ?? [],
omitted_json_top_level_keys:
@@ -0,0 +1,61 @@
import { expect, it } from "vitest";
import { completeJavaScriptAnalysisSteps } from "./JavaScriptAnalysisControl.js";
import { collectElectronWrappedCallPathsSteps } from "../../domain/javascript/electronWrappedCallPaths.js";
import {
indexJavaScriptBindingsWithReceiversSteps,
indexJavaScriptElectronIdentitySteps,
} from "../../domain/javascript/javascriptSemanticAnalysis.js";
import { completeSemanticSteps } from "../../domain/javascript/javascriptSemanticTraversal.js";
import { parseJavaScriptSource } from "../../domain/javascript/javascriptSourceParser.js";
import { createJavaScriptAnalysisAccumulator } from "../../domain/javascript/javascriptStaticAnalysisState.js";
it("cancels actual call-path work while its first seed indexes a long block", async () => {
const source = `const e = require("electron"); {
e.ipcMain.on("first", event => event.sender.copy());
${"0;".repeat(2048)}
}`;
const parsed = parseJavaScriptSource(source);
if (parsed === null) throw new Error("Expected parsed source");
const index = completeSemanticSteps(
indexJavaScriptBindingsWithReceiversSteps(parsed.program),
);
const identity = completeSemanticSteps(
indexJavaScriptElectronIdentitySteps(parsed.program, index.bindings),
);
const accumulator = createJavaScriptAnalysisAccumulator(source.length);
const work = collectElectronWrappedCallPathsSteps(
parsed.program,
index.bindings,
source,
accumulator,
identity,
);
// The first candidate has not yet been expanded: this checkpoint is inside
// its block indexing, before the walker's completed batch is yielded.
expect(work.next()).toEqual({ done: false, value: undefined });
expect(accumulator.ipc).toEqual([]);
const controller = new AbortController();
const reason = { request: "cancel prefix indexing" };
controller.abort(reason);
await expect(
completeJavaScriptAnalysisSteps(work, controller.signal),
).rejects.toMatchObject({ reason: "cancelled", cause: reason });
expect(work.next()).toEqual({ done: true, value: undefined });
expect(accumulator.ipc).toEqual([]);
expect(accumulator.electronCallPathLimitReached).toBeUndefined();
// A new analysis owns a new index and can still complete the same source.
const completed = createJavaScriptAnalysisAccumulator(source.length);
expect(
completeSemanticSteps(
collectElectronWrappedCallPathsSteps(
parsed.program,
index.bindings,
source,
completed,
identity,
),
),
).toContainEqual(expect.objectContaining({ entry: "first", conditions: [] }));
expect(completed.electronCallPathLimitReached).toBeUndefined();
});
@@ -3,6 +3,7 @@ import { applicationCoverageSchema } from "../javascript/javascriptApplicationEv
import type { JavaScriptApplicationAnalysisResult } from "../javascript/javascriptApplicationAnalysis.js";
import { javaScriptSemanticGraphInputSchema } from "../javascript/javascriptSemanticGraphSchemas.js";
import { javaScriptSourceSelectionSchema } from "../javascript/javascriptSourceSelection.js";
import { javaScriptStaticCoverageSchema } from "../javascript/javascriptStaticCoverage.js";
/** Source completeness is independent of exhausting a selected fact page. */
export const javascriptApplicationSourceCoverageSchema = z.strictObject({
@@ -37,3 +38,39 @@ export const javascriptApplicationSourceCoverage = (
semantic_unknown_count: analysis.semantic_graph.unknowns.length,
};
};
/** Count only source-file observations; historical missing facts remain unmeasured. */
export const javascriptStaticSourceCoverageSummary = (
analysis: JavaScriptApplicationAnalysisResult,
) => {
const summary = {
sources: 0,
unmeasured_sources: 0,
syntax: { complete: 0, recovered: 0, failed: 0, "not-examined": 0 },
semantics: { unknown_sources: 0, unexamined_sources: 0 },
examination: { complete: 0, limited: 0, "not-examined": 0 },
electron_applicability: { observed: 0, unknown: 0 },
};
for (const node of analysis.graph.nodes) {
for (const observation of node.observations) {
if (observation.properties.file_kind !== "javascript") continue;
summary.sources += 1;
const parsed = javaScriptStaticCoverageSchema.safeParse(
observation.properties.analysis_coverage,
);
if (!parsed.success) {
summary.unmeasured_sources += 1;
continue;
}
const coverage = parsed.data;
summary.syntax[coverage.syntax.status] += 1;
summary.examination[coverage.examination.status] += 1;
summary.electron_applicability[coverage.applicability.electron] += 1;
if (coverage.semantics.unknown_findings === null)
summary.semantics.unexamined_sources += 1;
else if (coverage.semantics.uncertainty.length > 0)
summary.semantics.unknown_sources += 1;
}
}
return summary;
};
@@ -11,6 +11,7 @@ import { err, ok, type Result } from "../result.js";
import type { JavaScriptModuleReference } from "./javascriptModuleReferences.js";
import {
javascriptApplicationSourceCoverage,
javascriptStaticSourceCoverageSummary,
type JavaScriptApplicationSourceCoverage,
} from "./javascriptApplicationCoverage.js";
import { projectJavaScriptFacts } from "./javascriptFactsView.js";
@@ -406,6 +407,7 @@ export const projectJavaScriptApplicationView = (
statistics: jsonObjectSchema.parse(analysis.statistics),
electron: jsonObjectSchema.parse(analysis.summary),
coverage: jsonObjectSchema.parse({
static_sources: javascriptStaticSourceCoverageSummary(analysis),
application: analysis.graph.coverage,
semantic: {
...analysis.semantic_graph.coverage,
@@ -0,0 +1,78 @@
import * as t from "@babel/types";
import { expect, it } from "vitest";
import { ElectronCallPathBlockPrefixes } from "./electronCallPathBlockPrefixes.js";
import { parseJavaScriptSource } from "./javascriptSourceParser.js";
import { completeSemanticSteps } from "./javascriptSemanticTraversal.js";
it("indexes a real flat block once, retaining contextual guards in source order", () => {
const parsed = parseJavaScriptSource(`{
${"0;".repeat(2048)}
if (uncertain) ordinary();
if (flag) throw 0;
electron.send("tail");
}`);
const block = parsed?.program.body[0];
if (!t.isBlockStatement(block)) throw new Error("Expected parsed block");
const first = block.body[0];
const tail = block.body.at(-1);
if (first === undefined || tail === undefined)
throw new Error("Expected block statements");
let examined = 0;
const prefixes = new ElectronCallPathBlockPrefixes(() => {
examined += 1;
return true;
});
const steps = prefixes.before(block, tail);
expect(steps.next()).toEqual({ done: false, value: undefined });
expect(examined).toBe(1024);
const prefix = completeSemanticSteps(steps);
expect(examined).toBe(block.body.length);
expect(prefix).toMatchObject({
before: 2050,
entries: [
{
position: 2048,
fact: {
kind: "branch",
test: { type: "Identifier", name: "uncertain" },
consequentExits: false,
alternateExits: false,
},
},
{
position: 2049,
fact: {
kind: "branch",
test: { type: "Identifier", name: "flag" },
consequentExits: true,
alternateExits: false,
},
},
],
});
for (const statement of block.body) {
const earlier = completeSemanticSteps(prefixes.before(block, statement));
expect(earlier?.entries).toBe(prefix?.entries);
}
expect(examined).toBe(block.body.length);
expect(completeSemanticSteps(prefixes.before(block, first))?.before).toBe(0);
});
it("does not publish an incomplete block index when its work budget ends", () => {
const parsed = parseJavaScriptSource(
`{ 0; 1; if (stop) throw 0; send("tail"); }`,
);
const block = parsed?.program.body[0];
if (!t.isBlockStatement(block)) throw new Error("Expected parsed block");
const tail = block.body.at(-1);
if (tail === undefined) throw new Error("Expected terminal statement");
let remaining = 2;
const prefixes = new ElectronCallPathBlockPrefixes(() => --remaining >= 0);
expect(completeSemanticSteps(prefixes.before(block, tail))).toBeUndefined();
remaining = block.body.length;
expect(completeSemanticSteps(prefixes.before(block, tail))).toMatchObject({
before: 3,
entries: [{ position: 2, fact: { kind: "branch" } }],
});
expect(remaining).toBe(0);
});
@@ -0,0 +1,85 @@
import * as t from "@babel/types";
import { symbolicDefiniteExit } from "./javascriptSymbolicControlFlow.js";
/** Syntax alone can establish an exit, but never a branch's contextual truth. */
export type ElectronStatementFallthrough =
| { readonly kind: "exit" }
| {
readonly kind: "branch";
readonly test: t.Expression;
readonly consequentExits: boolean;
readonly alternateExits: boolean;
};
interface PrefixEntry {
readonly position: number;
readonly fact: ElectronStatementFallthrough;
}
interface BlockIndex {
readonly positions: WeakMap<t.Node, number>;
readonly entries: readonly PrefixEntry[];
}
/**
* One collector's immutable syntax facts. Positions replace repeated body
* searches; ordinary fallthrough statements need no replay. Conditional tests
* remain present even when neither branch exits, since evaluating them belongs
* to the invocation's symbolic environment and can establish unknown coverage.
*/
export class ElectronCallPathBlockPrefixes {
readonly #blocks = new WeakMap<t.BlockStatement, BlockIndex>();
readonly #facts = new WeakMap<
t.Statement,
ElectronStatementFallthrough | null
>();
constructor(private readonly admitVisit: () => boolean) {}
statement(statement: t.Statement): ElectronStatementFallthrough | null {
const cached = this.#facts.get(statement);
if (cached !== undefined) return cached;
const fact: ElectronStatementFallthrough | null = symbolicDefiniteExit(
statement,
)
? { kind: "exit" }
: t.isIfStatement(statement)
? {
kind: "branch",
test: statement.test,
consequentExits: symbolicDefiniteExit(statement.consequent),
alternateExits: symbolicDefiniteExit(statement.alternate),
}
: null;
this.#facts.set(statement, fact);
return fact;
}
*before(
block: t.BlockStatement,
child: t.Node,
): Generator<
void,
| { readonly entries: readonly PrefixEntry[]; readonly before: number }
| undefined
> {
let index = this.#blocks.get(block);
if (index === undefined) {
const positions = new WeakMap<t.Node, number>();
const entries: PrefixEntry[] = [];
for (const [position, statement] of block.body.entries()) {
if (!this.admitVisit()) return undefined;
positions.set(statement, position);
const fact = this.statement(statement);
if (fact !== null) entries.push({ position, fact });
if ((position + 1) % 1024 === 0) yield;
}
index = { positions, entries };
this.#blocks.set(block, index);
}
const before = index.positions.get(child);
return before === undefined
? undefined
: { entries: index.entries, before };
}
}
@@ -1,6 +1,100 @@
import { describe, expect, it } from "vitest";
import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js";
it("preserves every direct observation in a long flat sibling block", () => {
const channels = Array.from({ length: 256 }, (_, index) => `flat:${index}`);
const result = analyzeJavaScriptStaticSource(`const e=require("electron"); {
${channels.map((channel) => `e.ipcRenderer.send("${channel}");`).join("\n")}
}`);
expect(result.electron.ipc.map(({ channel }) => channel)).toEqual(channels);
expect(result.electron.call_paths ?? []).toEqual([]);
expect(result.parse_status).toBe("complete");
});
it("keeps sibling guards, definite exits and nested shadowing separate", () => {
const result = analyzeJavaScriptStaticSource(`const e=require("electron"); {
if (stop) throw new Error("stop");
e.ipcMain.on("guarded", event => event.sender.copy());
{
const e = {ipcMain: {on() {}}};
e.ipcMain.on("shadowed", event => event.sender.copy());
}
if (true) throw new Error("end");
e.ipcMain.on("unreachable", event => event.sender.copy());
}`);
expect(
result.electron.call_paths?.map(
({ entry, terminal, steps, conditions }) => ({
entry,
terminal,
terminalStart: steps.at(-1)?.location.start,
conditions: conditions.map(({ expression }) => expression),
}),
),
).toEqual([
{
entry: "guarded",
terminal: "electron.ipcMain.on",
terminalStart: { line: 3, column: 4 },
conditions: ["!(stop)"],
},
{
entry: "guarded",
terminal: "ipc-event.sender.copy",
terminalStart: { line: 3, column: 37 },
conditions: ["!(stop)"],
},
]);
// Raw syntax after a definite exit remains an observation, not a reachable
// callback path. The shadowed ordinary object provides no Electron identity.
expect(result.electron.ipc.map(({ channel }) => channel)).toEqual([
"guarded",
"unreachable",
]);
});
it("evaluates shared return syntax separately for each invocation environment", () => {
const result = analyzeJavaScriptStaticSource(`const e=require("electron");
function install(channel, enabled) {
if (!enabled) return;
e.ipcMain.on(channel, event => event.sender.copy());
}
install("enabled", true);
install("disabled", false);
install("uncertain", externalFlag);
`);
expect(
result.electron.call_paths?.map(
({ entry, terminal, steps, conditions }) => ({
entry,
terminal,
invocationStart: steps[0]?.location.start,
conditions: conditions.map(({ expression }) => expression),
}),
),
).toEqual([
{
entry: "enabled",
terminal: "ipc-event.sender.copy",
invocationStart: { line: 6, column: 4 },
conditions: [],
},
{
entry: "uncertain",
terminal: "electron.ipcMain.on",
invocationStart: { line: 8, column: 4 },
conditions: ["!(!enabled)"],
},
{
entry: "uncertain",
terminal: "ipc-event.sender.copy",
invocationStart: { line: 8, column: 4 },
conditions: ["!(!enabled)"],
},
]);
expect(result.parse_status).toBe("partial");
});
const bundle = (entry: string) => `(() => {
const modules = {
2(module) { module.exports = require("electron"); },
+117 -79
View File
@@ -33,17 +33,22 @@ import {
boundedExpression,
contextBridgeMemberPathDepth,
} from "./electronStaticAnalysisValues.js";
import { argumentNode } from "./javascriptStaticAnalysisHelpers.js";
import {
argumentNode,
range,
calleeName,
} from "./javascriptStaticAnalysisHelpers.js";
import {
childNodes,
traverseJavaScriptValueAst,
traverseJavaScriptValueAstSteps,
} from "./javascriptSemanticTraversal.js";
import { range, calleeName } from "./javascriptStaticAnalysisHelpers.js";
import { ipcDescriptor } from "./electronStaticAnalysisIpc.js";
import { symbolicLogicalRight } from "./javascriptSymbolicControlFlow.js";
import {
symbolicDefiniteExit,
symbolicLogicalRight,
} from "./javascriptSymbolicControlFlow.js";
ElectronCallPathBlockPrefixes,
type ElectronStatementFallthrough,
} from "./electronCallPathBlockPrefixes.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
const callLabel = (node: t.Node): string =>
@@ -227,16 +232,19 @@ interface PathContext {
}
/** Substitutes inert wrapper syntax; every emitted path remains a candidate. */
export const collectElectronWrappedCallPaths = (
export function* collectElectronWrappedCallPathsSteps(
program: t.Program,
state: JavaScriptSemanticAnalysisState,
source: string,
accumulator: JavaScriptAnalysisAccumulator,
identity: JavaScriptElectronIdentity,
): readonly ElectronCallPath[] => {
): Generator<void, readonly ElectronCallPath[]> {
// Bounds repeated interprocedural expansion independently of bundle bytes.
// Exhaustion is reported as unknown coverage, never as a negative finding.
let remainingVisits = 1_000_000;
const prefixes = new ElectronCallPathBlockPrefixes(
() => --remainingVisits >= 0,
);
const resolver = new JavaScriptSymbolicBindings(state, program, identity);
const paths: ElectronCallPath[] = [];
const seen = new Set<string>();
@@ -282,28 +290,26 @@ export const collectElectronWrappedCallPaths = (
};
};
const sequentialFallthrough = (
statement: t.Statement,
fact: ElectronStatementFallthrough | null,
env: SymbolicEnvironment,
context: PathContext,
): { readonly continues: boolean; readonly context: PathContext } => {
if (symbolicDefiniteExit(statement)) return { continues: false, context };
if (!t.isIfStatement(statement)) return { continues: true, context };
const truth = symbolicTruth(resolver.evaluate(statement.test, env));
if (fact === null) return { continues: true, context };
if (fact.kind === "exit") return { continues: false, context };
const truth = symbolicTruth(resolver.evaluate(fact.test, env));
if (truth !== undefined)
return symbolicDefiniteExit(
truth ? statement.consequent : statement.alternate,
)
return (truth ? fact.consequentExits : fact.alternateExits)
? { continues: false, context }
: { continues: true, context };
if (symbolicDefiniteExit(statement.consequent))
if (fact.consequentExits)
return {
continues: true,
context: condition(context, statement.test, false),
context: condition(context, fact.test, false),
};
if (symbolicDefiniteExit(statement.alternate))
if (fact.alternateExits)
return {
continues: true,
context: condition(context, statement.test),
context: condition(context, fact.test),
};
return { continues: true, context };
};
@@ -1148,7 +1154,11 @@ export const collectElectronWrappedCallPaths = (
let next = context;
for (const statement of node.body) {
walk(statement, env, next);
const fallthrough = sequentialFallthrough(statement, env, next);
const fallthrough = sequentialFallthrough(
prefixes.statement(statement),
env,
next,
);
if (!fallthrough.continues) break;
next = fallthrough.context;
}
@@ -1194,70 +1204,98 @@ export const collectElectronWrappedCallPaths = (
call(node, env, context);
for (const child of childNodes(node)) walk(child, env, context);
}
traverseJavaScriptValueAst(program, state.erasedSyntaxNodes, {
enter: (node) => {
if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
return;
const isolatedSeed = t.isStringLiteral(node.arguments[0])
? undefined
: isolatedBridgeCall(node);
if (!t.isStringLiteral(node.arguments[0]) && isolatedSeed === undefined)
return;
const env = isolatedSeed?.env ?? resolver.environmentAt(node);
let context: PathContext = {
entryNode: node,
calls: [],
entry: "",
entryKind: null,
channel: null,
steps: [],
conditions: [],
active: new Set(),
};
for (
let child: t.Node = node, parent = state.parentsByNode.get(child);
parent && !t.isFunction(parent);
child = parent, parent = state.parentsByNode.get(child)
) {
if (t.isIfStatement(parent) || t.isConditionalExpression(parent)) {
if (child === parent.test) continue;
const truth = symbolicTruth(resolver.evaluate(parent.test, env));
const positive = child === parent.consequent;
if (truth !== undefined && truth !== positive) return;
if (truth === undefined)
context = condition(context, parent.test, positive);
} else if (t.isLogicalExpression(parent) && child === parent.right) {
const right = symbolicLogicalRight(
parent.operator,
resolver.evaluate(parent.left, env),
let prefixVisits = 0;
function* seed(
node: t.CallExpression | t.OptionalCallExpression,
): Generator<void, void> {
if (remainingVisits < 0) return;
const isolatedSeed = t.isStringLiteral(node.arguments[0])
? undefined
: isolatedBridgeCall(node);
if (!t.isStringLiteral(node.arguments[0]) && isolatedSeed === undefined)
return;
const env = isolatedSeed?.env ?? resolver.environmentAt(node);
let context: PathContext = {
entryNode: node,
calls: [],
entry: "",
entryKind: null,
channel: null,
steps: [],
conditions: [],
active: new Set(),
};
for (
let child: t.Node = node, parent = state.parentsByNode.get(child);
parent && !t.isFunction(parent);
child = parent, parent = state.parentsByNode.get(child)
) {
if (--remainingVisits < 0) return;
if (++prefixVisits % 1024 === 0) yield;
if (t.isIfStatement(parent) || t.isConditionalExpression(parent)) {
if (child === parent.test) continue;
const truth = symbolicTruth(resolver.evaluate(parent.test, env));
const positive = child === parent.consequent;
if (truth !== undefined && truth !== positive) return;
if (truth === undefined)
context = condition(context, parent.test, positive);
} else if (t.isLogicalExpression(parent) && child === parent.right) {
const right = symbolicLogicalRight(
parent.operator,
resolver.evaluate(parent.left, env),
);
if (right === false) return;
if (right === undefined)
context = condition(
context,
parent.left,
parent.operator === "??" ? "nullish" : parent.operator === "&&",
);
if (right === false) return;
if (right === undefined)
context = condition(
context,
parent.left,
parent.operator === "??" ? "nullish" : parent.operator === "&&",
);
} else if (t.isBlockStatement(parent)) {
const index = parent.body.findIndex(
(statement) => statement === child,
);
if (index >= 0) {
for (const statement of parent.body.slice(0, index)) {
const fallthrough = sequentialFallthrough(
statement,
env,
context,
);
if (!fallthrough.continues) return;
context = fallthrough.context;
}
} else if (t.isBlockStatement(parent)) {
const prefix = yield* prefixes.before(parent, child);
if (remainingVisits < 0) return;
if (prefix !== undefined) {
for (const entry of prefix.entries) {
if (entry.position >= prefix.before) break;
if (--remainingVisits < 0) return;
if (++prefixVisits % 1024 === 0) yield;
const fallthrough = sequentialFallthrough(entry.fact, env, context);
if (!fallthrough.continues) return;
context = fallthrough.context;
}
}
}
call(node, env, context, isolatedSeed?.callee);
}
call(node, env, context, isolatedSeed?.callee);
}
// The shared walker supplies one bounded batch at a time. Prefix work must
// run outside its synchronous callback so cancellation can interrupt a large
// block index or a genuinely long sequence of contextual branch facts.
const seeds: (t.CallExpression | t.OptionalCallExpression)[] = [];
const traversal = traverseJavaScriptValueAstSteps(
program,
state.erasedSyntaxNodes,
{
enter: (node) => {
if (t.isCallExpression(node) || t.isOptionalCallExpression(node))
seeds.push(node);
},
},
});
);
try {
for (;;) {
const batch = traversal.next();
for (const node of seeds) {
yield* seed(node);
if (remainingVisits < 0) break;
}
seeds.length = 0;
if (batch.done || remainingVisits < 0) break;
yield;
}
} finally {
traversal.return();
}
recordBridgeExposures();
if (remainingVisits < 0 || resolver.truncated) {
accumulator.unknownFindings += 1;
@@ -1265,4 +1303,4 @@ export const collectElectronWrappedCallPaths = (
}
if (resolver.unresolved) accumulator.unknownFindings += 1;
return paths;
};
}
@@ -21,6 +21,9 @@ it("preserves possible ambient effects and discloses projection widening in sour
expect(ir.limitations).toContain(RETURNED_REFERENCE_WIDENING_LIMITATION);
const facts = analyzeJavaScriptStaticSource(source);
expect(facts.limitations).toContain(RETURNED_REFERENCE_WIDENING_LIMITATION);
expect(facts.analysis_coverage?.semantics.uncertainty).toContain(
"returned-reference-widening",
);
expect(facts.endpoints).toEqual(
expect.arrayContaining([
expect.objectContaining({ value: "/preserved", mechanism: "call:fetch" }),
@@ -1,13 +1,21 @@
import { z } from "zod";
/** Primitive values admitted into the constant lattice. */
export type JavaScriptSemanticPrimitive = string | number | boolean | null;
/** Resource bound that prevented an exact semantic value. */
export type JavaScriptSemanticResourceLimit =
| "primitive-candidates"
| "primitive-bytes"
| "expression-depth"
| "getter-expansion"
| "alias-selections";
export const javaScriptSemanticResourceLimitSchema = z.enum([
"primitive-candidates",
"primitive-bytes",
"expression-depth",
"getter-expansion",
"alias-selections",
]);
/** Resource-limit classifications shared by static facts and semantic values. */
export type JavaScriptSemanticResourceLimit = z.infer<
typeof javaScriptSemanticResourceLimitSchema
>;
type JavaScriptSemanticObjectValue = {
readonly status: "object";
@@ -3,7 +3,7 @@ import * as t from "@babel/types";
import { JavaScriptRouteScan } from "./javascriptRouteScan.js";
import type { JavaScriptRouteSourceFacts } from "./javascriptRouteFacts.js";
import { RETURNED_REFERENCE_WIDENING_LIMITATION } from "./javascriptSemanticCoverage.js";
import { collectElectronWrappedCallPaths } from "./electronWrappedCallPaths.js";
import { collectElectronWrappedCallPathsSteps } from "./electronWrappedCallPaths.js";
import {
ELECTRON_IDENTITY_LIMITATION,
ELECTRON_MODULE,
@@ -76,6 +76,10 @@ import {
isTypeOnlyImportDeclaration,
semanticImportEqualsSpecifier,
} from "./javascriptModuleDeclaration.js";
import {
javaScriptStaticParseStatus,
type JavaScriptStaticCoverage,
} from "./javascriptStaticCoverage.js";
/** Parse one JavaScript artifact and recover static structure only. */
export const analyzeJavaScriptStaticSource = (
@@ -154,7 +158,7 @@ export function* analyzeParsedJavaScriptStaticSourceSteps(
finishPartialNetworkEndpoints(findings, bindings, index);
finishSqliteStorageCalls(findings, bindings, index);
}
accumulator.electronCallPaths = collectElectronWrappedCallPaths(
accumulator.electronCallPaths = yield* collectElectronWrappedCallPathsSteps(
file.program,
bindings,
source,
@@ -191,6 +195,43 @@ const finalizeStaticAnalysis = (
routes: JavaScriptRouteSourceFacts,
): JavaScriptStaticAnalysis => {
const parserErrors = file.errors.length;
const typeOnlySideEffect = hasUncertainTypeOnlyModuleSideEffect(file.program);
const electronObserved = accumulator.references.some(
({ value }) =>
value.specifier !== null && ELECTRON_MODULE.test(value.specifier),
);
const resourceLimits = [...bindings.valueResourceLimits].sort();
const coverage: JavaScriptStaticCoverage = {
syntax:
parserErrors === 0
? { status: "complete", error_count: 0 }
: { status: "recovered", error_count: parserErrors },
semantics: {
unknown_findings: accumulator.unknownFindings,
uncertainty: [
...(accumulator.unknownFindings > 0
? ["dynamic-findings" as const]
: []),
...(bindings.returnedReferenceWidenings.size > 0
? ["returned-reference-widening" as const]
: []),
...(typeOnlySideEffect
? ["type-only-module-side-effect" as const]
: []),
],
},
examination: {
status:
resourceLimits.length > 0 ||
accumulator.electronCallPathLimitReached === true
? "limited"
: "complete",
semantic_resource_limits: resourceLimits,
electron_call_paths_limited:
accumulator.electronCallPathLimitReached === true,
},
applicability: { electron: electronObserved ? "observed" : "unknown" },
};
const limitations = [
...(bindings.valueResourceLimits.has("alias-selections")
? [semanticResourceLimitReason("alias-selections")]
@@ -218,26 +259,17 @@ const finalizeStaticAnalysis = (
: [
"One or more static keys, expressions, or Electron boundary values were dynamic and remain unknown.",
]),
...(hasUncertainTypeOnlyModuleSideEffect(file.program)
...(typeOnlySideEffect
? [
"Compiler options are unavailable; inline type-only imports or re-exports may preserve a module side-effect dependency.",
]
: []),
...(accumulator.references.some(
({ value }) =>
value.specifier !== null && ELECTRON_MODULE.test(value.specifier),
)
? [ELECTRON_IDENTITY_LIMITATION]
: []),
...(electronObserved ? [ELECTRON_IDENTITY_LIMITATION] : []),
"JavaScript syntax was parsed as data and was never evaluated.",
];
return {
parse_status:
parserErrors > 0 ||
accumulator.unknownFindings > 0 ||
bindings.valueResourceLimits.has("alias-selections")
? "partial"
: "complete",
parse_status: javaScriptStaticParseStatus(coverage),
analysis_coverage: coverage,
parse_error_count: parserErrors,
visited_ast_nodes: accumulator.visitedNodes,
references: finalizeLocatedFindings(
@@ -23,10 +23,17 @@ import {
compareUnicodeCodePoints,
compositeKey,
} from "../unicodeCodePointOrder.js";
import { unexaminedJavaScriptStaticCoverage } from "./javascriptStaticCoverage.js";
/** Explicit result for source text that Babel cannot parse. */
export const failedJavaScriptStaticAnalysis = (): JavaScriptStaticAnalysis => ({
parse_status: "failed",
analysis_coverage: {
...unexaminedJavaScriptStaticCoverage(
"JavaScript source could not be parsed.",
),
syntax: { status: "failed", error_count: null },
},
parse_error_count: 1,
visited_ast_nodes: 0,
references: [],
@@ -1,5 +1,6 @@
import type { ElectronStaticFindings } from "./electronStaticAnalysisTypes.js";
import type { JavaScriptRouteSourceFacts } from "./javascriptRouteFacts.js";
import type { JavaScriptStaticCoverage } from "./javascriptStaticCoverage.js";
/** Token semantics shared by bundle producers and worker admission. */
export const JAVASCRIPT_AST_FINGERPRINT_ALGORITHM = "babel-ast-v3";
@@ -124,7 +125,10 @@ export interface JavaScriptRolePath {
/** Deterministic AST-only analysis of one JavaScript source file. */
export interface JavaScriptStaticAnalysis {
/** Established overall summary; syntax alone is reported in analysis_coverage. */
readonly parse_status: "complete" | "partial" | "failed";
/** Historical facts omit this measurement; consumers must not infer it. */
readonly analysis_coverage?: JavaScriptStaticCoverage;
readonly parse_error_count: number;
readonly visited_ast_nodes: number;
readonly references: readonly JavaScriptStaticReference[];
@@ -0,0 +1,106 @@
import { expect, it } from "vitest";
import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js";
import {
javaScriptStaticParseStatus,
unexaminedJavaScriptStaticCoverage,
} from "./javascriptStaticCoverage.js";
import { aliasSelectionBudgetSource } from "../../../tests/fixtures/javascriptAliasSelectionBudget.js";
it("separates dynamic semantic uncertainty from completed syntax and examination", () => {
const result = analyzeJavaScriptStaticSource(
"const {ipcMain}=require('electron'); ipcMain.on(channel, () => {});",
);
expect(result.parse_status).toBe("partial");
expect(result.analysis_coverage).toMatchObject({
syntax: { status: "complete", error_count: 0 },
semantics: { unknown_findings: 1, uncertainty: ["dynamic-findings"] },
examination: {
status: "complete",
semantic_resource_limits: [],
electron_call_paths_limited: false,
},
applicability: { electron: "observed" },
});
expect(result.electron.ipc).toMatchObject([
{ channel: null, channel_expression: "channel" },
]);
});
it("distinguishes recovered syntax from unparseable source without inventing an error total", () => {
const recovered = analyzeJavaScriptStaticSource("let value; let value;");
expect(recovered.parse_status).toBe("partial");
expect(recovered.analysis_coverage?.syntax).toEqual({
status: "recovered",
error_count: recovered.parse_error_count,
});
const failed = analyzeJavaScriptStaticSource("const value = ;");
expect(failed.parse_status).toBe("failed");
expect(failed.analysis_coverage).toMatchObject({
syntax: { status: "failed", error_count: null },
semantics: { unknown_findings: null },
examination: { status: "not-examined" },
});
});
it("keeps framework applicability unknown without positive Electron evidence", () => {
const ordinary = analyzeJavaScriptStaticSource("export const value = 1;");
expect(ordinary.parse_status).toBe("complete");
expect(ordinary.analysis_coverage?.applicability.electron).toBe("unknown");
const electron = analyzeJavaScriptStaticSource(
"const {ipcMain}=require('electron');",
);
expect(electron.analysis_coverage?.applicability.electron).toBe("observed");
});
it("reports compiler-dependent module side effects independently of complete syntax", () => {
const result = analyzeJavaScriptStaticSource(
"import { type Config } from './config';",
);
expect(result.analysis_coverage?.syntax).toEqual({
status: "complete",
error_count: 0,
});
expect(result.analysis_coverage?.semantics.uncertainty).toContain(
"type-only-module-side-effect",
);
});
it("retains typed examination limits alongside successfully parsed source", () => {
const result = analyzeJavaScriptStaticSource(aliasSelectionBudgetSource());
expect(result.parse_status).toBe("partial");
expect(result.analysis_coverage).toMatchObject({
syntax: { status: "complete", error_count: 0 },
examination: {
status: "limited",
semantic_resource_limits: ["alias-selections"],
},
});
});
it("preserves the legacy summary responsibility without relabeling independent limitations", () => {
const base = analyzeJavaScriptStaticSource(
"export const value = 1;",
).analysis_coverage!;
expect(
javaScriptStaticParseStatus({
...base,
semantics: {
unknown_findings: 0,
uncertainty: ["returned-reference-widening"],
},
examination: {
...base.examination,
status: "limited",
electron_call_paths_limited: true,
},
}),
).toBe("complete");
expect(
unexaminedJavaScriptStaticCoverage("Caller selected another source."),
).toMatchObject({
syntax: { status: "not-examined", error_count: null },
semantics: { unknown_findings: null },
examination: { status: "not-examined" },
unexamined_reason: "Caller selected another source.",
});
});
@@ -0,0 +1,72 @@
import { z } from "zod";
import { javaScriptSemanticResourceLimitSchema } from "./javascriptSemanticValueTypes.js";
/** Independent observations from one inert static pass; no runtime completeness claim. */
export const javaScriptStaticCoverageSchema = z.strictObject({
syntax: z.discriminatedUnion("status", [
z.strictObject({
status: z.literal("complete"),
error_count: z.literal(0),
}),
z.strictObject({
status: z.literal("recovered"),
error_count: z.int().positive(),
}),
z.strictObject({ status: z.literal("failed"), error_count: z.null() }),
z.strictObject({
status: z.literal("not-examined"),
error_count: z.null(),
}),
]),
semantics: z.strictObject({
unknown_findings: z.int().nonnegative().nullable(),
uncertainty: z.array(
z.enum([
"dynamic-findings",
"returned-reference-widening",
"type-only-module-side-effect",
]),
),
}),
examination: z.strictObject({
status: z.enum(["complete", "limited", "not-examined"]),
semantic_resource_limits: z.array(javaScriptSemanticResourceLimitSchema),
electron_call_paths_limited: z.boolean(),
}),
applicability: z.strictObject({ electron: z.enum(["observed", "unknown"]) }),
unexamined_reason: z.string().min(1).exactOptional(),
});
export type JavaScriptStaticCoverage = z.infer<
typeof javaScriptStaticCoverageSchema
>;
/** Preserve the established conservative summary, independently of finer facts. */
export const javaScriptStaticParseStatus = (
coverage: JavaScriptStaticCoverage,
): "complete" | "partial" | "failed" =>
coverage.syntax.status === "failed" ||
coverage.syntax.status === "not-examined"
? "failed"
: coverage.syntax.status === "recovered" ||
(coverage.semantics.unknown_findings ?? 0) > 0 ||
coverage.examination.semantic_resource_limits.includes(
"alias-selections",
)
? "partial"
: "complete";
/** Unperformed work has no measured syntax, finding count or framework applicability. */
export const unexaminedJavaScriptStaticCoverage = (
reason: string,
): JavaScriptStaticCoverage => ({
syntax: { status: "not-examined", error_count: null },
semantics: { unknown_findings: null, uncertainty: [] },
examination: {
status: "not-examined",
semantic_resource_limits: [],
electron_call_paths_limited: false,
},
applicability: { electron: "unknown" },
unexamined_reason: reason,
});
@@ -10,6 +10,28 @@ import { analyzeJavaScriptSemantics } from "../../domain/javascript/javascriptSe
import { getterExpansionSource } from "../../../tests/fixtures/javascriptGetterExpansion.js";
import { aliasSelectionBudgetSource } from "../../../tests/fixtures/javascriptAliasSelectionBudget.js";
it("preserves independent static coverage and historical unmeasured facts", () => {
const facts = analyzeJavaScriptStaticSource("require(moduleName);");
expect(
javaScriptWorkerStaticAnalysisSchema.parse(
JSON.parse(JSON.stringify(facts)),
),
).toEqual(facts);
const { analysis_coverage: _coverage, ...historical } = facts;
expect(javaScriptWorkerStaticAnalysisSchema.parse(historical)).toEqual(
historical,
);
expect(
javaScriptWorkerStaticAnalysisSchema.safeParse({
...facts,
analysis_coverage: {
...facts.analysis_coverage,
syntax: { status: "complete", error_count: -1 },
},
}).success,
).toBe(false);
});
it("transfers bounded alias selection coverage across the worker boundary", () => {
const source = aliasSelectionBudgetSource();
const facts = analyzeJavaScriptStaticSource(source);
@@ -1,20 +1,22 @@
import { javaScriptSemanticUnknownOmissionsSchema } from "../../domain/javascript/javascriptSemanticGraphSchemas.js";
import { z } from "zod";
import { digestSchema } from "../../domain/digests.js";
import type { JavaScriptStaticAnalysis } from "../../domain/javascript/javascriptStaticAnalysisTypes.js";
import { javaScriptRouteSourceFactsSchema } from "../../domain/javascript/javascriptRouteFacts.js";
import { JAVASCRIPT_AST_FINGERPRINT_ALGORITHM } from "../../domain/javascript/javascriptStaticAnalysisTypes.js";
import type {
JavaScriptSemanticProperty,
JavaScriptSemanticValue,
} from "../../domain/javascript/javascriptSemanticValueTypes.js";
import {
javaScriptSemanticUnknownOmissionsSchema,
javaScriptSemanticNodeSchema,
javaScriptSemanticRelationSchema,
javaScriptSemanticUnknownSchema,
javaScriptSemanticFingerprintSchema,
javaScriptSemanticEvidenceContextSchema,
} from "../../domain/javascript/javascriptSemanticGraphSchemas.js";
import { z } from "zod";
import { digestSchema } from "../../domain/digests.js";
import type { JavaScriptStaticAnalysis } from "../../domain/javascript/javascriptStaticAnalysisTypes.js";
import { javaScriptRouteSourceFactsSchema } from "../../domain/javascript/javascriptRouteFacts.js";
import { javaScriptStaticCoverageSchema } from "../../domain/javascript/javascriptStaticCoverage.js";
import { JAVASCRIPT_AST_FINGERPRINT_ALGORITHM } from "../../domain/javascript/javascriptStaticAnalysisTypes.js";
import type {
JavaScriptSemanticProperty,
JavaScriptSemanticValue,
} from "../../domain/javascript/javascriptSemanticValueTypes.js";
import { javaScriptSemanticResourceLimitSchema } from "../../domain/javascript/javascriptSemanticValueTypes.js";
const range =
javaScriptSemanticNodeSchema.shape.identity.shape.source_range.unwrap();
@@ -319,6 +321,7 @@ const referenceShape = {
export const javaScriptWorkerStaticAnalysisSchema = z
.strictObject({
parse_status: z.enum(["complete", "partial", "failed"]),
analysis_coverage: javaScriptStaticCoverageSchema.exactOptional(),
routes: javaScriptRouteSourceFactsSchema.exactOptional(),
parse_error_count: count,
visited_ast_nodes: count,
@@ -433,13 +436,6 @@ export const javaScriptWorkerStaticAnalysisSchema = z
},
})) satisfies z.ZodType<JavaScriptStaticAnalysis>;
const resourceLimit = z.enum([
"primitive-candidates",
"primitive-bytes",
"expression-depth",
"getter-expansion",
"alias-selections",
]);
const primitive = z.union([z.string(), z.number(), z.boolean(), z.null()]);
const property: z.ZodType<JavaScriptSemanticProperty> = z.lazy(() =>
z.strictObject({
@@ -487,7 +483,7 @@ const semanticValue: z.ZodType<JavaScriptSemanticValue> = z.lazy(() =>
z.strictObject({
status: z.enum(["unknown", "ambiguous", "cycle"]),
reason: z.string(),
resourceLimit: resourceLimit.optional(),
resourceLimit: javaScriptSemanticResourceLimitSchema.optional(),
}),
])
.transform((value) => {
@@ -544,19 +540,19 @@ const coverage = z
z.strictObject({
status: z.literal("complete"),
omittedCount: z.literal(0),
resourceLimits: z.array(resourceLimit).optional(),
resourceLimits: z.array(javaScriptSemanticResourceLimitSchema).optional(),
widenedReturnedReferences: z.array(range).optional(),
}),
z.strictObject({
status: z.literal("partial"),
omittedCount: z.union([z.literal(0), z.null()]),
resourceLimits: z.array(resourceLimit).optional(),
resourceLimits: z.array(javaScriptSemanticResourceLimitSchema).optional(),
widenedReturnedReferences: z.array(range).optional(),
}),
z.strictObject({
status: z.literal("failed"),
omittedCount: z.null(),
resourceLimits: z.array(resourceLimit).optional(),
resourceLimits: z.array(javaScriptSemanticResourceLimitSchema).optional(),
widenedReturnedReferences: z.array(range).optional(),
}),
])
@@ -92,6 +92,15 @@ cliTest.for([
expect(properties?.parse_status).toBe(
path === "main.mjs" ? "complete" : null,
);
if (/\.(?:mjs|js)$/u.test(path))
expect(properties?.analysis_coverage).toMatchObject({
syntax: {
status: requested.includes(path) ? "complete" : "not-examined",
},
examination: {
status: requested.includes(path) ? "complete" : "not-examined",
},
});
expect(Buffer.compare(await readFile(join(directory, path)), bytes)).toBe(
0,
);
@@ -13,8 +13,11 @@ import {
analysisViewJavaScriptEvidence,
analysisViewBindJavaScriptGraphs,
} from "../../fixtures/analysisView.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { analysisViewResultSchema } from "../../../src/domain/analysisView/analysisView.js";
import { parseEvidence, type Evidence } from "../../../src/domain/evidence.js";
import {
analysisViewResultSchema,
type AnalysisViewResult,
} from "../../../src/domain/analysisView/analysisView.js";
import {
createJavaScriptApplicationGraph,
createJavaScriptApplicationNode,
@@ -46,6 +49,57 @@ const facts = async (
return { evidence, page, wire };
};
/** Summary counts and both Evidence lookup paths preserve selected/excluded source facts. */
const expectSelectedSourceCoverage = async (
harness: Awaited<ReturnType<typeof connect>>,
parent: Evidence,
summary: Extract<
AnalysisViewResult,
{ kind: "summary"; parent_operation: "analyze_javascript_application" }
>,
): Promise<void> => {
expect(summary.summary.coverage).toMatchObject({
static_sources: {
sources: 2,
unmeasured_sources: 0,
syntax: { complete: 1, "not-examined": 1 },
examination: { complete: 1, "not-examined": 1 },
electron_applicability: { observed: 0, unknown: 2 },
},
});
const query = {
kind: "javascript-facts",
where: { paths: ["a.js", "b.js"], kinds: ["javascript-asset"] },
property_keys: ["analysis_coverage"],
limit: 64,
};
const retained = await facts(
harness,
{ kind: "retained-evidence", evidence_id: parent.evidence_id },
query,
);
const portable = await facts(
harness,
{ kind: "inline", evidence: parent },
query,
);
expect(portable.evidence).toEqual(retained.evidence);
expect(
retained.page.rows.map(({ properties }) => properties.analysis_coverage),
).toEqual(
expect.arrayContaining([
expect.objectContaining({
syntax: { status: "complete", error_count: 0 },
}),
expect.objectContaining({
syntax: { status: "not-examined", error_count: null },
unexamined_reason:
"Source is outside the caller-selected source_paths.",
}),
]),
);
};
it("pages selected real filesystem observations with shared identities and portable/retained parity", async () => {
const root = await createTestTempDirectory("rea-retained-file-facts-");
await Promise.all(
@@ -68,9 +122,15 @@ it("pages selected real filesystem observations with shared identities and porta
const summary = analysisViewResultSchema.parse(
parseEvidence(preparation.structuredContent).normalized_result,
);
if (
summary.kind !== "summary" ||
summary.parent_operation !== "analyze_javascript_application"
)
throw new Error("Expected JavaScript summary view");
const parent = harness.session.evidenceById(summary.parent_evidence_id);
if (parent === undefined) throw new Error("Missing retained parent");
const source = { kind: "retained-evidence", evidence_id: parent.evidence_id };
await expectSelectedSourceCoverage(harness, parent, summary);
const query = {
kind: "javascript-facts",
where: { paths: ["first.node", "second.node", "package.json", "missing"] },