feat: corresponding-source artifacts for native packages that redistribute copyleft code (ADR 0055)
CI / verify (pull_request) Successful in 1m3s
CI / infrastructure-smoke (pull_request) Successful in 36s
CI / migration-smoke (pull_request) Successful in 1m53s
CI / package-release (pull_request) Successful in 36s
CI / package-publication (pull_request) Canceled after 0s
CI / deploy-smoke (same-origin) (pull_request) Canceled after 0s
CI / deploy-smoke (split) (pull_request) Canceled after 0s
CI / image-build (pull_request) Canceled after 30s

Ekklesia may publicly distribute a native Module Package containing LGPL or MPL components only beside
a version-specific corresponding-source artifact. The artifact carries the exact source, patches,
build definitions, and rebuild and relink instructions for those components. ADR 0055 (Accepted)
records the policy: project compliance policy, not legal certification. It decides the item ADR 0052
deferred, and extends ADR 0049's distributable-release gate. Module Package v1, packageFormat 1, and
both libvips supplements are unchanged.

The mechanism is generic and lives in @ekklesia/module-pack:
- Source recipes: reviewed data under corresponding-sources/<package>/<version>/. recipe.json names
  the exact package (name, version, content digest) and the exact supplement it was prepared with,
  and lists every component with its role, licence, licence material, and relationship to the
  binary. Every source file and patch is recorded by origin, size, and SHA-256; documents are hashed
  too, and every exclusion has a reason. Loading is strict and refuses anything unlisted.
- Acquisition (the only network step) fetches each recorded file once into a cache keyed by
  SHA-256. It uses node:https with exact headers and no decoding, retries transient failures, and
  stores only bytes that match.
- Construction is offline. From a built package it finds the covered native dependencies (by
  declared licence, or by a supplement component's licence), verifies every byte, and writes four
  sibling artifacts: the deterministic <package>-corresponding-source.tar.gz and its .sha256;
  <package>.redistribution.json, linking package, dependencies, supplement, recipe, and source by
  digest; and <package>.source-notice.txt, the release notice.
- The public-distribution gate (verifyRedistribution, `ekklesia-corresponding-source verify`)
  approves only when the recipe, supplement, canonical archive, manifest, and metadata identify
  exactly these files. It refuses bundled copyleft code, and approves packages with no covered
  native code without an artifact. packModule is untouched.

First complete proof: @img/[email protected] (sharp 0.35.5), from the reviewed supplement
and provenance.
- 383 components and 387 source files, about 208 MB: the sharp-libvips v1.3.4 build definitions,
  the 27 library archives, the five build-time patches, the 353 crates librsvg's resolved Cargo.lock
  records, and rust-src for nightly-2026-09-27 (rustc 75a75c3e0), which vendors std's dependencies.
- BUILDING.md and two helpers point sharp-libvips's unchanged build logic at those files.
- Followed in a Rocky Linux 8 container with networking disabled, they rebuilt
  libvips-cpp.so.8.18.7, byte-identically across two builds. Swapped into a verified, extracted
  native package, the rebuilt library was the one loaded, and sharp ran.
- The native proof's source artifact, native-proof-0.1.0-linux-x64-glibc-corresponding-source.tar.gz,
  is 207242785 bytes, sha256 cc405492032b04a5391ab84e64f0e5e50f78e571ef7818204782c25a03eb50b7. It is
  byte-identical from unrelated directories and from a cache acquired fresh from upstream.

Tests:
- corresponding-source.test.ts runs in CI: synthetic inputs, every negative case, acquisition with
  retries and refusal, and determinism.
- corresponding-source-native.test.ts checks the real recipe everywhere. With a verified cache
  (`pnpm test:corresponding-source`, which acquires once) it builds the real artifact twice, gates
  it, fails it closed, and runs sharp from the package.

Docs: the module guide's distribution section, the README and architecture notes, and an ADR 0052
cross-reference.

No Signage change, version bump, publication, or tag.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ekklesia
2026-10-08 23:17:35 -05:00
co-authored by Claude Opus 5.5
parent 4919db6168
commit e083c44e05
22 changed files with 14580 additions and 6 deletions
+101 -3
View File
@@ -296,9 +296,10 @@ The archive holds one directory named with your module id:
- **What it does not do.** A supplement never changes the licence the package declares. The
supplements cover `@img/sharp-libvips-linux-x64` 1.3.3 and 1.3.4, the libvips binaries that
`sharp` 0.35.4 and 0.35.5 load; each matches only its own version and digest, and the packager's
native proof builds with sharp 0.35.5. They record upstream sources, but whether the LGPL and MPL
source obligations for those binaries are met is an open determination. A package that carries
one is not yet treated as publicly distributable.
native proof builds with sharp 0.35.5. A supplement identifies and verifies licence and notice
material only. Source availability for copyleft native code is a separate requirement, met by a
corresponding-source artifact
([Distributing a native package publicly](#distributing-a-native-package-publicly)).
- **Deterministic.** The same packager version and the same built inputs produce the same archive,
byte for byte, wherever the checkout is.
@@ -324,6 +325,103 @@ installed package normally has no source, so the platform cannot check it the sa
module's own CI is where its boundary rules, its production dependencies, and its behaviour inside
Ekklesia are proven ([ADR 0047](../architecture/decisions/0047-module-ci-harness.md)).
#### Distributing a native package publicly
Building and testing a native package needs nothing beyond the packager. **Distributing it
publicly** does, when it redistributes native code under the GNU LGPL, the MPL, or a similar licence
with source obligations
([ADR 0055](../architecture/decisions/0055-corresponding-source-artifacts.md)).
- **Covered dependencies.** A native dependency is covered when its declared licence, or any
component its reviewed supplement records inside it, carries source obligations. libvips is one:
it declares `LGPL-3.0-or-later`, and cairo and several Rust crates inside it are under the MPL.
- **The policy.** A package with covered dependencies may be distributed publicly only together with
a corresponding-source artifact. The artifact holds the exact source, patches, build definitions,
and rebuild and relink instructions for those dependencies, as bytes rather than links. If the
artifact cannot be produced and verified, the package is not approved for public distribution.
This is Ekklesia's project compliance policy, not legal advice or certification. A package that
passes the gate meets Ekklesia's redistribution policy, and nothing more is claimed.
- **Source recipes.** For each covered dependency version, a reviewed recipe is checked in beside
the supplements, under `packages/module-pack/corresponding-sources/<package>/<version>/`. It holds
`recipe.json` and its documents, notably `BUILDING.md`. The recipe names the exact package (name,
version, content digest) and the exact supplement it was prepared with. It lists every component
with its role (`compiled`, `build-input`, or `build-definition`), licence, licence material, and
relationship to the binary. Every source file and patch is recorded by origin, size, and SHA-256,
and every exclusion has a reason. Like supplements, recipes are reviewed data, and the mechanism
knows no particular library.
Releasing a native package publicly takes three steps after packaging:
```bash
# 1. Acquisition: the only step that uses the network. It fetches each recorded file once into a
# cache keyed by SHA-256, refusing any response that differs from the recipe.
pnpm exec ekklesia-corresponding-source acquire --package ../packages/<id>-<version>-linux-x64-glibc.tar.gz \
--cache ~/.cache/ekklesia/corresponding-sources
# 2. Construction: offline. Every byte is verified against the recipe before anything is written.
pnpm exec ekklesia-corresponding-source build --package ../packages/<id>-<version>-linux-x64-glibc.tar.gz \
--cache ~/.cache/ekklesia/corresponding-sources --out ../packages
# 3. The public-distribution gate. Exit status 0 means approved.
pnpm exec ekklesia-corresponding-source verify --package ../packages/<id>-<version>-linux-x64-glibc.tar.gz \
--source ../packages/<id>-<version>-linux-x64-glibc-corresponding-source.tar.gz \
--metadata ../packages/<id>-<version>-linux-x64-glibc.redistribution.json
```
Construction writes four sibling release artifacts beside the package. The source archive never goes
inside the Module Package, and `THIRD_PARTY_NOTICES` is unchanged.
```text
<id>-<version>-linux-x64-glibc.tar.gz the Module Package (packager)
<id>-<version>-linux-x64-glibc.tar.gz.sha256
<id>-<version>-linux-x64-glibc-corresponding-source.tar.gz the corresponding source
<id>-<version>-linux-x64-glibc-corresponding-source.tar.gz.sha256
<id>-<version>-linux-x64-glibc.redistribution.json binary ↔ source, by digest
<id>-<version>-linux-x64-glibc.source-notice.txt the release notice
```
- **The source archive** is deterministic: sorted entries, fixed modes, ownership, and timestamps,
and no machine-specific path. The same package and recipe produce the same bytes in any directory.
- `corresponding-source.json` records every file with its SHA-256, and `SHA256SUMS` repeats them.
- Each covered dependency has its own `dependencies/<package>/<version>/` directory, with
`BUILDING.md`, the recipe, `PROVENANCE.md`, the supplement, and `sources/` and `patches/`.
- **The redistribution metadata** answers "which exact source corresponds to this exact binary?"
from digests. It records:
- the package file and its SHA-256, the module identity, and the target;
- each covered dependency's version and content digest, and the supplement and recipe it was
assembled from;
- the source archive's file name and SHA-256, and the SHA-256 of its manifest.
- **The release notice** must be shown alongside the binary wherever it is offered. It states that
the release includes third-party software under licences including the GNU LGPL and the MPL, and
that corresponding source and build and relink materials for the covered native dependencies are
in the accompanying artifact. It names that artifact by its exact file name and SHA-256.
- **Fail-closed.** The gate refuses a package when:
- a covered dependency has no recipe, or a recipe or supplement does not match its exact bytes;
- the source archive is missing, non-canonical, or differs from its manifest;
- the archive holds a file the manifest does not record, or a generated file names a local path;
- the metadata or the notice does not name these exact files.
It also refuses bundled (not copied) copyleft code, which this mechanism does not cover. A package
with no covered native code is approved without an artifact.
- **Retention.** Keep a release's source artifact for as long as its binary release is distributed
or supported. Never delete one automatically while the binary remains available. Never replace its
bytes under the same name. A correction is a new release, or an explicitly versioned compliance
artifact.
**Available today:** a recipe for `@img/sharp-libvips-linux-x64` 1.3.4, used by sharp 0.35.5. Its
artifact is about 200 MB: 27 library archives, 5 patches, 353 Rust crates, and the Rust standard
library source of the recorded toolchain. Following its `BUILDING.md` rebuilds
`libvips-cpp.so.8.18.7` from that source alone. libvips 1.3.3 (sharp 0.35.4) has a supplement but no
recipe, so a package with it builds and tests as before but is not approved for public distribution.
`pnpm test:corresponding-source` runs the whole proof for the native proof module: acquisition into a
verified cache, two byte-identical builds, the gate, and sharp running from the package.
**Adding a native dependency** follows the same path as libvips:
- curate and review its supplement (ADR 0052);
- curate and review its source recipe and `BUILDING.md`, recording every source input the
dependency's build fetches;
- prove both with the native packaging tests and `pnpm test:corresponding-source`.
### License
Ekklesia is licensed under the GNU Affero General Public License, version 3 or (at your option) any