Implements the coordinated public package release train (ADR 0053). This produces the exact @ekklesia/* tarballs of a platform release, validates them, proves two builds of a commit byte-identical, and proves a standalone module consumes those artifacts end to end.
Nothing is published. No registry credentials are configured, Signage is untouched, and no package API or packageFormat changes.
What changes
scripts/package-release.ts (pnpm packages:release) is the canonical release builder.
From a clean commit (--allow-modified is for local use only and is recorded as such), it checks the release set and the coordinated versions.
It rebuilds the 9 public packages from clean output without the cache, packs each, and inspects every tarball.
Only when everything has passed does it write ekklesia-packages-<version>/: the tarballs, SHA256SUMS, and ekklesia-package-train.json.
It fails on a missing or unexpected package, or on any publishable or marked package outside the set.
Release manifest.ekklesia-package-train.json is provider-neutral and is not a registry catalog. It records packageTrain: 1, the version, source.revision and source.modified, and for each package its name, version, role, file, size, sha256, and npm integrity (sha512-…).
Tarball inspection. Each tarball must have:
the expected name, version, and file name;
a LICENSE, and only its declared files, each built file backed by its source;
no workspace: range, devDependencies, publishConfig, or install-time lifecycle script;
only public @ekklesia dependencies, at the train's version;
the distribution marker;
no local path;
dependency maps in the same key order as its source.
Pack-only pnpm hook (scripts/lib/pack-hooks.cjs). It is passed to pnpm pack with --config.pnpmfile and never affects installs or the lockfile.
Key order. pnpm inserts rewritten workspace: dependencies in async-completion order. Packing the same commit from two checkouts gave module-pack's package.json different key orders, so the tarballs differed. The hook restores the source key order. It changes no name or range, and the tarball is still an ordinary pnpm pack tarball.
devDependencies. The hook drops them. They named private packages (@ekklesia/core, and @ekklesia/[email protected], which is outside the train).
scripts/test-package-release.sh (pnpm test:package-release, CI job package-release) is the gate a future publish job must depend on.
Builds the train.
Builds it again from a separate worktree at another absolute path, and requires byte-identical release directories.
Standalone consumer proof. It now consumes a release directory as the source of truth:
Install. The release is checked against its manifest. The fixture is copied outside the repository and every @ekklesia/* package, transitive ones included, comes from the release tarballs. The install runs --offline from the local store: no registry, no workspace link, no source checkout.
Lockfile. It must record each artifact's own integrity, and the module then reinstalls with --frozen-lockfile.
Install shape. No drizzle-orm and exactly one react. @ekklesia/contracts is only transitive. Every @ekklesia package, module-pack included, is exactly the release version.
Build.skipLibCheck: false typecheck, then backend and UI build.
Package. Packed with the release's module-pack, verified by Core, packager version recorded as the release's, no install path in the package.
Docs. New docs/development/releases.md covers:
the release order: version set → build → test → pack the train → consumer proof → [future publish] → tag by the maintainer, with tag authority unchanged;
the train, the artifacts, and the manifest;
the reproducibility findings;
the fact that publication is not implemented, and that these exact artifacts are what a future publish step publishes.
The module guide, testing docs, README and CLAUDE.md are updated to match.
ADR 0053 → Accepted. Only its status line and index entry change. Everything it decides that doesn't need a registry is now proven in CI, and registry provisioning is on the ADR's own Deferred list.
Evidence
pnpm verify passes: 2,564 tests, including the new release-set, manifest-inspection, release-directory, manifest-serialization and pack-hook tests.
pnpm test:package-release passes, strict on a clean commit, three times. Each run's two builds (this checkout and a separate worktree) were byte-identical (11 files), and every run produced the same module-pack tarball (a49732cc…).
The consumer proof's module package is byte-identical across runs.
No deploy smoke was run: platform runtime behaviour is unchanged.
Not in this PR
Registry provisioning, credentials, access verification, and publication.
Implements the coordinated public package release train ([ADR 0053](docs/architecture/decisions/0053-standalone-module-development-packages.md)). This produces the exact `@ekklesia/*` tarballs of a platform release, validates them, proves two builds of a commit byte-identical, and proves a standalone module consumes those artifacts end to end.
**Nothing is published.** No registry credentials are configured, Signage is untouched, and no package API or `packageFormat` changes.
## What changes
- **`scripts/package-release.ts`** (`pnpm packages:release`) is the canonical release builder.
- From a clean commit (`--allow-modified` is for local use only and is recorded as such), it checks the release set and the coordinated versions.
- It rebuilds the 9 public packages from clean output without the cache, packs each, and inspects every tarball.
- Only when everything has passed does it write `ekklesia-packages-<version>/`: the tarballs, `SHA256SUMS`, and `ekklesia-package-train.json`.
- It fails on a missing or unexpected package, or on any publishable or marked package outside the set.
- **Release manifest.** `ekklesia-package-train.json` is provider-neutral and is not a registry catalog. It records `packageTrain: 1`, the version, `source.revision` and `source.modified`, and for each package its name, version, role, file, size, `sha256`, and npm `integrity` (`sha512-…`).
- **Tarball inspection.** Each tarball must have:
- the expected name, version, and file name;
- a `LICENSE`, and only its declared `files`, each built file backed by its source;
- no `workspace:` range, `devDependencies`, `publishConfig`, or install-time lifecycle script;
- only public `@ekklesia` dependencies, at the train's version;
- the distribution marker;
- no local path;
- dependency maps in the same key order as its source.
- **Pack-only pnpm hook** (`scripts/lib/pack-hooks.cjs`). It is passed to `pnpm pack` with `--config.pnpmfile` and never affects installs or the lockfile.
- **Key order.** pnpm inserts rewritten `workspace:` dependencies in async-completion order. Packing the same commit from two checkouts gave `module-pack`'s `package.json` different key orders, so the tarballs differed. The hook restores the source key order. It changes no name or range, and the tarball is still an ordinary `pnpm pack` tarball.
- **`devDependencies`.** The hook drops them. They named private packages (`@ekklesia/core`, and `@ekklesia/[email protected]`, which is outside the train).
- **`scripts/test-package-release.sh`** (`pnpm test:package-release`, CI job `package-release`) is the gate a future publish job must depend on.
1. Builds the train.
2. Builds it again from a separate worktree at another absolute path, and requires byte-identical release directories.
3. Runs `scripts/test-standalone-module.sh --release <dir>`.
- **Standalone consumer proof.** It now consumes a release directory as the source of truth:
- **Install.** The release is checked against its manifest. The fixture is copied outside the repository and every `@ekklesia/*` package, transitive ones included, comes from the release tarballs. The install runs `--offline` from the local store: no registry, no workspace link, no source checkout.
- **Lockfile.** It must record each artifact's own integrity, and the module then reinstalls with `--frozen-lockfile`.
- **Install shape.** No `drizzle-orm` and exactly one `react`. `@ekklesia/contracts` is only transitive. Every `@ekklesia` package, `module-pack` included, is exactly the release version.
- **Build.** `skipLibCheck: false` typecheck, then backend and UI build.
- **Package.** Packed with the release's `module-pack`, verified by Core, packager version recorded as the release's, no install path in the package.
- **Docs.** New `docs/development/releases.md` covers:
- the release order: version set → build → test → pack the train → consumer proof → [future publish] → tag by the maintainer, with tag authority unchanged;
- the train, the artifacts, and the manifest;
- the reproducibility findings;
- the fact that publication is not implemented, and that these exact artifacts are what a future publish step publishes.
The module guide, testing docs, README and `CLAUDE.md` are updated to match.
- **ADR 0053 → Accepted.** Only its status line and index entry change. Everything it decides that doesn't need a registry is now proven in CI, and registry provisioning is on the ADR's own Deferred list.
## Evidence
- `pnpm verify` passes: 2,564 tests, including the new release-set, manifest-inspection, release-directory, manifest-serialization and pack-hook tests.
- `pnpm test:package-release` passes, strict on a clean commit, three times. Each run's two builds (this checkout and a separate worktree) were byte-identical (11 files), and every run produced the same `module-pack` tarball (`a49732cc…`).
- The consumer proof's module package is byte-identical across runs.
- No deploy smoke was run: platform runtime behaviour is unchanged.
## Not in this PR
- Registry provisioning, credentials, access verification, and publication.
- Signing and provenance.
- Any change to tagging.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Produces the exact public package tarballs of a platform release, validates
them, and proves a standalone module consumes those artifacts end to end.
Nothing is published and no credential is configured.
- scripts/package-release.ts (pnpm packages:release) is the canonical
release builder: from a clean commit it rebuilds the nine public
packages from clean output, packs each, inspects every tarball, checks
the release set and coordinated versions, and only then writes
ekklesia-packages-<version>/ with the tarballs, SHA256SUMS, and a
provider-neutral manifest (ekklesia-package-train.json: version,
revision, and each package's file, size, SHA-256, npm integrity).
- Packing uses a pack-only pnpm beforePacking hook (passed with
--config.pnpmfile, never the workspace's install settings). pnpm inserts
rewritten workspace: dependencies in async completion order, so two
checkouts of one commit packed module-pack's package.json with keys in a
different order; the hook restores the source key order (and the
inspection enforces it). It also drops devDependencies, which named
private packages outside the train.
- scripts/test-package-release.sh (pnpm test:package-release, the CI
package-release gate) builds the train twice, the second time from a
separate worktree, requires byte-identical release directories, then
runs the standalone consumer proof on the release: manifest checked,
offline install from the tarballs only, lockfile integrity equal to the
manifest's, frozen-lockfile reinstall, no Drizzle, one React, contracts
only transitive, every @ekklesia package and module-pack at the release
version, skipLibCheck: false, UI build, pack, and Core verification.
- docs/development/releases.md documents the release order, the train,
the artifacts, and the reproducibility findings; registry publication
remains unimplemented.
- ADR 0053 is Accepted: everything it decides that does not need a
registry is now proven, and registry provisioning is on its own
Deferred list.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The package-release job failed its clean-tree assertion in CI. Reproduced
as root in a container with a bind-mounted checkout, as the Gitea runner
does: after checkout the tree is clean; `pnpm install --frozen-lockfile`
then creates `.pnpm-store/` (17,272 untracked files) at the checkout's
root, because pnpm keeps its store on the node_modules file system and
the checkout is its own mount; `pnpm build` changes nothing. No tracked
file is ever modified.
- `.pnpm-store/` is ignored, like node_modules: a package cache, never
source. The clean-commit requirement is unchanged for every tracked
file and every untracked file Git does not ignore.
- The clean-tree failure now lists `git status --short` and
`git diff --stat`.
- Nothing is built before the clean-source assertion: the gate no longer
runs a global `pnpm build` first. The builder asserts a clean tree,
builds, then fails if its build changed the tree, before packing. Core's
verifier is built after both releases, for the consumer proof only.
- Every secondary offline install (the second worktree, the consumer
module) uses the workspace install's own store explicitly, since pnpm
picks a store per file system and those directories are on another.
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.
Implements the coordinated public package release train (ADR 0053). This produces the exact
@ekklesia/*tarballs of a platform release, validates them, proves two builds of a commit byte-identical, and proves a standalone module consumes those artifacts end to end.Nothing is published. No registry credentials are configured, Signage is untouched, and no package API or
packageFormatchanges.What changes
scripts/package-release.ts(pnpm packages:release) is the canonical release builder.--allow-modifiedis for local use only and is recorded as such), it checks the release set and the coordinated versions.ekklesia-packages-<version>/: the tarballs,SHA256SUMS, andekklesia-package-train.json.Release manifest.
ekklesia-package-train.jsonis provider-neutral and is not a registry catalog. It recordspackageTrain: 1, the version,source.revisionandsource.modified, and for each package its name, version, role, file, size,sha256, and npmintegrity(sha512-…).Tarball inspection. Each tarball must have:
LICENSE, and only its declaredfiles, each built file backed by its source;workspace:range,devDependencies,publishConfig, or install-time lifecycle script;@ekklesiadependencies, at the train's version;Pack-only pnpm hook (
scripts/lib/pack-hooks.cjs). It is passed topnpm packwith--config.pnpmfileand never affects installs or the lockfile.workspace:dependencies in async-completion order. Packing the same commit from two checkouts gavemodule-pack'spackage.jsondifferent key orders, so the tarballs differed. The hook restores the source key order. It changes no name or range, and the tarball is still an ordinarypnpm packtarball.devDependencies. The hook drops them. They named private packages (@ekklesia/core, and@ekklesia/[email protected], which is outside the train).scripts/test-package-release.sh(pnpm test:package-release, CI jobpackage-release) is the gate a future publish job must depend on.scripts/test-standalone-module.sh --release <dir>.Standalone consumer proof. It now consumes a release directory as the source of truth:
@ekklesia/*package, transitive ones included, comes from the release tarballs. The install runs--offlinefrom the local store: no registry, no workspace link, no source checkout.--frozen-lockfile.drizzle-ormand exactly onereact.@ekklesia/contractsis only transitive. Every@ekklesiapackage,module-packincluded, is exactly the release version.skipLibCheck: falsetypecheck, then backend and UI build.module-pack, verified by Core, packager version recorded as the release's, no install path in the package.Docs. New
docs/development/releases.mdcovers:The module guide, testing docs, README and
CLAUDE.mdare updated to match.ADR 0053 → Accepted. Only its status line and index entry change. Everything it decides that doesn't need a registry is now proven in CI, and registry provisioning is on the ADR's own Deferred list.
Evidence
pnpm verifypasses: 2,564 tests, including the new release-set, manifest-inspection, release-directory, manifest-serialization and pack-hook tests.pnpm test:package-releasepasses, strict on a clean commit, three times. Each run's two builds (this checkout and a separate worktree) were byte-identical (11 files), and every run produced the samemodule-packtarball (a49732cc…).Not in this PR
🤖 Generated with Claude Code