fix(javascript): retain unresolved value frontiers in provenance queries

This commit is contained in:
morluto
2026-10-10 01:39:37 +08:00
parent b628bba1fa
commit 3e19875c7f
4 changed files with 75 additions and 23 deletions
@@ -5,22 +5,66 @@ import { expect, it } from "vitest";
import { createTestTempDirectory } from "../../../tests/fixtures/temporaryDirectory.js";
import { javascriptApplicationAnalysisResultSchema } from "../../domain/javascript/javascriptApplicationAnalysis.js";
import { javaScriptSemanticQueryResultSchema } from "../../domain/javascript/javascriptSemanticQuerySchemas.js";
import { queryJavaScriptSemanticGraph } from "../../domain/javascript/javascriptSemanticQuery.js";
import { analyzeJavaScriptApplication } from "./JavaScriptApplicationService.js";
it("does not expose a resolved positive-zero literal for a negative-zero export", async () => {
const root = await createTestTempDirectory("rea-signed-zero-");
await writeFile(join(root, "app.js"), "export const value = -0;\n");
const result = await analyzeJavaScriptApplication({
input_path: root,
format: "directory",
});
if (!result.ok) throw result.error;
const analysis = javascriptApplicationAnalysisResultSchema.parse(
result.value.normalized_result,
);
expect(
analysis.semantic_graph.nodes.filter(
({ kind, properties }) => kind === "literal" && properties.value === 0,
),
).toEqual([]);
});
it.each([
{
source: "export const value = -0;\n",
target: { kind: "binding", label: "value" },
},
{
source: "export const root = { value: -0 };\n",
target: { kind: "property-slot", propertyPointer: "/value" },
},
])(
"keeps negative-zero value unknown in an exported query (%#)",
async ({ source, target }) => {
const root = await createTestTempDirectory("rea-signed-zero-");
await writeFile(join(root, "app.js"), source);
const result = await analyzeJavaScriptApplication({
input_path: root,
format: "directory",
});
if (!result.ok) throw result.error;
const analysis = javascriptApplicationAnalysisResultSchema.parse(
result.value.normalized_result,
);
const node =
target.kind === "binding"
? analysis.semantic_graph.nodes.find(
({ kind, label }) => kind === "binding" && label === target.label,
)
: analysis.semantic_graph.nodes.find(
({ kind, properties }) =>
kind === "property-slot" &&
properties.property_pointer === target.propertyPointer,
);
if (node === undefined)
throw new Error("Expected the selected semantic node");
const query = queryJavaScriptSemanticGraph(analysis.semantic_graph, {
seed: { kind: "semantic-node", node_id: node.node_id },
direction: "backward-provenance",
allowed_relations: ["defines"],
});
const transportedQuery = javaScriptSemanticQueryResultSchema.parse(
JSON.parse(JSON.stringify(query)),
);
expect(transportedQuery.nodes.some(({ kind }) => kind === "literal")).toBe(
false,
);
expect(transportedQuery.coverage.status).toBe("partial");
expect(transportedQuery.unknowns).toEqual(
expect.arrayContaining([
expect.objectContaining({
reason: "unknown-value",
node_id: node.node_id,
detail: expect.stringContaining(
"Negative zero cannot be preserved by JSON primitive evidence.",
),
}),
]),
);
},
);