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

12 KiB

0051: Module packages declare their own license and carry its text

  • Status: Accepted
  • Date: 2026-10-07
  • Amends: 0049 — every Module Package v1 artifact gains an authoritative package-level license declaration and a LICENSE payload. packageFormat stays 1.
  • Related: the repository license, ADR 0048, ADR 0050

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:

{
  "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:

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.