feat: Module Package v1 carries the module's own license (ADR 0051)
CI / verify (pull_request) Successful in 58s
CI / infrastructure-smoke (pull_request) Successful in 35s
CI / migration-smoke (pull_request) Successful in 1m53s
CI / image-build (pull_request) Successful in 30s
CI / deploy-smoke (same-origin) (pull_request) Successful in 50s
CI / deploy-smoke (split) (pull_request) Successful in 1m32s

Every Module Package v1 now declares the module's license and carries its
text. packageFormat stays 1; pre-ADR-0051 metadata fails closed.

- Contracts: ekklesia.package.json requires an SPDX `license` expression,
  validated with spdx-expression-parse, with UNLICENSED and non-expressions
  refused. The `files` list must hold exactly one non-empty root LICENSE.
  The package contracts move to the @ekklesia/contracts/module-package
  subpath, so the SPDX parser and its data (spdx-exceptions and
  spdx-license-ids ship no licence files) never reach a module bundle
  through the SDK's re-export of the contracts root.
- Packager: reads the source package.json's string SPDX declaration and
  never invents one. It requires an explicit --license-file (a non-empty
  regular file, no symlink, no parent search, no download) and copies its
  bytes unchanged to LICENSE, listed with size and SHA-256 and covered by
  the archive digest. THIRD_PARTY_NOTICES stays third-party only, and
  bundled Ekklesia code stays in `incorporates`.
- Verifier: rejects missing or invalid license metadata, and a missing,
  unlisted, duplicated, or corrupted LICENSE, through the schema and the
  existing strict inventory.
- Template: packaged as AGPL-3.0-or-later with the repository's canonical
  LICENSE, passed explicitly (Dockerfile, smoke, docs). Packing it twice
  gives byte-identical archives.

Started by Codex; reviewed and completed in Claude Code (empty license-text
refusal, schema non-empty LICENSE, symlink and empty-file tests).

ADR 0051 is Accepted.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ekklesia
2026-10-07 18:51:03 -05:00
co-authored by Claude Opus 5.5
parent 30f61094e6
commit 85fae9e07e
22 changed files with 400 additions and 40 deletions
+22 -10
View File
@@ -97,6 +97,7 @@ development dependency like `@ekklesia/module-ui-build`:
```bash
pnpm exec ekklesia-pack-module --module . --target any --out ../packages \
--license-file ./LICENSE \
--source-revision "$(git rev-parse HEAD)"
```
@@ -110,7 +111,8 @@ The archive holds one directory named with your module id:
<id>/
├── package.json generated: name, version, private, "type": "module", the layer marker
├── ekklesia.module.json your manifest, byte for byte
├── ekklesia.package.json package metadata: target, runtime, provenance, file digests
├── ekklesia.package.json package metadata: license, target, runtime, provenance, file digests
├── LICENSE your explicitly supplied module license material, byte for byte
├── THIRD_PARTY_NOTICES licence and notice text of every third-party package bundled
├── dist/server/index.js your backend entrypoints, bundled
├── dist/schema/index.js
@@ -130,11 +132,18 @@ The archive holds one directory named with your module id:
- **`any` is the only target today.** A dependency that is native or platform-specific (a compiled
addon, a package that declares an operating system or CPU, or one that loads a per-platform binary
such as `sharp`) cannot be in an `any` package, and packaging fails. Native targets come later.
- **Licences.** `THIRD_PARTY_NOTICES` credits every third-party package your bundles contain, with
its declared licence and the licence and notice files it ships. A package that declares no licence
fails packaging, and so does one that ships no licence text, unless its licence's standard text is
complete without a copyright line (Apache-2.0, which the packager then supplies and says so). The
Ekklesia platform's own packages are listed in `ekklesia.package.json` under `incorporates`.
- **Your module's licence.** Your source `package.json` must contain a non-empty string SPDX
expression. The packager validates its syntax, rejects `UNLICENSED`, and copies it unchanged to
the required `ekklesia.package.json.license`. `--license-file` is also required: it names the
regular file whose exact bytes become the root `LICENSE` payload and inventory entry. The
packager never searches parent directories, guesses text from the expression, or downloads it.
- **Third-party licences.** `THIRD_PARTY_NOTICES` separately credits every third-party package your
bundles contain, with its declared licence and the licence and notice files it ships. A dependency
that declares no licence fails packaging, and so does one that ships no licence text, unless its
licence's standard text is complete without a copyright line (Apache-2.0, which the packager then
supplies and says so). The module itself is not added to this notice file. Bundled Ekklesia
packages remain listed in `ekklesia.package.json` under `incorporates`; one applicable AGPL text
is sufficient for current official modules.
- **Deterministic.** The same packager version and the same built inputs produce the same archive,
byte for byte, wherever the checkout is.
@@ -167,10 +176,13 @@ later version, `AGPL-3.0-or-later` ([LICENSE](../../LICENSE)), and official Ekkl
same license. Declare it in your module's development `package.json`
(`"license": "AGPL-3.0-or-later"`), as the module template does.
A Module Package v1 does not yet record a module's own license: its runtime `package.json` and
`ekklesia.package.json` have fixed fields, and its contents are fixed, by ADR 0049. Carrying the
module's license, and the license text for the Ekklesia code bundled into its backend, needs a
change to that format.
A Module Package v1 records that validated expression in its required
`ekklesia.package.json.license` and contains the exact explicitly supplied bytes at `LICENSE`
([ADR 0051](../architecture/decisions/0051-module-package-license-material.md)). The generated
runtime `package.json` is not authoritative for licensing. `THIRD_PARTY_NOTICES` remains only for
redistributed third-party dependencies, while bundled Ekklesia components remain represented by
`incorporates`. This strict-reader addition keeps `packageFormat` at `1`; older pre-0051 artifacts
without the field and payload fail closed.
### Removing a module keeps its data