feat: the public @ekklesia package release train, built, reproduced, and proven (ADR 0053) #80

Merged
Zayin_Admin merged 2 commits from feat/public-package-release-train into main 2026-10-08 13:53:41 +00:00
Member

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.

    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

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)
ekklesia-bot added 1 commit 2026-10-08 13:04:06 +00:00
feat: the public @ekklesia package release train, built, reproduced, and proven (ADR 0053)
CI / verify (pull_request) Successful in 1m2s
CI / infrastructure-smoke (pull_request) Successful in 35s
CI / migration-smoke (pull_request) Successful in 1m52s
CI / package-release (pull_request) Failing after 16s
CI / image-build (pull_request) Successful in 29s
CI / deploy-smoke (same-origin) (pull_request) Successful in 44s
CI / deploy-smoke (split) (pull_request) Successful in 1m29s
9debe4e891
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]>
Zayin_Admin added 1 commit 2026-10-08 13:44:53 +00:00
fix: keep pnpm's package store out of the release builder's clean-source check
CI / verify (pull_request) Successful in 1m4s
CI / infrastructure-smoke (pull_request) Successful in 37s
CI / migration-smoke (pull_request) Successful in 1m54s
CI / package-release (pull_request) Successful in 32s
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
4f1b1d68c7
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]>
Zayin_Admin merged commit 57df2e15f2 into main 2026-10-08 13:53:41 +00:00
Zayin_Admin deleted branch feat/public-package-release-train 2026-10-08 13:53:41 +00:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Zayin/Ekklesia#80