One artifact. A module release is one immutable, deterministic .tar.gz (sorted paths, uid/gid 0, mtime 0, gzip header with no name or time), with its SHA-256 distributed beside it. Identity is the contents, never the transport; Gitea, GitHub, OCI, object storage, offline media, or a marketplace may carry it unchanged.
Layout. One top-level directory named with the module id, containing:
bundled backend entrypoints and the built browser artifacts;
migrations and THIRD_PARTY_NOTICES;
node_modules only for native dependencies.
No source, tests, declarations, source maps, development dependencies, or workspace: references.
Backend bundling. Every pure-JavaScript dependency is bundled, including the SDK and contracts. The canonical packager alone decides and validates what stays external (native or platform-specific code); it is not an author-facing choice.
Targets.any, or <platform>-<arch>[-<libc>] in Node's own vocabulary, with libc required on Linux. Signage is linux-x64-glibc because of sharp. One target per package; multi-architecture packages deferred.
ekklesia.package.json (packageFormat: 1). It records:
id, version and target;
runtime needs (Node range; Node-API or ABI, and libc version, for native packages);
provenance (revision, packager);
incorporated SDK and contracts versions;
a transitional.databaseRuntime Drizzle requirement;
the exact list of payload files, each with size and SHA-256, excluding the metadata file itself.
Platform and SDK ranges stay only in the manifest.
Drizzle. Explicitly a temporary implementation constraint, not the Module Database ABI; a separate ADR will make the platform's ORM an implementation detail.
Licences.THIRD_PARTY_NOTICES is mandatory. The packager PR must prove licence preservation before anything counts as a release.
Archive safety.No links of any kind in v1, no absolute or .. paths, no special files, no setuid/setgid; extraction normalizes ownership and modes; nothing is executable.
Installation lifecycle (intended, not implemented). Verify digest → hidden staging → validate metadata and per-file hashes → target and runtime check → whole candidate inventory → isolated entrypoint load → migration plan → stop API → swap → explicitly approved migrations → restart → readiness.
Rollback. Restoring the previous code is supported only before new migrations apply; after that, rollback means restoring the matching backup. No down migrations.
Trust. The digest gives integrity, not authenticity. Signing and trust roots are deferred, and modules remain trusted code.
Tooling. An Ekklesia-owned packager (proposed packages/module-pack, ekklesia-pack-module). pnpm deploy is explicitly not the mechanism.
Other files
docs/architecture/decisions/README.md: index entry.
0047-module-ci-harness.md (Proposed): one header cross-reference saying the harness should consume canonical packages once 0049 is accepted.
docs/module-development/README.md: it previously told authors to run pnpm deploy --legacy in their workspace. It now points to ADR 0049 and warns that pnpm deploy rewrites a workspace's install state. This was observed both in scratch workspaces and earlier in a real checkout.
No accepted ADR's text changes; ADR 0048's lifecycle decisions are untouched.
Status
Proposed, following the repository's usual lifecycle (0044, 0045, 0046, 0047): it becomes Accepted with the first implementation.
Verification
pnpm verify: passed (core 1015, api 510; boundaries, module and migration validation, identity schema, build).
pnpm deploy:config: valid.
All 132 relative links and in-page anchors in the changed files resolve.
Proposes **ADR 0049: Ekklesia Module Package v1**, extending ADR 0048. **Docs only**: no runtime code, packager, installer, Docker, CI, Signage, or instance changes.
## What it decides
- **One artifact.** A module release is one immutable, deterministic `.tar.gz` (sorted paths, uid/gid 0, mtime 0, gzip header with no name or time), with its SHA-256 distributed beside it. Identity is the contents, never the transport; Gitea, GitHub, OCI, object storage, offline media, or a marketplace may carry it unchanged.
- **Layout.** One top-level directory named with the module id, containing:
- a **generated** runtime `package.json` (name, version, private, `"type": "module"`, layer only);
- the unchanged manifest and `ekklesia.package.json`;
- bundled backend entrypoints and the built browser artifacts;
- migrations and `THIRD_PARTY_NOTICES`;
- `node_modules` only for native dependencies.
No source, tests, declarations, source maps, development dependencies, or `workspace:` references.
- **Backend bundling.** Every pure-JavaScript dependency is bundled, including the SDK and contracts. The canonical packager alone decides and validates what stays external (native or platform-specific code); it is not an author-facing choice.
- **Targets.** `any`, or `<platform>-<arch>[-<libc>]` in Node's own vocabulary, with libc required on Linux. Signage is `linux-x64-glibc` because of `sharp`. One target per package; multi-architecture packages deferred.
- **`ekklesia.package.json` (`packageFormat: 1`).** It records:
- id, version and target;
- runtime needs (Node range; Node-API or ABI, and libc version, for native packages);
- provenance (revision, packager);
- incorporated SDK and contracts versions;
- a `transitional.databaseRuntime` Drizzle requirement;
- the exact list of payload files, each with size and SHA-256, excluding the metadata file itself.
Platform and SDK ranges stay only in the manifest.
- **Drizzle.** Explicitly a temporary implementation constraint, **not** the Module Database ABI; a separate ADR will make the platform's ORM an implementation detail.
- **Licences.** `THIRD_PARTY_NOTICES` is mandatory. The packager PR must prove licence preservation before anything counts as a release.
- **Archive safety.** **No links of any kind in v1**, no absolute or `..` paths, no special files, no setuid/setgid; extraction normalizes ownership and modes; nothing is executable.
- **Installation lifecycle (intended, not implemented).** Verify digest → hidden staging → validate metadata and per-file hashes → target and runtime check → whole candidate inventory → isolated entrypoint load → migration plan → stop API → swap → explicitly approved migrations → restart → readiness.
- **Rollback.** Restoring the previous code is supported only before new migrations apply; after that, rollback means restoring the matching backup. No down migrations.
- **Trust.** The digest gives integrity, not authenticity. Signing and trust roots are deferred, and modules remain trusted code.
- **Tooling.** An Ekklesia-owned packager (proposed `packages/module-pack`, `ekklesia-pack-module`). **`pnpm deploy` is explicitly not the mechanism.**
## Other files
- `docs/architecture/decisions/README.md`: index entry.
- `0047-module-ci-harness.md` (Proposed): one header cross-reference saying the harness should consume canonical packages once 0049 is accepted.
- `docs/module-development/README.md`: it previously told authors to run `pnpm deploy --legacy` in their workspace. It now points to ADR 0049 and warns that `pnpm deploy` rewrites a workspace's install state. This was observed both in scratch workspaces and earlier in a real checkout.
No accepted ADR's text changes; ADR 0048's lifecycle decisions are untouched.
## Status
**Proposed**, following the repository's usual lifecycle (0044, 0045, 0046, 0047): it becomes Accepted with the first implementation.
## Verification
- `pnpm verify`: passed (core 1015, api 510; boundaries, module and migration validation, identity schema, build).
- `pnpm deploy:config`: valid.
- All 132 relative links and in-page anchors in the changed files resolve.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
A separately versioned module is distributed as one immutable, self-contained,
deterministic .tar.gz with a separately distributed SHA-256. The package
contract is independent of hosting, registry, transport, and marketplace.
ADR 0049 extends ADR 0048 with:
- the package layout: one top-level module directory with a generated runtime
package.json, the unchanged manifest, ekklesia.package.json, bundled backend
entrypoints, built browser artifacts, migrations, THIRD_PARTY_NOTICES, and
node_modules only for native dependencies;
- backend bundling of all pure-JavaScript dependencies, with externals decided
and validated by the canonical packager, never by module authors;
- targets: `any`, or <platform>-<arch>[-<libc>] (linux-x64-glibc for Signage);
- ekklesia.package.json (packageFormat 1): id, version, target, runtime needs,
provenance, incorporated SDK versions, a `transitional` Drizzle requirement
explicitly not the Module Database ABI, and per-file size and SHA-256;
- archive safety: no links of any kind, no absolute or `..` paths, no special
files or setuid/setgid; extraction normalizes ownership and modes;
- the intended installation lifecycle and its rollback limit: code-only
rollback only before new migrations apply, a backup restore after;
- integrity, not authenticity; signing and trust roots deferred;
- pnpm deploy explicitly not the packaging mechanism.
Status Proposed, following the repository's usual lifecycle. ADR 0047 gains a
cross-reference to consume canonical packages. The module development guide no
longer recommends running pnpm deploy in a workspace, which corrupts that
workspace's install state. No runtime, tooling, Docker, CI, Signage, or
instance changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Proposes ADR 0049: Ekklesia Module Package v1, extending ADR 0048. Docs only: no runtime code, packager, installer, Docker, CI, Signage, or instance changes.
What it decides
One artifact. A module release is one immutable, deterministic
.tar.gz(sorted paths, uid/gid 0, mtime 0, gzip header with no name or time), with its SHA-256 distributed beside it. Identity is the contents, never the transport; Gitea, GitHub, OCI, object storage, offline media, or a marketplace may carry it unchanged.Layout. One top-level directory named with the module id, containing:
package.json(name, version, private,"type": "module", layer only);ekklesia.package.json;THIRD_PARTY_NOTICES;node_modulesonly for native dependencies.No source, tests, declarations, source maps, development dependencies, or
workspace:references.Backend bundling. Every pure-JavaScript dependency is bundled, including the SDK and contracts. The canonical packager alone decides and validates what stays external (native or platform-specific code); it is not an author-facing choice.
Targets.
any, or<platform>-<arch>[-<libc>]in Node's own vocabulary, with libc required on Linux. Signage islinux-x64-glibcbecause ofsharp. One target per package; multi-architecture packages deferred.ekklesia.package.json(packageFormat: 1). It records:transitional.databaseRuntimeDrizzle requirement;Platform and SDK ranges stay only in the manifest.
Drizzle. Explicitly a temporary implementation constraint, not the Module Database ABI; a separate ADR will make the platform's ORM an implementation detail.
Licences.
THIRD_PARTY_NOTICESis mandatory. The packager PR must prove licence preservation before anything counts as a release.Archive safety. No links of any kind in v1, no absolute or
..paths, no special files, no setuid/setgid; extraction normalizes ownership and modes; nothing is executable.Installation lifecycle (intended, not implemented). Verify digest → hidden staging → validate metadata and per-file hashes → target and runtime check → whole candidate inventory → isolated entrypoint load → migration plan → stop API → swap → explicitly approved migrations → restart → readiness.
Rollback. Restoring the previous code is supported only before new migrations apply; after that, rollback means restoring the matching backup. No down migrations.
Trust. The digest gives integrity, not authenticity. Signing and trust roots are deferred, and modules remain trusted code.
Tooling. An Ekklesia-owned packager (proposed
packages/module-pack,ekklesia-pack-module).pnpm deployis explicitly not the mechanism.Other files
docs/architecture/decisions/README.md: index entry.0047-module-ci-harness.md(Proposed): one header cross-reference saying the harness should consume canonical packages once 0049 is accepted.docs/module-development/README.md: it previously told authors to runpnpm deploy --legacyin their workspace. It now points to ADR 0049 and warns thatpnpm deployrewrites a workspace's install state. This was observed both in scratch workspaces and earlier in a real checkout.No accepted ADR's text changes; ADR 0048's lifecycle decisions are untouched.
Status
Proposed, following the repository's usual lifecycle (0044, 0045, 0046, 0047): it becomes Accepted with the first implementation.
Verification
pnpm verify: passed (core 1015, api 510; boundaries, module and migration validation, identity schema, build).pnpm deploy:config: valid.🤖 Generated with Claude Code