Skip to content

S0349-astm-10: publish a dependency inventory with each release - #89

Merged
NSchatz merged 2 commits into
mainfrom
sdd/S0349-astm-10
Sep 22, 2026
Merged

NSchatz merged 2 commits into
mainfrom
sdd/S0349-astm-10

Conversation

@NSchatz

@NSchatz NSchatz commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Spec: pipeline/active/S0349-astm-10/spec.md (umbrella)

Each published version now carries a machine-readable inventory of what that build was made from,
produced and graded by this repo, and retrievable from that version's release page without cloning
anything.

What is here

  • scripts/check-dependency-inventory.ts produces a CycloneDX 1.6 document into the gitignored
    dist-artifacts/dependency-inventory.cdx.json and then grades it. The resolved set is read from
    pnpm-lock.yaml rather than from the manifest's two lists, because the manifest states ranges
    while a build resolves versions. No subprocess, no network call, no registry credential.
  • Refusals, each by name and each non-zero: missing, empty, unparseable, identity,
    version, incomplete. A produce-side refusal also removes any stale artifact at that path, so
    an older inventory can never be attached as this build's.
  • prepublishOnly gains pnpm check:dependency-inventory. That is the side of the npm publish
    where a refusal keeps the version off the registry.
  • pnpm check gains the same command, so producing and grading it is reachable from a checkout.
  • A second job in .github/workflows/release.yml attaches the artifact to the release the shared
    pipeline cut. It is a job here because that pipeline attaches a fixed two-name asset list and
    declares no workflow_call outputs; it finds the release from the tag at the published commit,
    uploads with --clobber so a re-run attaches to the same release without republishing, reads the
    asset back, and fails the run rather than skipping quietly if it did not arrive.
  • test/dependency-inventory.test.ts, 26 cases, each naming the criterion it grades and each
    driving the real script as a subprocess in a throwaway tree.

What is deliberately not here

No runtime source changes, so no src/ directory joins the per-directory coverage gate and no
exported value, type, warning code or emitted byte moves. No new dependency of any kind. No
signing, attestation or provenance work: npm provenance remains what it already was. No gate on
what the dependency set contains: this grades that the set is stated truthfully, never that it is
approved.

Field discipline

The artifact ships on a public release page, so it carries the package identity, the component
names, their versions, a purl per component and the format declaration, and nothing else. No
timestamp, no serial number, no producing-tool record, no path, no integrity hash, no registry URL.
Generation is byte-for-byte deterministic for a given commit.

Gates

pnpm check, pnpm test (71 files, 1246 passed, 4 skipped), pnpm lint, pnpm typecheck,
pnpm format:check, pnpm check:no-emdash, pnpm check:no-internal-refs and pnpm phi-scan are
all green. A Changeset and a CHANGELOG.md [Unreleased] entry are included, and the pending-set
bookkeeping in documentation/release-readiness.md is updated so its gate stays green.

Three criteria (the asset on a real release page, a real re-run after a failed attach, and the
observed ordering on a real publish) can only be graded on a publish this change does not trigger.

Produce a CycloneDX inventory of what a build was made from, refuse it
unless it is true of that build, and attach it to the version's GitHub
release as a downloadable asset.

scripts/check-dependency-inventory.ts reads the package identity from
package.json and the resolved set from pnpm-lock.yaml, writes the
artifact into the gitignored dist-artifacts/, then grades it: missing,
empty, unparseable, identity-less, version-mismatched or short of a
resolved dependency are each refused by name with a non-zero status, and
a produce-side refusal removes any stale artifact rather than leaving one
to be read as this build's.

The guard runs in prepublishOnly, before the registry upload, so a bad
inventory costs a failed run instead of a published version nothing
describes. The attach is a second job in this repo's release workflow,
because the shared pipeline attaches a fixed two-name asset list and
declares no workflow_call outputs; it runs after the publish because the
release does not exist before one, finds the release from the tag at the
published commit, and fails its run rather than skipping quietly when the
asset does not arrive.

No runtime source changes, so no per-directory coverage gate moves and no
exported value, type or emitted byte is touched.
Comment thread scripts/check-dependency-inventory.ts Fixed
…tern

CodeQL js/redos on scripts/check-dependency-inventory.ts. The repeated group
opened with [-+] while its own character class also carried -, so a near miss
such as 0.0.0+ followed by repetitions of -- backtracked exponentially: 1.2ms
to reject at 27 characters, 9.0ms at 35, 99ms at 39, 704ms at 43, and a
59-character input did not finish in three minutes.

That is reachable rather than theoretical. The text comes from a lockfile key,
which is to say from a registry, and this script runs in the publishing path.

Each tail is now optional rather than repeated, and the prerelease class can no
longer swallow the +, so the split is decided by the first + and there is
nothing to backtrack over. Flat at 0.017ms on a 2007-character input, and it
accepts and rejects identically on every input that matters. No test added: the
subject is performance rather than a criterion, and CodeQL already holds it.
@NSchatz
NSchatz merged commit f7a0776 into main Sep 22, 2026
9 checks passed
@NSchatz
NSchatz deleted the sdd/S0349-astm-10 branch October 8, 2026 18:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants