From 42c1b30d1f10c789138a5f19e2ae69e7eddb7afa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Padilla=20Vega=20=28m=CC=B5=CD=8A=CD=9Cx=CC=B5?= =?UTF-8?q?=CC=8D=CD=9B=CD=86=CC=8C=CD=83=CC=9B=CC=94=CC=9B=CD=92=CD=9D?= =?UTF-8?q?=CD=82=CD=8B=CC=84=CC=9A=CD=96W=CC=B6=CD=91=CC=AF=CD=85=CD=9C?= =?UTF-8?q?=CC=A4=CC=A7=CC=A0=CC=A3=CC=A8=CC=A8=CD=93=CD=96=CC=B1=CD=99?= =?UTF-8?q?=CD=99o=CC=B7=CC=89=CC=9A=CC=8C=CC=8E=CC=86=CC=BF=CC=94=CD=91?= =?UTF-8?q?=CD=8C=CC=94=CC=92=CC=94=CD=9B=CD=80=CD=86=CC=B1=CC=AC=CD=9A?= =?UTF-8?q?=CC=9E=CC=ACl=CC=B5=CC=94=CC=85=CD=86=CD=94=CC=BA=CD=8E=CC=A2?= =?UTF-8?q?=CC=AD=CC=A8=CC=9Df=CC=B5=CC=86=CC=80=CC=BF=CD=8B=CC=86=CC=85?= =?UTF-8?q?=CD=8A=CD=9D=CC=87=CC=BE=CD=8A=CD=81=CD=89=CD=94=CD=8D=CC=AC?= =?UTF-8?q?=CD=95=CD=89=CD=99=CC=B9=CD=88=CC=98=CD=88=29?= Date: Thu, 24 Sep 2026 16:46:39 -0700 Subject: [PATCH] docs: define the portable sbom contract, without a generator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An SBOM is the clearest case of a check whose contract is portable while its implementation cannot be, so this template defines the contract and ships no generator: a `sbom` target in the `ci` chain, output under the gitignored artifacts directory, published by the CI stubs beside the test and coverage reports. Nothing portable can produce the document itself. An SBOM's substance is the *resolved* dependency graph, which only exists inside an ecosystem's resolver — NuGet's project.assets.json, a package-lock.json, a populated virtualenv. A repo-level tool either shells out to those resolvers or parses manifests and guesses, and guessing gets transitive and conditionally-referenced packages wrong. Standardising one generator here would also force its toolchain onto every project made from this template, which is what the Requirements list exists to avoid. This repository gets no `sbom` target of its own on purpose: it ships no artifact. Its only dependencies are pre-commit and the rev-pinned hook repos, so an SBOM of it would inventory a developer toolchain nobody deploys. Also records two decisions that are easy to get wrong: generate from the build output rather than the source tree, and treat a per-pull-request SBOM (a drift detector) as a different artifact from a release SBOM (which wants attaching and attesting). --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/README.md b/README.md index d4e25eb..659c4da 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,48 @@ test: ## Run the test suite Because the logic is in the Makefile, those steps run identically locally and on every CI platform — no YAML changes required. +#### The `sbom` convention + +An SBOM is the clearest example of a check whose *contract* is portable while its +*implementation* cannot be. This template therefore defines the contract and +deliberately ships no generator: + +| Portable — keep this shape in every project | Project-specific | +| --- | --- | +| A `sbom` target, in the `ci` chain, after whatever builds | The generator and its toolchain | +| Output under the same gitignored artifacts directory the other reports use | The dependency-manifest path it reads | +| Published by the CI stubs alongside the test and coverage reports | The SBOM's subject name and version | + +Nothing portable can generate the document itself, because an SBOM's substance is +the *resolved* dependency graph, and that graph only exists inside an ecosystem's +resolver — NuGet's `project.assets.json`, a `package-lock.json`, a populated +virtualenv. A repo-level tool either shells out to those resolvers anyway or +parses manifests and guesses, and guessing gets transitive and +conditionally-referenced packages wrong. Standardising a generator here would also +mean forcing its toolchain onto every project made from this template, which is +exactly what the [Requirements](#requirements) list above exists to avoid. + +This repository has no `sbom` target of its own on purpose: it ships no artifact. +Its only dependencies are `pre-commit` and the rev-pinned hook repos — a developer +toolchain nobody deploys — so an SBOM of it would inventory something that is +never shipped. + +[`dotnet-template`](https://github.com/mxwlf/dotnet-template) implements this +contract for .NET, with Microsoft's `sbom-tool` producing SPDX 2.2 from +`project.assets.json`, if you want a worked example. + +Two decisions worth making deliberately when you do implement it: + +- **Generate from the build output, not the source tree.** An SBOM should describe + what you ship, which means it comes after the build, the way a coverage report + comes after the tests. +- **A per-pull-request SBOM and a release SBOM are different artifacts.** The + first is a drift detector — it catches a dependency added without its lockfile + updated. The authoritative one describes a released artifact and should be + attached to the release and attested (for example with `actions/attest-sbom`). + Putting `sbom` in `ci` gets you the first; the second belongs to a release + workflow. + ### What you still configure per platform (and why) The thin-wrapper pattern minimizes platform-specific config but cannot