From c717c8a3ec1067ad86b53a3d2dedafc647f3072d Mon Sep 17 00:00:00 2001 From: Chris Tigga Date: Fri, 23 Oct 2026 02:43:51 +0530 Subject: [PATCH] fix: preserve unknown JSON omission counts (#5905) * fix: preserve unknown JSON omission counts * fix: preserve unknown counts for unselected JSON * fix: preserve unknown counts for uppercase JSON --- .../JavaScriptArtifactGraphStructure.ts | 7 +- ...riptArtifactGraphObservationLookup.test.ts | 128 ++++++++++++++++++ 2 files changed, 134 insertions(+), 1 deletion(-) diff --git a/src/application/javascript/JavaScriptArtifactGraphStructure.ts b/src/application/javascript/JavaScriptArtifactGraphStructure.ts index 559f9a7c73..c74ec0618e 100644 --- a/src/application/javascript/JavaScriptArtifactGraphStructure.ts +++ b/src/application/javascript/JavaScriptArtifactGraphStructure.ts @@ -612,7 +612,12 @@ const createFileTarget = ( javascript?.parse_status ?? (skipped ? "skipped" : null), json_parse_status: json?.status ?? null, json_top_level_keys: json?.top_level_keys ?? [], - omitted_json_top_level_keys: json?.omitted_top_level_keys ?? 0, + omitted_json_top_level_keys: + json === undefined + ? file.kind === "json" || file.kind === "package-json" + ? null + : 0 + : json.omitted_top_level_keys, vendor_markers: javascript?.vendors ?? [], }, evidence: artifactObservationEvidence({ diff --git a/tests/boundary/filesystem/javascriptArtifactGraphObservationLookup.test.ts b/tests/boundary/filesystem/javascriptArtifactGraphObservationLookup.test.ts index 4ec345dab7..4239a643fa 100644 --- a/tests/boundary/filesystem/javascriptArtifactGraphObservationLookup.test.ts +++ b/tests/boundary/filesystem/javascriptArtifactGraphObservationLookup.test.ts @@ -124,3 +124,131 @@ it("uses the first structured observation when paths repeat", async () => { await reader.close(); } }); + +it("preserves unknown omitted-key count for unavailable JSON", async () => { + const root = await createTestTempDirectory("rea-unavailable-json-"); + await Promise.all([ + writeFile(join(root, "package.json"), JSON.stringify({ name: "fixture" })), + writeFile(join(root, "config.json"), Buffer.from([0xff])), + ]); + + const snapshot = await scanCanonicalArtifactInventory(root, {}); + const reader = createJavaScriptArtifactReader(root, "directory"); + + try { + const files = await readJavaScriptArtifactFiles(reader, snapshot); + const analysis = analyzeJavaScriptArtifactFiles(files); + const graph = buildJavaScriptArtifactGraph(snapshot, files, analysis); + + const configFile = graph.nodes.find(({ observations }) => + observations.some( + ({ properties }) => + properties.path === "config.json" && properties.file_kind === "json", + ), + ); + + const configObservation = configFile?.observations.find( + ({ properties }) => + properties.path === "config.json" && properties.file_kind === "json", + ); + + expect(configObservation?.properties).toMatchObject({ + path: "config.json", + text_status: "invalid-utf8", + json_parse_status: "unavailable", + omitted_json_top_level_keys: null, + }); + } finally { + await reader.close(); + } +}); + +it("preserves unknown omitted-key count for unselected JSON", async () => { + const root = await createTestTempDirectory("rea-unselected-json-"); + + await Promise.all([ + writeFile(join(root, "package.json"), JSON.stringify({ name: "fixture" })), + writeFile(join(root, "config.json"), JSON.stringify({ setting: true })), + ]); + + const snapshot = await scanCanonicalArtifactInventory(root, {}); + const reader = createJavaScriptArtifactReader(root, "directory"); + + try { + const files = await readJavaScriptArtifactFiles( + reader, + snapshot, + undefined, + ["package.json"], + ); + + const analysis = analyzeJavaScriptArtifactFiles(files); + const graph = buildJavaScriptArtifactGraph(snapshot, files, analysis); + + const configFile = graph.nodes.find(({ observations }) => + observations.some( + ({ properties }) => + properties.path === "config.json" && properties.file_kind === "json", + ), + ); + + const configObservation = configFile?.observations.find( + ({ properties }) => + properties.path === "config.json" && properties.file_kind === "json", + ); + + expect(configObservation?.properties).toMatchObject({ + path: "config.json", + text_status: "outside-analysis-scope", + json_parse_status: null, + omitted_json_top_level_keys: null, + }); + } finally { + await reader.close(); + } +}); + +it("preserves unknown omitted-key count for unselected uppercase JSON", async () => { + const root = await createTestTempDirectory("rea-unselected-uppercase-json-"); + + await Promise.all([ + writeFile(join(root, "package.json"), JSON.stringify({ name: "fixture" })), + writeFile(join(root, "config.JSON"), JSON.stringify({ setting: true })), + ]); + + const snapshot = await scanCanonicalArtifactInventory(root, {}); + const reader = createJavaScriptArtifactReader(root, "directory"); + + try { + const files = await readJavaScriptArtifactFiles( + reader, + snapshot, + undefined, + ["package.json"], + ); + + const analysis = analyzeJavaScriptArtifactFiles(files); + const graph = buildJavaScriptArtifactGraph(snapshot, files, analysis); + + const configFile = graph.nodes.find(({ observations }) => + observations.some( + ({ properties }) => + properties.path === "config.JSON" && properties.file_kind === "json", + ), + ); + + const configObservation = configFile?.observations.find( + ({ properties }) => + properties.path === "config.JSON" && properties.file_kind === "json", + ); + + expect(configObservation?.properties).toMatchObject({ + path: "config.JSON", + text_status: "outside-analysis-scope", + json_parse_status: null, + omitted_json_top_level_keys: null, + }); + } finally { + await reader.close(); + } +});