fix(javascript): retain runtime candidate ambiguity

Keep referenced ambiguous candidates unknown instead of classifying them as absent.

Refs: #3192
This commit is contained in:
morluto committed 2026-10-14 17:49:15 +08:00
1 parent 44c9d2924d
commit 5e79d468e5
3 files changed
+133

No files matched your search

@@ -46,6 +46,7 @@ const classifyLayer = (
): { readonly states: JavaScriptStaticLoadState[] } => {
const nodes = layer.graph.nodes.filter(isLoadStateNode);
const direct = new Map<string, string[]>();
const ambiguous = new Set<string>();
for (const match of matches)
if (
match.status === "matched" &&
@@ -53,6 +54,10 @@ const classifyLayer = (
match.static_node_id !== null
)
append(direct, match.static_node_id, match.runtime_node_id);
else if (match.status === "ambiguous")
for (const candidate of match.candidate_static_nodes)
if (candidate.static_layer_id === layer.layerId)
ambiguous.add(candidate.static_node_id);
const resident = residentNodes(layer, new Set(direct.keys()));
const runtimeScope = createStaticRuntimeScope(layer, runtimeEntities);
const scopedEntities = runtimeEntities.filter(
@@ -77,6 +82,12 @@ const classifyLayer = (
runtimeNodeIds: [],
reason: "containing-asset-was-loaded",
});
if (ambiguous.has(node.node_id))
return state(layer, node, {
status: "unknown",
runtimeNodeIds: [],
reason: "ambiguous-runtime-candidate",
});
const nodeWithinScope = staticNodeWithinRuntimeScope(node, runtimeScope);
if (layerComplete && nodeWithinScope)
return state(layer, node, {
@@ -228,6 +228,7 @@ const staticLoadStateSchema = z.strictObject({
"not-observed-in-bounded-capture",
"layer-outside-runtime-scope",
"static-or-runtime-coverage-incomplete",
"ambiguous-runtime-candidate",
]),
});
+121
View File
@@ -13,6 +13,12 @@ import {
type FakeCdpBrowser,
} from "../../fixtures/fakeCdpBrowser.js";
import { writeElectronBoundaryFixture } from "../../fixtures/electronBoundaryApplication.js";
import {
SOURCE,
applicationFixture,
analyzeFixture,
electronRuntimeEvidence,
} from "../../fixtures/javascriptRuntimeReconciliation.js";
import { JAVASCRIPT_RUNTIME_RECONCILIATION_EXAMPLE } from "../../../src/contracts/javascript/javascriptRuntimeReconciliationExample.js";
const execute = promisify(execFile);
@@ -219,8 +225,123 @@ describe("Electron CLI runtime reconciliation", () => {
},
INTEGRATION_TEST_TIMEOUT_MS,
);
it(
"keeps ambiguous runtime candidates unknown while unrelated scoped assets remain absent",
async () => {
const application = await applicationFixture();
const assets = await createTestTempDirectory("rea-runtime-cli-assets-");
const runtime = await createTestTempDirectory("rea-runtime-cli-live-");
const inputPath = join(assets, "reconciliation.json");
temporary.push(application, assets, runtime);
await Promise.all([
writeFile(join(assets, "app.js"), SOURCE),
writeFile(join(assets, "absent.js"), "export const absent = true;\n"),
writeFile(join(runtime, "app.js"), SOURCE),
]);
const rootMapping = [{ kind: "file-root", root: runtime }] as const;
const input = {
static_layers: [
{
role: "application",
analysis: await analyzeFixture(application),
runtime_mappings: rootMapping,
},
{
role: "assets",
analysis: await analyzeFixture(assets),
runtime_mappings: rootMapping,
},
],
runtime_observations: [
electronRuntimeEvidence(runtime, SOURCE, { includeWorker: false }),
],
};
await writeFile(inputPath, JSON.stringify(input));
const reconciled = await runCli(
["reconcile-javascript-runtime", inputPath, "--json"],
process.env,
);
const normalized =
isRecord(reconciled) && isRecord(reconciled.normalized_result)
? reconciled.normalized_result
: undefined;
const reconciliations = normalized?.reconciliations;
const loadStates = normalized?.static_load_states;
if (!Array.isArray(reconciliations) || !Array.isArray(loadStates))
throw new Error("Missing runtime reconciliation output");
const ambiguous = reconciliations.find(
(item) =>
isRecord(item) &&
item.status === "ambiguous" &&
item.entity_kind === "script",
);
if (
!isRecord(ambiguous) ||
!Array.isArray(ambiguous.candidate_static_nodes)
)
throw new Error("Missing ambiguous runtime script");
const candidateIds = ambiguous.candidate_static_nodes.flatMap((item) =>
isRecord(item) && typeof item.static_node_id === "string"
? [item.static_node_id]
: [],
);
expect(candidateIds).toHaveLength(2);
const candidateStates = loadStates.filter(
(item) =>
isRecord(item) &&
typeof item.static_node_id === "string" &&
candidateIds.includes(item.static_node_id),
);
expect(candidateStates).toHaveLength(2);
expect(
candidateStates.every(
(item) =>
isRecord(item) &&
item.status === "unknown" &&
item.reason === "ambiguous-runtime-candidate",
),
).toBe(true);
expect(loadStates).toEqual(
expect.arrayContaining([
expect.objectContaining({
status: "not-observed-in-capture",
reason: "not-observed-in-bounded-capture",
}),
]),
);
const singleLayerPath = join(application, "single-reconciliation.json");
await writeFile(
singleLayerPath,
JSON.stringify({
static_layers: [input.static_layers[0]],
runtime_observations: input.runtime_observations,
}),
);
const uniquelyMatched = await runCli(
["reconcile-javascript-runtime", singleLayerPath, "--json"],
process.env,
);
expect(uniquelyMatched).toMatchObject({
normalized_result: {
static_load_states: expect.arrayContaining([
expect.objectContaining({
status: "loaded",
reason: "runtime-script-correspondence",
}),
]),
},
});
},
INTEGRATION_TEST_TIMEOUT_MS,
);
});
const isRecord = (value: unknown): value is Record<string, unknown> =>
typeof value === "object" && value !== null && !Array.isArray(value);
const runCli = async (
arguments_: readonly string[],
environment: NodeJS.ProcessEnv,