feat: publish the exact @ekklesia package train to the npm registry (ADR 0053)
CI / verify (pull_request) Successful in 1m1s
CI / infrastructure-smoke (pull_request) Successful in 35s
CI / migration-smoke (pull_request) Successful in 1m53s
CI / package-release (pull_request) Failing after 43s
CI / package-publication (pull_request) Skipped
CI / image-build (pull_request) Successful in 29s
CI / deploy-smoke (split) (pull_request) Canceled after 0s
CI / deploy-smoke (same-origin) (pull_request) Canceled after 38s

Publishes one package release train to the Gitea npm registry under
River_of_Glory_Church, whose packages are publicly readable on purpose:
exactly the .tgz files the package-release gate proved, and nothing else.
Nothing is published by this change.

- scripts/publish-package-train.ts validates the whole local train before
  any request (gate manifest hash, version, revision, unmodified source,
  the nine packages, every artifact's size, SHA-256, integrity,
  SHA256SUMS line, and inner package.json), then publishes in dependency
  order with `npm publish <file.tgz>` under a staging tag. Each package
  is reconciled first (absent: publish; same bytes: accept; other bytes:
  hard stop, version burned), then read back: metadata (name, version,
  integrity, dependency maps, normalized bin, tarball on the registry)
  and the tarball itself, byte for byte, also anonymously. `latest` moves
  only after all nine verify; reruns converge and never delete anything.
  It writes a publication record. The token comes from
  EKKLESIA_PACKAGES_PUBLISH_TOKEN only, through a temporary npm config
  that refers to the variable.
- scripts/test-package-publication.sh proves it against a disposable
  Gitea 1.27.2 with public owners, a publisher team and an outsider:
  anonymous reads, refused unauthorized publishing, byte-identical round
  trips, the registry's own 409, partial-train recovery, the burned
  version hard stop, and dist-tag retry convergence.
- CI: package-release keeps its proven release and hands it on as an
  artifact with its manifest hash; the new package-publication job
  publishes exactly that artifact to the disposable Gitea. Artifact
  actions v3 (node20): v4 refuses any server other than github.com.
- .github/workflows/publish-packages.yml publishes to the real registry
  only by manual dispatch on main, after its own gate run, with the
  secret in the publish step alone.
- docs: the public-read model, the workflow, the record, credentials,
  immutability policy, provider moves, and the first-publication steps.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ekklesia
2026-10-08 09:49:27 -05:00
co-authored by Claude Opus 5.5
parent 57df2e15f2
commit d3c62e63bf
17 changed files with 2393 additions and 33 deletions
+29 -7
View File
@@ -73,16 +73,35 @@ entrypoints. A module in its own repository depends on the published packages in
### Developing a module in its own repository
> **Status:** the public packages are ready for a standalone module, and a release check proves it
> from locally packed tarballs. They are **not published yet**, and there is no registry URL to use
> yet: registry publication is the next slice
> ([ADR 0053](../architecture/decisions/0053-standalone-module-development-packages.md)). This
> section describes the workflow that publication enables.
> **Status:** the packages are published by release
> ([ADR 0053](../architecture/decisions/0053-standalone-module-development-packages.md),
> [Releases and the public package train](../development/releases.md)). The first published train
> is the next platform release after 0.1.2.
A module does not need an Ekklesia checkout. Do not clone Ekklesia and put your module in its
`modules/` directory: your module is its own repository, and it depends on Ekklesia's
module-development packages like any other npm packages.
The packages are **publicly readable**: installing them needs no account, no token, and no access
to Ekklesia's Gitea. Only Ekklesia's release workflow can publish them. Point the `@ekklesia` scope at
the registry in your repository's committed `.npmrc`; that is all the configuration there is:
```ini
@ekklesia:registry=https://git.riverofglorymn.org/api/packages/River_of_Glory_Church/npm/
```
Then the module builds and packages like any other:
```bash
pnpm install
pnpm build
pnpm exec ekklesia-pack-module --module . --target any --out ../packages \
--license-file ./LICENSE --source-revision "$(git rev-parse HEAD)"
```
Before 1.0, a minor version may change these APIs (the project's SemVer policy). Pin the train you
build against with ranges in `package.json` and exact versions in your lockfile.
| Package | Where it goes | What it is |
| --- | --- | --- |
| `@ekklesia/module-sdk` | `dependencies` | `defineModuleRuntime`, routes, configuration, storage, realtime, principals |
@@ -148,6 +167,9 @@ import it yourself, because what it exports is the platform's, not a supported a
allowBuilds:
esbuild: true
```
- **Lockfiles name the registry.** pnpm records Gitea's tarball URLs in your lockfile. If the
packages ever move to another registry, change `@ekklesia:registry` and regenerate the lockfile;
nothing else changes, and the integrity of every package stays the same.
- **TypeScript.** Extend the shared configurations by package name, for example
`"extends": "@ekklesia/typescript-config/node.json"` for the backend and
`@ekklesia/typescript-config/browser-react.json` for the UI.
@@ -160,8 +182,8 @@ credited as third-party software, wherever they came from.
Ekklesia proves this on every CI run: `pnpm test:package-release` builds the public packages of a
release as one train of tarballs, proves two builds identical, and installs exactly those tarballs
into a module outside the repository (`tests/fixtures/standalone-module`), with no workspace link
and no registry, then typechecks, builds, packages, and verifies it. Those artifacts are what a
future publication step publishes ([Releases and the public package train](../development/releases.md)).
and no registry, then typechecks, builds, packages, and verifies it. Those exact artifacts are what
the release publishes ([Releases and the public package train](../development/releases.md)).
### An installed module is self-contained