ADR-0008: Govern published binaries with a derived Bin & License Contract
- Status: Accepted
- Date: 2026-07-27 (K1-K3), completed 2026-08-06 (K4 + placement)
- Affects:
cppjs-packages/README.md(the contract),cppjs-core/cpp.js/src/actions/{buildBinTools,licenses}.js,src/utils/{binTools,provenance,licenseReport,familyManifest}.js,scripts/{check-publish-hygiene,generate-third-party}.js
Context
Shipping upstream CLI tools as npm packages means distributing statically linked aggregates of a dozen third-party libraries. Hand-maintained tool lists, NOTICE files and license fields rot silently and are wrong per variant; executables leaking out of library packages is a licensing hazard; and an opaque binary invites the question "what exactly is in this and how was it built?".
Decision
Everything a binary-publishing package ships is derived from single
sources and machine-enforced — the Bin & License Contract
(cppjs-packages/README.md), four rules:
- K1 — library packages never publish executables (checker gate over
npm packoutput). - K2 —
-binpackages declare their tool surface as data (the recipebinmap); the engine derives npm commands,.npmignore,cppjs-bin.jsonand the multicall multitool. - K3 — NOTICE/SBOM are derived at build time from recipe metadata
(
cppjs licenses), keyed per variant, never hand-written. - K4 —
-binpackages carry a derivedcppjs.provenanceblock and a derived compoundlicensefield (the AND of every statically linked component's effective license).
Placement: the distribution's home is cpp.js — users install the -bin
package directly; no product-package wrapper. The contract is
placement-independent: if that ever changes, the rules travel with the
binaries.
Consequences
- Positive — tool surface, notices, SBOM, provenance and the license
field cannot drift from the build reality; violations fail
pnpm checkbefore they reach npm; honest license fields surface copyleft content (e.g. a geos binary declares its LGPL component) instead of hiding it behind a wrapper's MIT. - Negative — the checker runs
npm pack --dry-runacross the package tree (minutes); package.json fields are machine-owned (hand edits get overwritten by the next build); the derived license expressions are long.
Alternatives considered
- Hand-maintained NOTICE/license fields — rejected: per-variant accuracy is exactly what humans get wrong; a correct NOTICE on the wrong variant is worse than none.
- Post-hoc SBOM scanners (syft-class) — rejected as the source of truth: they guess from the finished artifact; cpp.js knows the link-time ground truth.
- "Build but don't publish" for library executables — rejected: don't build at all; no licensed artifact should ever exist in dist.
See also
- Related code:
cppjs-packages/README.md,docs/playbooks/licensing-lgpl.md - Related ADRs: ADR-0005 (wasi platform)