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]>
A separately versioned module is now distributed as one deterministic
.tar.gz and its SHA-256, produced by an Ekklesia-owned packager and checked by
Core before anything is installed. This slice implements the portable `any`
target, with the module template as the end-to-end proof. ADR 0049 is
accepted.
@ekklesia/contracts: the Module Package v1 contract. The strict
ekklesia.package.json schema (packageFormat 1, identity, target, runtime,
provenance, incorporated Ekklesia packages, strict transitional facts, an
exact byte-wise-sorted file list with size and SHA-256), target names, and the
generated runtime package.json.
@ekklesia/module-pack (new, tooling): `ekklesia-pack-module --module <dir>
--target any --out <dir> --source-revision <commit>`.
- Bundles server, schema, and jobs with esbuild, SDK, contracts, and Drizzle
included; only Node built-ins remain imports. Any warning, unresolved
import, external dependency, or native or platform-specific package fails.
- Copies browser artifacts as built, after proving them current, and the
migrations; generates package.json and ekklesia.package.json; never ships
src, tests, declarations, source maps, or workspace ranges.
- THIRD_PARTY_NOTICES: every third-party package traced from the bundles'
own inputs, credited once with its declared licence and every licence,
copying, copyright, and notice file it ships. A missing licence fails; a
missing text fails unless its standard text is complete without copyright
lines (Apache-2.0, vendored, needed by drizzle-orm).
- Deterministic ustar and gzip: sorted paths, uid/gid 0, empty names, mtime
0, 0755/0644, no links. Byte-identical across runs and machines.
- Writes only the archive and its .sha256; never deploys, links, or installs,
so the workspace's install state is untouched.
Core: verifyModulePackage and `verify-module-package.js <archive>
[--sha256 | --sha256-file] [--extract-to <new dir>]`. Reads the archive in
memory and refuses links, special entries, setuid/setgid/sticky modes, unsafe
or escaping paths, and a second top-level directory; checks metadata, every
file, identity, target, Node.js range, SDK, and the transitional Drizzle
agreement; runs the platform's module and migration discovery; loads the
backend once in a forked process with an empty environment; only then
extracts, with normalized modes, into a new directory. Errors are sanitized.
@ekklesia/module-ui-build: analyzeModuleUi reports each artifact's inputs
and whether the built file is current. The nested stylesheet build now works
from the module directory, so artifacts no longer depend on the caller's
working directory.
The split deploy smoke builds the package with the canonical packager
(scripts/lib/build-module-package.Dockerfile replaces the test-only pnpm
deploy packaging), verifies it with the same generic image, installs the
verified tree, removes it, and reinstalls the exact same archive.
Co-Authored-By: Claude Opus 5.5 <[email protected]>