Publishes a coordinated @ekklesia/* package train to the Gitea npm registry under River_of_Glory_Church (ADR 0053). It publishes exactly the .tgz files that pnpm packages:release produced and the gate proved.
This PR publishes nothing to the real registry. Real publication is only ever started by hand. Signage is untouched, and package APIs, packageFormat, versions and tags are unchanged.
Read model
Packages are publicly readable on purpose: no account or token is needed to search, read or install them. That isn't a public launch; the source is already publicly readable. Only publishing is restricted, to one dedicated publisher account through one manual workflow.
Architecture: ADR 0054
ADR 0054 (Accepted) records the project owner's decision that @ekklesia/* packages may be anonymously readable before launch.
What it supersedes. It replaces ADR 0053 only where 0053 describes the pre-launch package phase as private, or requires authenticated package reads.
Everything else in ADR 0053 is unchanged. ADR 0053 itself gains only a cross-reference line.
What reads do not mean. They are not a launch, and promise no API stability beyond pre-1.0 SemVer.
What stays the same. Publication remains authenticated and restricted, published versions stay immutable, and the River_of_Glory_Church namespace is an acceptable host.
What changes
scripts/publish-package-train.ts, the publication tool:
Validates the whole train before sending anything:
the manifest equals the one the gate proved (--manifest-sha256);
the requested --version and this commit's --revision match;
modified: false, and exactly the nine packages;
every artifact's file name, size, SHA-256, npm integrity, SHA256SUMS line, and the package.json inside it.
Publishes in dependency order, then alphabetically, never in directory order. Uses npm publish <file.tgz> under the staging tag ekklesia-staging.
Reconciles each package first:
absent → publish;
present with the same bytes → accept and verify;
present with other bytes → hard stop. The version is burned, nothing is deleted or overwritten, and nothing after it is published.
Verifies each round trip:
the registry metadata's name, version, integrity, dependency maps and bin. npm strips ./ from bin paths in the metadata it uploads; the tarball keeps the original form;
the tarball URL is on the configured registry;
the downloaded tarball is byte-identical to the release artifact;
optionally (--require-public-read), so is an anonymous download.
latest moves last, only after all nine are verified, and never backwards. Reruns converge.
Writes a publication record (--record).
Never deletes, unpublishes or overwrites; a test checks the tool's code for this.
Credentials:
the token comes only from EKKLESIA_PACKAGES_PUBLISH_TOKEN;
npm runs with a temporary configuration that refers to the variable, with user, global and project npm configuration ignored;
the token is sent only to the registry's own origin;
the temporary configuration is removed afterwards.
scripts/test-package-publication.sh (pnpm test:package-publication): a disposable Gitea 1.27.2, pinned by digest, configured like the real one: public owners, a publisher team with Packages: Write only, and an outsider. It proves:
anonymous reads work;
unauthenticated and outsider publishes are refused;
round trips are byte-identical, including anonymously;
the registry's own 409 on a duplicate version;
recovery after an interruption before the sixth package;
the burned-version hard stop, unchanged on retry;
dist-tag retry convergence.
CI.
package-release now keeps its proven release (--keep), exposes version, revision and manifest SHA-256 as job outputs, and uploads the release as an artifact.
The new package-publication job downloads that exact artifact, checks its manifest hash against the gate's output and runs sha256sum --check, then publishes it to the disposable Gitea. It holds no credentials.
The artifact actions are v3 (node20): v4's isGhes() refuses any server other than github.com.
.github/workflows/publish-packages.yml: real publication only.
Triggered by workflow_dispatch on main only; it never cancels a run in progress.
Its own gate run → the publish job (no build, no install) → the publication record artifact.
The secret appears in that one step alone. Tests check the trigger, branch guard, dependency and secret placement, and that CI never references the secret.
Docs.
docs/development/releases.md: the public-read model, the workflow, the tool, the record, credentials, the immutability policy, moving registries, and the manual steps before the first publication.
The module guide's standalone workflow: one committed .npmrc line, no token; pnpm install, pnpm build, ekklesia-pack-module.
testing.md, the README status, and CLAUDE.md.
Evidence
pnpm verify passes: 2,585 tests, including 20 new tests for the tool and the workflow rules.
pnpm test:package-publication: 10 of 10, against the disposable Gitea 1.27.2.
pnpm test:package-release passes strict, with both builds byte-identical.
During development I checked by hand against a throwaway Gitea 1.27.2:
npm's bin normalization;
that npm itself refuses to publish over an existing version, before contacting the server;
the server's 409 on a raw duplicate upload;
the outsider's 401;
byte-identical anonymous downloads.
Before the first real publication (manual, an instance administrator)
Create a dedicated publisher account and a River_of_Glory_Church team with only Packages: Write.
Issue a token with only write:package, and store it as the repository secret EKKLESIA_PACKAGES_PUBLISH_TOKEN.
Confirm the owner has no package cleanup rule.
Set the next platform version (not 0.1.2), merge, and run Publish packages on main.
Publishes a coordinated `@ekklesia/*` package train to the Gitea npm registry under `River_of_Glory_Church` ([ADR 0053](docs/architecture/decisions/0053-standalone-module-development-packages.md)). It publishes exactly the `.tgz` files that `pnpm packages:release` produced and the gate proved.
**This PR publishes nothing to the real registry.** Real publication is only ever started by hand. Signage is untouched, and package APIs, `packageFormat`, versions and tags are unchanged.
## Read model
Packages are **publicly readable on purpose**: no account or token is needed to search, read or install them. That isn't a public launch; the source is already publicly readable. Only publishing is restricted, to one dedicated publisher account through one manual workflow.
## Architecture: ADR 0054
[ADR 0054](docs/architecture/decisions/0054-public-package-reads-before-launch.md) (Accepted) records the project owner's decision that `@ekklesia/*` packages may be anonymously readable before launch.
- **What it supersedes.** It replaces ADR 0053 **only** where 0053 describes the pre-launch package phase as private, or requires authenticated package reads.
- **Everything else in ADR 0053 is unchanged.** ADR 0053 itself gains only a cross-reference line.
- **What reads do not mean.** They are not a launch, and promise no API stability beyond pre-1.0 SemVer.
- **What stays the same.** Publication remains authenticated and restricted, published versions stay immutable, and the `River_of_Glory_Church` namespace is an acceptable host.
## What changes
- **`scripts/publish-package-train.ts`**, the publication tool:
- **Validates the whole train before sending anything:**
- the manifest equals the one the gate proved (`--manifest-sha256`);
- the requested `--version` and this commit's `--revision` match;
- `modified: false`, and exactly the nine packages;
- every artifact's file name, size, SHA-256, npm integrity, `SHA256SUMS` line, and the `package.json` inside it.
- **Publishes in dependency order,** then alphabetically, never in directory order. Uses `npm publish <file.tgz>` under the staging tag `ekklesia-staging`.
- **Reconciles each package first:**
- absent → publish;
- present with the same bytes → accept and verify;
- present with other bytes → **hard stop**. The version is burned, nothing is deleted or overwritten, and nothing after it is published.
- **Verifies each round trip:**
- the registry metadata's name, version, integrity, dependency maps and `bin`. npm strips `./` from `bin` paths in the metadata it uploads; the tarball keeps the original form;
- the tarball URL is on the configured registry;
- the downloaded tarball is byte-identical to the release artifact;
- optionally (`--require-public-read`), so is an anonymous download.
- **`latest` moves last,** only after all nine are verified, and never backwards. Reruns converge.
- **Writes a publication record** (`--record`).
- **Never deletes, unpublishes or overwrites;** a test checks the tool's code for this.
- **Credentials:**
- the token comes only from `EKKLESIA_PACKAGES_PUBLISH_TOKEN`;
- npm runs with a temporary configuration that refers to the variable, with user, global and project npm configuration ignored;
- the token is sent only to the registry's own origin;
- the temporary configuration is removed afterwards.
- **`scripts/test-package-publication.sh`** (`pnpm test:package-publication`): a disposable Gitea 1.27.2, pinned by digest, configured like the real one: public owners, a publisher team with Packages: Write only, and an outsider. It proves:
- anonymous reads work;
- unauthenticated and outsider publishes are refused;
- round trips are byte-identical, including anonymously;
- the registry's own 409 on a duplicate version;
- recovery after an interruption before the sixth package;
- the burned-version hard stop, unchanged on retry;
- dist-tag retry convergence.
- **CI.**
- `package-release` now keeps its proven release (`--keep`), exposes version, revision and manifest SHA-256 as job outputs, and uploads the release as an artifact.
- The new **`package-publication`** job downloads that exact artifact, checks its manifest hash against the gate's output and runs `sha256sum --check`, then publishes it to the disposable Gitea. It holds no credentials.
- The artifact actions are **v3 (node20)**: v4's `isGhes()` refuses any server other than github.com.
- **`.github/workflows/publish-packages.yml`**: real publication only.
- Triggered by `workflow_dispatch` on `main` only; it never cancels a run in progress.
- Its own gate run → the publish job (no build, no install) → the publication record artifact.
- The secret appears in that one step alone. Tests check the trigger, branch guard, dependency and secret placement, and that CI never references the secret.
- **Docs.**
- `docs/development/releases.md`: the public-read model, the workflow, the tool, the record, credentials, the immutability policy, moving registries, and the manual steps before the first publication.
- The module guide's standalone workflow: one committed `.npmrc` line, no token; `pnpm install`, `pnpm build`, `ekklesia-pack-module`.
- `testing.md`, the README status, and `CLAUDE.md`.
## Evidence
- `pnpm verify` passes: 2,585 tests, including 20 new tests for the tool and the workflow rules.
- `pnpm test:package-publication`: 10 of 10, against the disposable Gitea 1.27.2.
- `pnpm test:package-release` passes strict, with both builds byte-identical.
- During development I checked by hand against a throwaway Gitea 1.27.2:
- npm's `bin` normalization;
- that npm itself refuses to publish over an existing version, before contacting the server;
- the server's 409 on a raw duplicate upload;
- the outsider's 401;
- byte-identical anonymous downloads.
## Before the first real publication (manual, an instance administrator)
1. Create a dedicated publisher account and a `River_of_Glory_Church` team with only Packages: Write.
2. Issue a token with only `write:package`, and store it as the repository secret `EKKLESIA_PACKAGES_PUBLISH_TOKEN`.
3. Confirm the owner has no package cleanup rule.
4. Set the next platform version (not 0.1.2), merge, and run **Publish packages** on `main`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
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]>
The CI package-release gate passed, but the upload found no files: the
artifact actions leave hidden files out, and .package-release starts
with a dot. Keep the release in package-train instead, in both
workflows, and test that no artifact path is hidden.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ADR 0053 was accepted assuming a private package phase before launch. The
project owner has decided otherwise: Ekklesia is not secret, its source is
already public, and only publication must be restricted.
ADR 0054 supersedes ADR 0053 only where it describes that phase as
private or requires authenticated package reads. Packages may be read
anonymously, which is neither a launch nor an API stability promise
beyond pre-1.0 SemVer; publication stays authenticated and restricted;
published versions stay immutable; the River_of_Glory_Church Gitea
namespace is an acceptable host; a public launch remains a separate
decision. Every other ADR 0053 decision is unchanged.
ADR 0053 gets only a cross-reference line. The ADR index, the release and
module-development guides, the README status, and the publish workflow's
comment now point at ADR 0054.
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.
Publishes a coordinated
@ekklesia/*package train to the Gitea npm registry underRiver_of_Glory_Church(ADR 0053). It publishes exactly the.tgzfiles thatpnpm packages:releaseproduced and the gate proved.This PR publishes nothing to the real registry. Real publication is only ever started by hand. Signage is untouched, and package APIs,
packageFormat, versions and tags are unchanged.Read model
Packages are publicly readable on purpose: no account or token is needed to search, read or install them. That isn't a public launch; the source is already publicly readable. Only publishing is restricted, to one dedicated publisher account through one manual workflow.
Architecture: ADR 0054
ADR 0054 (Accepted) records the project owner's decision that
@ekklesia/*packages may be anonymously readable before launch.River_of_Glory_Churchnamespace is an acceptable host.What changes
scripts/publish-package-train.ts, the publication tool:--manifest-sha256);--versionand this commit's--revisionmatch;modified: false, and exactly the nine packages;SHA256SUMSline, and thepackage.jsoninside it.npm publish <file.tgz>under the staging tagekklesia-staging.bin. npm strips./frombinpaths in the metadata it uploads; the tarball keeps the original form;--require-public-read), so is an anonymous download.latestmoves last, only after all nine are verified, and never backwards. Reruns converge.--record).EKKLESIA_PACKAGES_PUBLISH_TOKEN;scripts/test-package-publication.sh(pnpm test:package-publication): a disposable Gitea 1.27.2, pinned by digest, configured like the real one: public owners, a publisher team with Packages: Write only, and an outsider. It proves:package-releasenow keeps its proven release (--keep), exposes version, revision and manifest SHA-256 as job outputs, and uploads the release as an artifact.package-publicationjob downloads that exact artifact, checks its manifest hash against the gate's output and runssha256sum --check, then publishes it to the disposable Gitea. It holds no credentials.isGhes()refuses any server other than github.com..github/workflows/publish-packages.yml: real publication only.workflow_dispatchonmainonly; it never cancels a run in progress.docs/development/releases.md: the public-read model, the workflow, the tool, the record, credentials, the immutability policy, moving registries, and the manual steps before the first publication..npmrcline, no token;pnpm install,pnpm build,ekklesia-pack-module.testing.md, the README status, andCLAUDE.md.Evidence
pnpm verifypasses: 2,585 tests, including 20 new tests for the tool and the workflow rules.pnpm test:package-publication: 10 of 10, against the disposable Gitea 1.27.2.pnpm test:package-releasepasses strict, with both builds byte-identical.binnormalization;Before the first real publication (manual, an instance administrator)
River_of_Glory_Churchteam with only Packages: Write.write:package, and store it as the repository secretEKKLESIA_PACKAGES_PUBLISH_TOKEN.main.🤖 Generated with Claude Code