Files
Ekklesia/docs/architecture/decisions/0051-module-package-license-material.md
T
ekklesiaandClaude Opus 5.5 85fae9e07e
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
feat: Module Package v1 carries the module's own license (ADR 0051)
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]>
2026-10-07 18:51:03 -05:00

208 lines
12 KiB
Markdown

# 0051: Module packages declare their own license and carry its text
- Status: Accepted
- Date: 2026-10-07
- Amends: [0049](0049-module-package-v1.md) — every Module Package v1 artifact gains an
authoritative package-level license declaration and a `LICENSE` payload. `packageFormat` stays 1.
- Related: [the repository license](../../../README.md#license),
[ADR 0048](0048-generic-image-and-installed-module-storage.md),
[ADR 0050](0050-module-database-abi-v1.md)
## Context
ADR 0049 makes a module release one immutable, self-contained package and already requires
`THIRD_PARTY_NOTICES` for code bundled from third-party dependencies. It deliberately says that no
package is a distributable release until license and notice collection is proven. The implemented
packager now generates those notices from the dependency code it actually bundles, but Module
Package v1 still does not declare the module's own license or carry the module's license text.
Those are different obligations. `THIRD_PARTY_NOTICES` excludes both the module itself and bundled
Ekklesia-owned packages. The source module's development `package.json` is not copied into the
package: the packager generates a minimal runtime `package.json` instead. Consequently, an archive
can preserve every third-party notice and still lose the authoritative declaration and text for the
work being distributed.
The repository and its workspace packages declare `AGPL-3.0-or-later`, and the repository carries
the GNU AGPL version 3 text in `LICENSE`. The canonical module template also declares
`AGPL-3.0-or-later` in its source `package.json`. Official Ekklesia modules use that license today,
but a packager cannot assume that every module has the platform's license. A module release must say
what license its own publisher actually declared and must carry the corresponding material.
The current `ekklesia.package.json` contract is strict: a reader refuses unknown fields. Adding a
required field therefore makes pre-change and post-change readers reject one another's artifacts.
That is intentional here, not a reason for `packageFormat: 2`. ADR 0049 has not admitted a
distributable v1 release without proven license handling; this decision completes that still-open v1
requirement before real modules are distributed. The packager and verifier must adopt the tightening
together, and strict readers fail closed during that cutover rather than silently misreading a
package.
## Decision
### The package declares the module's own license once
Every Module Package v1 `ekklesia.package.json` has a required top-level `license` field containing
the module's SPDX license expression:
```json
{
"packageFormat": 1,
"module": { "id": "example", "version": "0.1.0" },
"license": "AGPL-3.0-or-later"
}
```
`ekklesia.package.json.license` is the authoritative package-release declaration. The expression is
not repeated in `ekklesia.module.json` or in the generated runtime `package.json`. The module's
source `package.json` is a packaging input, not release metadata inside the finished artifact.
The source module must explicitly declare its license in its own project metadata. The canonical
packager reads the normal string `license` field from the source module's `package.json`, validates
it as a supported SPDX license expression, and preserves that declaration in
`ekklesia.package.json`. It refuses a missing, empty, `UNLICENSED`, legacy object-shaped, malformed,
or unsupported declaration. It never substitutes the Ekklesia platform's license and does not try
to decide whether a publisher chose an appropriate license.
For official Ekklesia modules today the declaration is `AGPL-3.0-or-later`, matching the repository
license. This is a fact about those modules, not a default the packager invents for other modules.
### The package carries the applicable license material
Every distributable Module Package v1 contains a regular file at this canonical path inside the
module directory:
```text
LICENSE
```
`LICENSE` contains the license material applicable to the module release. It is an ordinary payload
file under ADR 0049:
- it appears exactly once in the strict `files` list in `ekklesia.package.json`;
- its byte size and SHA-256 are recorded there;
- the archive SHA-256 covers it and the metadata that records it;
- it is immutable for that release and changes only by producing a different archive.
The license bytes must be available as an explicit source or build input. The canonical source is a
module-owned `LICENSE` file at the module source root. Tooling may also accept an explicitly named
license-text input when a workspace layout needs one, but it must place those bytes at the package's
canonical `LICENSE` path. It must not search ancestor directories and silently adopt a repository
license, generate project-specific license material, or download text from the network. The
in-repository template implementation may later add its own `LICENSE` input or explicitly select
the repository's AGPL text; this ADR does neither.
The packager validates that both inputs exist: the declared SPDX expression and the applicable
license text. It preserves the supplied text as deterministic package input. It is not a license
compatibility engine and does not offer legal advice.
### `THIRD_PARTY_NOTICES` remains a separate concern
`THIRD_PARTY_NOTICES` continues to describe third-party packages actually bundled into or included
with the module: their identities, versions, declared licenses, uses, and required license and notice
texts. It is not the module's own license file and cannot satisfy the `LICENSE` requirement.
The module itself is not added to `THIRD_PARTY_NOTICES`. A first-party module is not duplicated there
merely because it uses the same AGPL license as Ekklesia. `LICENSE` and `THIRD_PARTY_NOTICES` both
remain required because they describe different parts of the release.
### Bundled Ekklesia code
A module package may bundle Ekklesia-owned SDK, contracts, database adapter, UI, and related code.
`incorporates` continues to record the exact versions of those Ekklesia components. It is an
inventory and compatibility fact, not a second authoritative declaration of the module's license.
Today's official module packages and the Ekklesia components they incorporate are all
`AGPL-3.0-or-later`. Their package `LICENSE` therefore carries the applicable AGPL text once. The
package does not need one identical copy per incorporated Ekklesia component, and those components
do not move into `THIRD_PARTY_NOTICES` merely to obtain duplicate text.
This ADR does not decide every future combination of a community module's license with differently
licensed incorporated code. Publishers remain responsible for their choices and obligations. The
packager preserves declared information and required material; it does not judge broad license
compatibility.
### Packaging and verification fail closed
The implementation that follows this ADR must make the packager refuse a module when:
- its source project has no acceptable explicit SPDX license expression;
- its license-text input is missing or is not a regular file;
- producing the package would omit `LICENSE` from the strict payload.
The package verifier must refuse an artifact when:
- `ekklesia.package.json.license` is missing, malformed, or outside the expression syntax the
contract supports;
- the `LICENSE` payload is missing;
- `LICENSE` is absent from the exact `files` list; or
- its listed size or SHA-256 does not match its bytes, as with every other payload file.
This ADR does not implement those checks. The contracts, packager, verifier, fixtures, template,
and package proofs must change together in the implementation PR.
### Determinism and runtime metadata
`LICENSE` introduces no timestamp, generated host path, network lookup, or other environmental
input. The deterministic archive rules in ADR 0049 apply to it unchanged.
The generated runtime `package.json` remains minimal and is not authoritative for licensing. There
is no runtime need to add a `license` field there. `ekklesia.package.json` is the release metadata,
while the source module's `package.json` only supplies the publisher's declaration to the packager.
### Package format remains 1
`packageFormat` remains **1**. This is an additive tightening that completes ADR 0049's licensing
requirement before the first distributable Module Package v1 release. It does not change the archive
encoding, safety rules, target model, installation layout, or meaning of any existing field.
The strict metadata schema is preserved. A verifier that predates this decision refuses the new
unknown `license` field; an updated verifier refuses an old package that lacks it or lacks `LICENSE`.
Both outcomes are safe. There is no installed or published v1 release compatibility promise to
preserve because ADR 0049 explicitly withheld distributable-release status pending this work.
ADR 0050's database ABI decision is unaffected. Whether a package uses database ABI v1 or the legacy
database facade has no bearing on its license declaration or payload.
## Consequences
- A Module Package v1 release is self-describing about the module's own license and carries the
corresponding material without consulting its source repository or the network.
- Operators can audit the release declaration in `ekklesia.package.json`, the module's license in
`LICENSE`, and dependency obligations in `THIRD_PARTY_NOTICES` without conflating them.
- Module publishers must make both the SPDX declaration and license text explicit packaging inputs.
- The template and every package fixture must be updated before packaging succeeds, but only in the
later implementation PR.
- Existing development artifacts produced before this tightening are intentionally rejected and
rebuilt; none was a distributable v1 release under ADR 0049.
- Identical AGPL text is carried once for today's official module and incorporated Ekklesia code.
## Alternatives
- **Use only `THIRD_PARTY_NOTICES`.** Rejected: it describes third-party dependencies and explicitly
excludes the module itself and Ekklesia-owned incorporated packages.
- **Make the generated runtime `package.json` authoritative.** Rejected: it is deliberately minimal
runtime discovery metadata, and copying the declaration there would duplicate release metadata.
- **Copy the platform's license automatically.** Rejected: the platform cannot declare a module's
license for its publisher, and a community module may make a different declaration.
- **Search parent directories for `LICENSE`.** Rejected: the chosen file would depend on checkout
layout and could silently apply an unrelated repository's terms. An out-of-tree input must be
explicit.
- **Download standard license text while packaging.** Rejected: builds must remain offline,
deterministic, and independent of a mutable remote source.
- **Duplicate the AGPL text for every incorporated Ekklesia package.** Rejected: identical copies
add no information. `incorporates` records component identity and version; one applicable text is
sufficient for today's official packages.
- **Create `packageFormat: 2`.** Rejected: v1 has not crossed its own distributable-release boundary,
strict readers fail safely, and no archive or installation mechanism changes.
- **Make the packager decide license compatibility.** Rejected: preserving declarations and material
is mechanical; judging every present and future license combination is a broader legal and policy
problem.
## Deferred
- Implementing contract, packager, verifier, fixture, template, and package-proof changes.
- The exact set and version of SPDX expression syntax accepted by the implementation, beyond the
requirement that unsupported expressions fail closed.
- Broad policy for future mixed-license community modules and incorporated code.
- Container-image license distribution.
- Marketplace, installer, publisher verification, and package-signing policy.