fix(javascript): admit candidate endpoint interpretation in worker output

b58adafa4 emits `interpretation: "candidate"` on static endpoint findings,
but the worker's strict endpoint schema still rejected the key, so
`analyze_javascript_application` failed output validation for any source
with a candidate endpoint. Admit the field as an exact optional literal.

Update application acceptance and boundary expectations to current main:
endpoint identity keys tag literal values (88afcc738), source-map
observations and edges carry `resolution_status` (b58adafa4), and a proven
Webpack bootstrap reports bundled contextBridge exposures as direct syntax.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kaoru0822 2026-10-14 02:36:45 +00:00
parent aff46f51fd
commit 0fdac09259
5 changed files with 31 additions and 9 deletions

View File

@ -328,6 +328,7 @@ export const javaScriptWorkerStaticAnalysisSchema = z
kind: z.enum(["route", "network"]),
value: z.string(),
mechanism: z.string(),
interpretation: z.literal("candidate").exactOptional(),
}),
),
storage: z.array(

View File

@ -485,6 +485,7 @@ it("preserves complete line-comment source-map URLs through CLI and MCP", async
declared_url: declaredUrl,
declared_url_sha256: digest,
resolved_path: "app.js.map",
resolution_status: "resolved",
available: true,
},
evidence: expect.objectContaining({
@ -507,6 +508,7 @@ it("preserves complete line-comment source-map URLs through CLI and MCP", async
properties: {
declared_url_sha256: digest,
resolved_path: "app.js.map",
resolution_status: "resolved",
},
}),
);
@ -565,6 +567,7 @@ it("resolves source-map URL paths once without changing their declarations", asy
declared_url: declaredUrl,
declared_url_sha256: digest,
resolved_path: path,
resolution_status: "resolved",
available: true,
},
evidence: expect.objectContaining({
@ -587,7 +590,11 @@ it("resolves source-map URL paths once without changing their declarations", asy
expect.objectContaining({
relation: "maps_to",
target_node_id: map?.node_id,
properties: { declared_url_sha256: digest, resolved_path: path },
properties: {
declared_url_sha256: digest,
resolved_path: path,
resolution_status: "resolved",
},
}),
);
}
@ -1576,8 +1583,11 @@ it("retains bundled isolated-world contextBridge APIs through dedicated and gene
world: "isolated",
world_id: { status: "literal", value: 1001, expression: null },
api_key: "isolatedApi",
api_status: "object-literal",
members: ["ping"],
resolution_basis: "wrapper-substitution",
unknown_members: 0,
// The proven Webpack bootstrap resolves `load(2)` directly.
resolution_basis: "direct-syntax",
}),
evidence: expect.objectContaining({
location: expect.objectContaining({

View File

@ -73,6 +73,7 @@ cliTest.for(["CLI", "stdio MCP"] as const)(
declared_url: url,
declared_url_sha256: digest,
resolved_path: url,
resolution_status: "resolved",
available: true,
},
evidence: expect.objectContaining({
@ -92,7 +93,11 @@ cliTest.for(["CLI", "stdio MCP"] as const)(
expect.objectContaining({
relation: "maps_to",
target_node_id: map?.node_id,
properties: { declared_url_sha256: digest, resolved_path: url },
properties: {
declared_url_sha256: digest,
resolved_path: url,
resolution_status: "resolved",
},
}),
);
},

View File

@ -108,7 +108,9 @@ cliTest(
),
).toHaveLength(1);
expect(repeatedNode.observations).toHaveLength(2);
expect(repeatedNode.identity).toMatchObject({ key: "/ok" });
expect(repeatedNode.identity).toMatchObject({
key: JSON.stringify(["literal", "/ok"]),
});
expect(
repeatedNode.observations.every(({ label }) => label === "/ok"),
).toBe(true);

View File

@ -79,6 +79,10 @@ it("retains empty literal evidence across the application boundary surfaces", as
);
});
/** Endpoint identity keys tag literal values apart from candidates. */
const literalEndpointKey = (value: string): string =>
JSON.stringify(["literal", value]);
it("keeps bridge entry paths stable through worker validation and API seed matching", async () => {
const output = await analyzeSource(`
const { contextBridge, ipcRenderer } = require("electron");
@ -144,7 +148,7 @@ it("analyzes a plain long-line loader without package metadata", async () => {
for (const node of nodes) {
expect(node.identity).toMatchObject({
strategy: "artifact-local-key",
key: "",
key: node.kind === "endpoint" ? literalEndpointKey("") : "",
});
expect(node.observations.map(({ label }) => label)).toEqual([null]);
}
@ -165,9 +169,9 @@ it("keeps exact values for empty literals without synthetic application labels",
// a literal two-quote endpoint; both keep exact identities.
expect(endpoints).toEqual(
expect.arrayContaining([
{ key: "", labels: [null], values: [""] },
{ key: '""', labels: ['""'], values: ['""'] },
{ key: "/api", labels: ["/api"], values: ["/api"] },
{ key: literalEndpointKey(""), labels: [null], values: [""] },
{ key: literalEndpointKey('""'), labels: ['""'], values: ['""'] },
{ key: literalEndpointKey("/api"), labels: ["/api"], values: ["/api"] },
]),
);
expect(endpoints).toHaveLength(3);
@ -252,7 +256,7 @@ it("does not match an empty endpoint with a literal two-quote seed", async () =>
case_sensitive: true,
}).filter(({ node_id: nodeId }) => endpoints.has(nodeId));
expect(matches.map(({ node_id: nodeId }) => endpoints.get(nodeId))).toEqual([
'""',
literalEndpointKey('""'),
]);
});