Release tooling: publish ably/pubsub-server through a distribution mirror - #225
Draft
umair-ably wants to merge 5 commits into
Draft
Release tooling: publish ably/pubsub-server through a distribution mirror#225umair-ably wants to merge 5 commits into
umair-ably wants to merge 5 commits into
Conversation
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This was referenced Sep 9, 2026
umair-ably
added a commit
that referenced
this pull request
Sep 9, 2026
One dependency-free PHP script, shared by release.yml and the check.yml dry-run job, so the release gate and the continuous gate cannot drift. It reports every failure rather than the first, and pushes nothing: the release workflow only starts publishing once this exits 0. Checks: the version input's shape; Defaults::LIB_VERSION and the top CHANGELOG.md heading agree with it (with --dry-run they only have to agree with each other, since there is no authoritative version on a PR); composer.json names ably/pubsub-server, which is what makes the copy of release.yml on main inert against the legacy layout; composer validate --strict; that the release's own tags do not already exist somewhere other than the dispatched commit; and the invariant the distribution mirror exists to protect — that no Composer-valid tag at or above 2.0.0 exists in this repository, because the legacy ably/ably-php Packagist package indexes every Composer-valid tag here regardless of its composer.json name. An existing tag that already points at HEAD is reported as a re-run rather than a failure, so a partial release can be completed by dispatching the same version again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There is no registry upload step in PHP — Packagist serves whatever git tags
a repository carries — so "publishing" here means pushing the release commit
and the plain version tag to a mirror repository, tagging and releasing here,
and proving Packagist picked it up.
The mirror exists because a Packagist package indexes every Composer-valid
tag of the repository it is bound to, whatever that tag's composer.json says
(composer's VcsRepository::preProcess overwrites each version's name with the
default branch's, and packagist's Updater then stamps the package's own name
over it). The legacy ably/ably-php package stays bound to this repository, so
a plain 2.0.0 tag here would be served as its latest version to every
`ably/ably-php: *` consumer. Hence: plain tags only on ably/ably-pubsub-php-dist,
namespaced pubsub-server/<version> tags only here.
workflow_dispatch only, permissions {} at the top with contents: write on the
job alone. The pre-flight runs before anything is pushed and the unit-level
tests run with it. Every publishing step checks for its own artifact and skips
it, so a partial run is completed by dispatching the same version again.
The mirror push needs a MIRROR_PUSH_TOKEN secret: GITHUB_TOKEN cannot reach
another repository. Neither the mirror repo nor the secret exists yet; both are
admin prerequisites, and until the mirror exists the pre-flight fails on its
tag check and nothing is pushed anywhere.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The gate that blocks a release and the gate that runs continuously are now the same script, so version-site drift and a stray Composer-valid 2.x tag surface at pull-request time rather than at dispatch time. The job checks out full history because tags are what the 2.x guard inspects; a shallow checkout would let it pass by knowing nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The file had no release section at all; publishing was manual and tag-driven. Adds the new flow (bump the two version sites in a PR, merge, dispatch release.yml), what the pre-flight checks, the mirror and the three things it needs that live outside this repository, and — at length, because it is the one mistake that cannot be undone — why a plain 2.x.y tag must never be pushed here and why 1.x maintenance tags stay plain. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both the mirror's plain tag and this repository's namespaced tag are annotated, and an annotated tag needs a tagger identity: `git tag -a` fails with "Committer identity unknown" on a runner, which has none by default. The identity was configured in the step that tags this repository, which is after the step that tags the mirror. Configured once, right after the fetch, so it is in place before either. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
umair-ably
force-pushed
the
pubsub-split/release-tooling
branch
from
September 9, 2026 10:33
b25071e to
a40e898
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #223 — review that first; this diff shows only the release-tooling changes.
Implements plan steps 15, 15b (repo-side half), 15c (repo-side half) and 16 of
plan.md— row 2 of the PR stack in step 10b. Applies PDR-091b.This repository has no release workflow at all today: publishing is manual and tag-driven — bump
Defaults::LIB_VERSION, edit the CHANGELOG, push a tag, write a GitHub release, and let the Packagist webhook notice. That is fine for a package whose repository serves exactly one Packagist entry. It stops being fine the moment this repository has to publishably/pubsub-serverwhileably/ably-phpis still bound to it.What
scripts/release-preflight.phpcomposer installand against a tree whose autoloader is broken). Shared by the release workflow, the PR dry-run job and a maintainer at a terminal, so the release gate and the continuous gate cannot drift. Reports every failure rather than the first..github/workflows/release.ymlworkflow_dispatchonly, one requiredversioninput.permissions: {}at the top,contents: writeon the single job. Pre-flight → unit tests → mirror push and plain tag → namespaced tag and GitHub release here → Packagist visibility poll. Third-party actions pinned to full SHAs with version comments, ascheck.ymldoes..github/workflows/check.ymlrelease-dry-runjob on every PR and push:php scripts/release-preflight.php --dry-run. Full-history checkout, because tags are what the 2.x guard inspects and a shallow checkout would let it pass by knowing nothing.CONTRIBUTING.md2.x.ytag must never be pushed here. The existing contribution bullets are unchanged..gitattributes/scripts export-ignore, so the release tooling is not in the archive consumers install.README.md,CHANGELOG.md,src/andtests/are deliberately untouched — the docs PR is being written in parallel.Why a distribution mirror and namespaced tags
Verified against the composer/packagist and composer/composer sources: a Packagist package indexes every Composer-valid tag of the repository it is bound to, whatever that tag's
composer.jsonsays. Composer'sVcsRepository::preProcessdeliberately overwrites each version'snamewith the default branch's name — the comment there says it "ensures that a package can be renamed in one place and that all old tags will still be installable using that new name" — and Packagist'sUpdaterthen stamps its own package name over every version. There is no name-based filtering of versions anywhere in that path. Tags whose name is not a valid version, likepubsub-server/2.0.0, are skipped outright (VcsRepository::validateTag).ably/ably-phphas to stay bound to this repository. Its ~8.7M downloads put it behind Packagist'sPopularPackageSafetyValidator, so its URL cannot be moved to a different repository without Packagist support, and a repository-ID change would freeze the package.Put those two facts together and a plain
2.0.0tag pushed here becomesably/ably-phpversion2.0.0: a different package name, a different namespace, a different PHP floor, served as the latest release to every consumer withably/ably-php: *or>=1.1. They upgrade into an install that does not load. Deleting the tag afterwards does not undo it — a maintainer has to pull the version on Packagist by hand.So the two packages cannot share a repository:
ably/ably-phpstays here, and keeps indexing plain1.xtags frommaintenance/1.x. Those tags stay plain: that package is supposed to index them.ably/pubsub-serveris published from a read-only distribution mirror,ably/ably-pubsub-php-dist(name per plan.md step 1; the repository does not exist yet). The plain<version>tag exists only there. The mirror is a build artifact that happens to be a git repository — it takes the fullmainhistory so its SHAs match this repository's, and it never accepts issues or pull requests.pubsub-server/<version>tags, which Composer skips as invalid version names.Why one package, not core + server
This is a deviation from every other SDK in the programme, and it is a decision rather than an omission — PDR-091b2's PHP row already names only
ably/pubsub-server. It is argued in full in #223; the short form, and the part that belongs in this PR:export … from;class_aliasdoes not satisfy a type hint written against the aliased name in a way static analysis follows. A core would force consumers touse Ably\PubSub\Core\Models\Message— type-hinting out of a package whose description says "internal".The guardrails
Each was broken deliberately and the failure observed, rather than only checked in the passing direction.
git tag 2.0.0in the worktreeably/ably-phpindexes it. Passed again aftergit tag -d.--version 9.9.9LIB_VERSIONand the CHANGELOG heading — not just the first.Repository not found, with a pointer to plan.md steps 9 and 15c. This is the current state of the world, and it is why nothing can be released yet.composer.jsonnamesably/pubsub-servermain(PR 3) inert against the legacy layout.--versionLIB_VERSIONand the CHANGELOG heading are compared to each other, since a PR has no authoritative version.MIRROR_PUSH_TOKENis set::error::naming the secret, the mirror and CONTRIBUTING.md, before any push is attempted.::error::explaining that the release itself is done and what to check on the mirror.Two design notes on the pre-flight:
pubsub-server/<version>or the mirror's<version>already points at the commit being released, that is a re-run and it is reported as one; if it points at a different commit, it is always a failure. Releasing one version from two commits is never right.The plain
2.xtag is created inside the release job, pushed to the mirror and deleted from the runner's clone in the same step. It is never pushed toorigin, and if it ever were, the next release's pre-flight would refuse to run.Verification
Run locally on PHP 8.5 / Composer 2 in a worktree off
pubsub-split/restructure.actionlint .github/workflows/release.yml .github/workflows/check.yml— clean onrelease.yml. The one remaining finding is pre-existing, from Split Pub/Sub SDK: ably/pubsub-server package, Ably\PubSub namespace, server door #223:check.yml:44SC2086on$IGNORE_PLATFORM_REQ, which is deliberately unquoted so an empty matrix value expands to no argument. Not touched here.php -l scripts/release-preflight.php— no syntax errors.php scripts/release-preflight.php --dry-run— exit 0: version sites agree at 2.0.0;composer validate --strictpassed; no Composer-valid tag ≥ 2.0.0 (21 tags inspected).php scripts/release-preflight.php --version 2.0.0 --skip-remote— exit 0, with the mirror check reported as skipped rather than silently absent.php scripts/release-preflight.php --version 9.9.9 --skip-remote— exit 1, 2 failures listed.git tag 2.0.0 && php scripts/release-preflight.php --dry-run— exit 1 on the tag guard; exit 0 again aftergit tag -d 2.0.0.PROTOCOL=json vendor/bin/phpunit --filter 'HttpTest|PackagingTest|DefaultsTest|ClientOptionsTest'— OK (29 tests, 86 assertions), the same filter the release job runs.ably/ably-php: exit 0 for1.1.12(present), exit 1 for9.9.9(absent). So the poll can distinguish the two, which is the only thing it has to do.CHANGELOG.mdand produced the2.0.0section body alone, stopping at the next##heading.The workflow itself cannot be executed end to end until the mirror repository and its token exist, and it is not dispatchable at all until PR 3 puts it on the default branch. Every step that does not need those was exercised.
Commits
Add the release pre-flight scriptAdd release.yml: publish through the distribution mirrorCI: run the release pre-flight in dry-run mode on every PRrelease-dry-runjobCONTRIBUTING: document the release process and the tag rulerelease.yml: configure the git identity before any tag is createdgit tag -afails with "Committer identity unknown" on a runner. The identity was configured in the step that tags this repository, one step after the step that tags the mirror. Verified against a scratch repo with the global and system git config suppressed.Not in this PR / admin prerequisites
Nothing here can publish anything until these are done. They are owner and infra tasks, not code (plan.md steps 9 and 15c).
ably/ably-pubsub-php-dist, with a README saying "read-only distribution mirror — develop and file issues at ably-pubsub-php" and issues and pull requests disabled.MIRROR_PUSH_TOKENsecret on this repository: a fine-grained PAT or GitHub App installation token withcontents: writeon the mirror and nothing else.GITHUB_TOKENcannot reach another repository.https://packagist.org/api/github?username=ably, the same hook this repository has).ably/pubsub-serveron packagist.org against the mirror's URL after the first dispatch has seeded the mirror'smain— the mirror's default branch carriesname: ably/pubsub-serverfrom day one, so Packagist derives the right name. Add the SDK-team maintainers. This can happen before the integration branch merges, which is what lets the prerelease be a real Packagist install.release.ymltomain, soworkflow_dispatchis registered while the split still lives onintegration/v2. It is inert there: the pre-flight refuses a ref whosecomposer.jsonis stillably/ably-php. Gated on the 1.x maintenance branch being cut first (step 24).🤖 Generated with Claude Code