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]>
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
LICENSEpayload.packageFormatstays 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
fileslist inekklesia.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
LICENSEfrom the strict payload.
The package verifier must refuse an artifact when:
ekklesia.package.json.licenseis missing, malformed, or outside the expression syntax the contract supports;- the
LICENSEpayload is missing; LICENSEis absent from the exactfileslist; 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 inLICENSE, and dependency obligations inTHIRD_PARTY_NOTICESwithout 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.jsonauthoritative. 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.
incorporatesrecords 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.