/** * Component-level BOM aggregation (#614, epic #551 follow-up to #613). * * A component may produce more than one leaf BOM: a software SBOM * (./sbom.ts's `generate-sbom`, over an image/jar/zip/dir) for its built * artifact, and/or an IaC config-BOM (./config-bom.ts's `extract-config-bom`) * for its synthesized template. Each leaf BOM already validates as its own * standalone SPDX/CycloneDX document (#613). This module composes every leaf * BOM a component's build archive carries into **one** component-level BOM — * a real assembly (CycloneDX `metadata.component` + nested `components` + * `compositions` + `dependencies`, or SPDX `documentDescribes`-equivalent * `CONTAINS`/`DEPENDS_ON` relationships), not a re-implementation of the * writer: `aggregateComponentBom` below reads each leaf BOM's own structural * inventory back out (it does not re-parse the leaf's serialized bytes) and * hands the result to ./bom-writer.ts's `writeBom`, which gained * `BomInput.subDocuments` support for exactly this purpose. * * **Why not just concatenate/parse the leaf documents' bytes.** The leaf BOM * bytes are the source of truth for each artifact independently, but this * module needs each leaf's package list as structured data to nest it * correctly in the assembly — reparsing SPDX/CycloneDX JSON back into * `BomPackage[]` would be a second, format-aware reader this codebase does * not otherwise need. Callers therefore pass each leaf's already-known * `BomPackage[]` (what `generate-sbom`/`extract-config-bom` built before * calling `writeBom` themselves) plus the archive entry it produced — see * `ComponentBomLeaf` below. A caller assembling a component's archive * end-to-end has both on hand already; nothing here re-derives them from * scratch. * * **Single-artifact = 1:1, multi-artifact = a real assembly.** A component * with exactly one leaf BOM produces a component BOM whose only "assembly" * is itself — see `aggregateComponentBom`'s single-leaf fast path, which * still runs through the same writer and produces a valid document, just * with an empty `subDocuments` composition (nothing to nest). A component * with two or more leaves gets `subDocuments` populated, the real multi-leaf * case #614 asks for. * * **Release-level rollup is out of scope here** (deferred per #614's note) — * this module aggregates *within* one component's archive, never across * components/releases. */ import type { BuildArchiveEntry, BuildArchiveManifest } from "./build-archive.js"; import { type BomPackage } from "./bom-writer.js"; import { type SbomDocument, type SbomFormat } from "./sbom-generator.js"; /** * One leaf BOM a component produced, ready to fold into the component-level * aggregate. `entry` is the `sbom`-kind `BuildArchiveEntry` `generate-sbom`/ * `extract-config-bom` already wrote into the manifest (carries `bomKind`, * `subjectDigest`, `packageCount`, `generator`); `packages` is the same * `BomPackage[]` that capability built before calling `writeBom` for its own * standalone document. */ export interface ComponentBomLeaf { /** The `sbom`-kind manifest entry this leaf corresponds to (see `findSbomForSubject`/`findConfigBomForSubject`, ./build-archive.ts). */ entry: BuildArchiveEntry; /** The leaf's own enumerated packages/components/resources. */ packages: BomPackage[]; } export interface AggregateComponentBomInput { /** Component name — becomes the aggregate BOM's subject name. */ component: string; /** The build archive manifest this component's leaves live in — used to derive a stable subject id from `manifestDigest` and to validate that every leaf's `entry` actually belongs to this manifest. */ manifest: BuildArchiveManifest; /** Every leaf BOM to compose. Order is preserved in the resulting `subDocuments`/nested components. Must be non-empty — a component with no BOM to aggregate has nothing for this function to do (see module doc's "skips cleanly" convention elsewhere in this directory). */ leaves: ComponentBomLeaf[]; /** BOM format for the aggregate document. Defaults to `DEFAULT_SBOM_FORMAT` (SPDX), same precedence convention as every other BOM-producing capability in this directory. */ format?: SbomFormat; /** Clock override for deterministic tests. */ now?: () => Date; } export interface AggregateComponentBomOutput { /** The composed component-level BOM document. */ bom: SbomDocument; /** Total package/component count across every leaf (the sum `chant components status` surfaces, see ../../lifecycle/build-ledger.ts). */ totalPackageCount: number; /** How many leaf BOMs were composed — 1 for a single-artifact component (still a valid document, just with nothing to nest), 2+ for a real multi-artifact assembly. */ leafCount: number; } /** * Compose a component's leaf BOMs (a software SBOM and/or an IaC config-BOM, * per `input.leaves`) into one component-level BOM via ./bom-writer.ts's * assembly support (`BomInput.subDocuments`). Pure — no I/O, no manifest * mutation; the caller decides whether/where to write the result to disk * (mirroring `extract-config-bom`'s `outDir` convention) and whether to fold * it back into the archive as its own entry. * * Throws if `input.leaves` is empty — aggregation over zero leaves is a * caller bug (compose only when at least one BOM exists), not a case this * function silently no-ops on. */ export declare function aggregateComponentBom(input: AggregateComponentBomInput): AggregateComponentBomOutput; /** * Convenience for the common case: given a manifest that already carries * `sbom`-kind entries (written by `generate-sbom`/`extract-config-bom`) and * the `BomPackage[]` each one was built from (keyed by archive path, since * that's the one stable identifier a caller has for "which leaf is this"), * assemble the `ComponentBomLeaf[]` `aggregateComponentBom` expects. Kept * separate from `aggregateComponentBom` itself so a caller that already has * `ComponentBomLeaf[]` in hand (e.g. straight from the capabilities that just * ran) can skip this lookup entirely. */ export declare function componentBomLeavesFromManifest(manifest: BuildArchiveManifest, packagesByPath: Map): ComponentBomLeaf[]; //# sourceMappingURL=component-bom.d.ts.map