diff --git a/src/firmware/FirmwareReports.test.ts b/src/firmware/FirmwareReports.test.ts index 0560dc56be..c108cf22e6 100644 --- a/src/firmware/FirmwareReports.test.ts +++ b/src/firmware/FirmwareReports.test.ts @@ -706,46 +706,79 @@ describe("firmware chunk dependency failures", () => { }); describe("Unblob multi-file extraction diagnostics", () => { - it("names a missing extractor dependency and stage while retaining the raw report", () => { - const dependencyFailure = { - __typename__: "ExtractorDependencyNotFoundReport", - severity: "ERROR", - dependencies: ["sasquatch"], - }; - const record = root(); - const normalized = normalizeUnblobReport( - [ + it.each([ + { dependencies: ["sasquatch"], names: ["sasquatch", "nested-tool"] }, + { dependencies: ["sasquatch", ""], names: ["sasquatch", "nested-tool"] }, + { dependencies: [], names: ["nested-tool"] }, + ])( + "retains nested dependency diagnostics beneath $dependencies", + ({ dependencies, names }) => { + const dependencyFailure = { + __typename__: "ExtractorDependencyNotFoundReport", + severity: "ERROR", + dependencies, + nested_reports: [ + { + __typename__: "ExtractorDependencyNotFoundReport", + dependencies: ["nested-tool", "nested-tool"], + }, + ], + }; + const record = root(); + const normalized = normalizeUnblobReport( + [ + { + ...record, + reports: [ + ...record.reports.filter( + (item) => item.__typename__ !== "UnknownChunkReport", + ), + { + __typename__: "ChunkReport", + id: "gzip-id", + start_offset: 0, + end_offset: 20, + size: 20, + handler_name: "gzip", + is_encrypted: false, + extraction_reports: [dependencyFailure], + }, + ], + }, + ], + context, + ); + expect(normalized.coverage).toBe("partial"); + expect(normalized.diagnostics).toEqual([ { - ...record, - reports: [ - ...record.reports.filter( - (item) => item.__typename__ !== "UnknownChunkReport", - ), - { - __typename__: "ChunkReport", - id: "gzip-id", - start_offset: 0, - end_offset: 20, - size: 20, - handler_name: "gzip", - is_encrypted: false, - extraction_reports: [dependencyFailure], - }, - ], + input_path: "$input", + stage: "gzip", + missing_dependencies: names, + report: dependencyFailure, }, - ], - context, - ); - expect(normalized.coverage).toBe("partial"); - expect(normalized.diagnostics).toEqual([ - { - input_path: "$input", - stage: "gzip", - missing_dependencies: ["sasquatch"], - report: dependencyFailure, - }, - ]); - }); + ]); + expect(normalized.chunk_missing_dependencies).toEqual([ + ...(dependencies.includes("sasquatch") + ? [ + { + input_path: "$input", + provider_id: "gzip-id", + dependencies: ["sasquatch"], + }, + ] + : []), + ...(dependencies.length === 0 || dependencies.includes("") + ? [ + { + input_path: "$input", + provider_id: "gzip-id", + dependencies: [], + }, + ] + : []), + ]); + }, + ); }); describe("Unblob task derivation identities", () => { @@ -1257,7 +1290,7 @@ it.each(["task", "chunk", "multi-file"] as const)( { __typename__: "ExtractorDependencyNotFoundReport", severity: "ERROR", - dependencies: ["z-tool", "a-tool", "z-tool"], + dependencies: ["z-tool", "", "a-tool", "z-tool"], producer_detail: "named source declaration", }, { diff --git a/src/firmware/FirmwareReports.ts b/src/firmware/FirmwareReports.ts index 4120375865..fe1c68758c 100644 --- a/src/firmware/FirmwareReports.ts +++ b/src/firmware/FirmwareReports.ts @@ -144,11 +144,6 @@ const known = new Set([ "MultiFileReport", ]); -const dependencyFailure = z.object({ - __typename__: z.literal("ExtractorDependencyNotFoundReport"), - dependencies: z.array(z.string().min(1)).min(1), -}); - /** Collect the named extractors from any dependency report nested in one value. */ const diagnosticDependencyNames = (value: unknown): string[] => { // Producer-controlled name volume must not make deduplication quadratic; @@ -160,11 +155,16 @@ const diagnosticDependencyNames = (value: unknown): string[] => { return; } if (typeof node !== "object" || node === null) return; - const failure = dependencyFailure.safeParse(node); + const failure = missingDependency.safeParse(node); if (failure.success) { - for (const dependency of failure.data.dependencies) found.add(dependency); - return; + // Empty names carry unknown identity, not evidence against the known + // names in the same declaration. Ownership facts retain that unknown; + // diagnostics project only names and keep the complete source report. + for (const dependency of failure.data.dependencies) + if (dependency !== "") found.add(dependency); } + // A declaration may also carry nested producer diagnostics. Its own + // admission does not determine whether those independent reports exist. for (const child of Object.values(node)) visit(child); }; visit(value);