This runbook is written for the recurring maintainer path: linked accrue +
accrue_admin releases via Release Please on a green main, followed by ordered
Hex publishes and lightweight post-publish checks. The same-day 1.0.0 bootstrap
story is an exceptional appendix at the end — read that only when you are
intentionally coordinating a first public major.
Planning milestones vs Hex SemVer: files under .planning/ may use labels like v1.14 or v1.15 for internal milestone bookkeeping. Those do not replace the accrue / accrue_admin @version values in each mix.exs or the versions published on Hex. Consumers pin and upgrade against Hex + changelogs; maintainers use this runbook plus accrue/guides/upgrade.md.
The 1.0.x line treats the documented public facade as the SemVer boundary: generated MyApp.Billing, the Accrue.Billing context, public behaviors such as Accrue.Auth, Accrue.PDF, and Accrue.Mailer, public Ecto schemas, public Plug routes, and the documented Telemetry event contract. Routine releases tighten correctness, docs, observability, and provider parity within that boundary; breaking the boundary requires a new major.
- SemVer discipline. Patch releases carry bug fixes and doc-only changes inside the documented facade. Minor releases carry additive features, optional config, optional adapters, forward-compatible Telemetry events, and soft deprecations. Major releases remove hard-deprecated symbols, change the documented Plug/router contract, introduce breaking schema migrations on
accrue_*tables, or change the webhook signature verification contract. - Deprecation cycle. Breaking changes on the documented surface follow a two-step deprecation cycle: first mark the old path in
CHANGELOG.md, with@deprecated, and inaccrue/guides/upgrade.mdwithout a runtime warning; then hard-deprecate in a later minor with a runtime warning. The replacement path must exist for at least one minor before removal in the next major. - Cadence. Patch releases land as needed. Minor releases land when a coherent additive batch is ready. Major releases are rare and pre-announced with at least one
2.0.0-rc.Nstabilization window before the stable tag. - Lockstep.
accrueandaccrue_admincontinue shipping as a coordinated combined Release Please PR. Major versions stay aligned, even when an admin-only minor leads the core package. - Supported integration surface.
accrue/guides/maturity-and-maintenance.mdis the authoritative list of the public facade, the Telemetry event contract, and the pieces that are explicitly not part of the SemVer boundary. - Verification expectation. Every release passes the merge-blocking
host-integrationFake-backed gate. Majors additionally require thelive-stripelane green within the release window as maintainer sign-off. - Forward-port policy. critical security fixes are forward-ported to the latest minor of the previous major for 6 months after a new major ships. Older majors are end-of-life and documented in
accrue/guides/maturity-and-maintenance.md. - Pre-release tags. Use
1.x.y-rc.Nfor opt-in previews of risky changes and2.0.0-rc.Nfor the next major's stabilization window. RC tags publish to Hex with--preand never auto-resolve for~> 1.0consumers. - Verification lanes. Fake stays the canonical deterministic gate, while provider parity remains a separate lane for Stripe-specific validation that Fake cannot cover.
- Last verified line. Update the line below whenever
release-please-config.json,.release-please-manifest.json, or.github/workflows/release-please.ymlchange.
Last verified against release-please-config.json, .release-please-manifest.json,
and .github/workflows/release-please.yml on 2026-04-23 (UTC). Update this line when
automation semantics change.
- Confirm CI is green on
main, especially therelease-gateworkflow and the lanes below. - Let Release Please open the combined linked release PR (see
release-please-config.json—separate-pull-requests: false— and.release-please-manifest.jsonfor per-package versions). - Review the PR: both
accrue/mix.exsandaccrue_admin/mix.exs@versionvalues match the manifest, both package-localCHANGELOG.mdfiles update, and automation outputs look sane. - Default (primary path): merge the combined Release Please PR manually on GitHub when required checks are green. Merge after checklist sign-off, or after review dispatch Actions → Release PR automation → Run workflow
with the PR number so auto-merge queues only via
workflow_dispatch(not on PR open/sync). Usescripts/ci/gh_merge_release_pr.shif you need the Release Please PR number before dispatching. - Confirm Hex package availability for
accruebefore relying onaccrue_adminconsumers. - Let
.github/workflows/release-please.ymlpublishaccrue_adminwhen the workflow gates (needs.release.outputs.*,ACCRUE_ADMIN_HEX_RELEASE=1) say it is safe —accruepublishes first. - Verify HexDocs for both packages, tags, and GitHub releases as appropriate.
The standard path is .github/workflows/release-please.yml:
- Release Please runs only on pushes to
mainand manualworkflow_dispatch. release-please-config.jsonuses one combined release PR foraccrueandaccrue_admin(separate-pull-requests: false) so versions andscripts/ci/verify_package_docs.shstay aligned.- Authoritative package changelogs are only
accrue/CHANGELOG.mdandaccrue_admin/CHANGELOG.md(the paths wired inrelease-please-config.json); do not add duplicate changelogs under nested directories such asaccrue/accrue/oraccrue_admin/accrue_admin/. - Automated publish is gated by same-workflow outputs:
needs.release.outputs.accrue_release_createdneeds.release.outputs.accrue_admin_release_created
accruepublishes first.accrue_adminpublishes only after theaccruepublish job succeeds when both packages release together.- The
publish-accrue-adminjob inrelease-please.ymldeclaresneeds: [release, publish-accrue], which enforcesaccruebeforeaccrue_adminfor linked Hex publishes. accrue_admindry-run and publish steps exportACCRUE_ADMIN_HEX_RELEASE=1.- If Release Please creates the core GitHub Release but not the admin one in the same run, the workflow lockstep fallback still publishes
accrue_adminwhen both manifest versions match (same push SHA).
This automation does not publish from pull_request, pull_request_target, or ordinary branch pushes.
.github/workflows/release-pr-automation.yml runs only on workflow_dispatch. After you review the Release Please PR, optionally dispatch it with the PR number so gh pr merge --merge --auto queues merge when required checks pass — there is no subscription to pull_request events.
Enable Allow auto-merge under repository Settings → General. If branch protection requires approving reviews, complete human review first, then dispatch (or merge manually / use scripts/ci/gh_merge_release_pr.sh).
- Release Please is the single writer for
@versionbumps inaccrue/mix.exsandaccrue_admin/mix.exsand for new numbered sections inaccrue/CHANGELOG.mdandaccrue_admin/CHANGELOG.md. - At the merge/tag/publish boundary,
## Unreleased/## [Unreleased]must not be the only home for work that already appears under the shipped version section: when the release PR is cut, freeze or drain Unreleased so nothing ships with prose that belongs under the version that is tagging. - Human polish belongs on the open release PR or in conventional commit bodies, not as a competing manual version block on
main.
Canonical local demo: Fakeis the required deterministic gate for docs and release readiness. This is the normal release lane.Provider parity: Stripe test modeis for optional/manual provider-parity checks. Use it to prove hosted Checkout behavior, signed Stripe webhook delivery, SCA/3DS branches, and response-shape fidelity that Fake does not cover.Advisory/manual: live Stripeis for final app-level confidence before shipping your app. It is not required for clone-to-evaluate, standard CI, or normal Accrue releases.
Fake is the canonical front door and required deterministic gate. Stripe-backed lanes exist to catch provider drift and app-specific integration risk, not to replace Fake or to block ordinary package releases.
The required deterministic gate includes package verification, host integration, generated drift/docs drift, the checked-in security/trust artifact at .planning/phases/15-trust-hardening/15-TRUST-REVIEW.md, seeded performance smoke, compatibility floor/target checks, and browser accessibility/responsive checks.
For the provider-parity detail lane, see guides/testing-live-stripe.md.
- The combined release PR updates both
accrue/mix.exsandaccrue_admin/mix.exs@versionconsistently with.release-please-manifest.jsonbefore publish jobs run. - The same PR updates both package-local
CHANGELOG.mdfiles. accruepublishes beforeaccrue_admin.Canonical local demo: Fakeremains the required deterministic gate before release.- The required deterministic gate still includes
security/trust artifact,seeded performance smoke,compatibility floor/target checks, andbrowser accessibility/responsive checks. Provider parity: Stripe test modestays optional/manual and out of the required release lane.Advisory/manual: live Stripestays advisory/manual before shipping your app, not a package release blocker.RELEASE_PLEASE_TOKENandHEX_API_KEYexist only as GitHub Actions secrets.- Secrets are never checked into docs, commit messages, config files, or echoed in workflow logs, and public docs must not ask for webhook secrets, customer data, or PII.
Create both secrets in GitHub before the first release run:
- Open the repository on GitHub.
- Go to Settings -> Secrets and variables -> Actions.
- Add
RELEASE_PLEASE_TOKENas a GitHub token that can create release pull requests, push release tags, create GitHub releases, and write pull-request comments for this repository. - Add
HEX_API_KEYas a Hex.pm API key that can publish theaccrueandaccrue_adminpackages. - Never paste either value into workflow files, docs, commit messages, terminal transcripts, issues, or pull requests.
For the first anonymous maintainer release, use the GitHub identity szTheory
and the noreply commit email already configured in this checkout:
szTheory@users.noreply.github.com.
Run the required deterministic gate first:
cd accrue
mix test --warnings-as-errors
bash ../scripts/ci/verify_package_docs.shThat Canonical local demo: Fake lane is the required deterministic gate for release readiness because it stays credential-free and reproducible.
Required deterministic gate checklist:
- package verification
- host integration
- generated drift/docs drift
- security/trust artifact
- seeded performance smoke
- compatibility floor/target checks
- browser accessibility/responsive checks
If you need provider fidelity coverage, run Provider parity: Stripe test mode separately. It is optional/manual, uses Stripe test-mode secrets through environment variables or GitHub secrets, and exists to prove provider-specific behavior that Fake cannot:
- hosted Checkout behavior
- signed Stripe webhook delivery and signature fidelity
- SCA/3DS branches
- Stripe response-shape drift
Keep provider-backed checks out of the required release lane. In real integrations, signed webhook verification and runtime secrets remain required; this runbook does not make raw-body verification optional. Follow the public webhook guidance in accrue/guides/webhooks.md and the provider-parity detail guide at guides/testing-live-stripe.md. Never paste webhook secrets, customer data, or PII into release docs, issue reports, or retained artifacts.
Advisory/manual: live Stripe is the last lane. Use it for final host-app confidence before shipping your app, after the Fake gate and any Stripe test-mode parity checks. It is advisory/manual before shipping your app, not a required Accrue release blocker.
If Release Please dry-run cannot produce a combined release PR when you need one, use the manual fallback only after creating and reviewing a manual release PR that sets both package versions and both package changelogs consistently.
Use .github/workflows/publish-hex.yml only as a manual fallback or recovery path:
package: chooseaccrueoraccrue_admintag: reviewed tag or commit ref to publish fromrelease_version: expected version at that ref
Manual fallback order:
- Publish
accrue. - Confirm Hex availability.
- Publish
accrue_admin.
Each recovery run checks out the explicit ref, verifies the package @version, runs mix hex.publish --dry-run, then runs mix hex.publish --yes. The recovery workflow never references steps.release.outputs[...].
When the dual publish is not one atomic transaction, prefer the smallest corrective step first:
- Retry
accrue_adminfor the same version if coreaccrueat V is already correct on Hex — token, metadata, or transient CI issues often clear on a focused re-run. mix hex.publish --revertonly for a clear mistake onaccrueand only inside Hex’s short post-publish window; see Hex immutability / retire FAQ.- Otherwise use
mix hex.retireon the bad release and ship a new paired version forward (new combined release PR), with changelog honesty about what not to use. - If
accrueat V should not be consumed without admin V, document the partial state and follow the retire / forward-fix path rather than leaving a silent half-pair.
See https://hex.pm/docs/faq for revert windows, retirement, and registry semantics.
Use this section only when you are intentionally coordinating a first public 1.0.0
(or an equivalent historic bootstrap) for both packages in one tightly managed window.
- Confirm CI is green on
main, especially therelease-gateworkflow and the required deterministic gate for both packages. - Trigger or merge the combined Release Please PR that explicitly carries
Release-As: 1.0.0for both package paths when needed. The first bootstrap should use Conventional Commits plus theRelease-As: 1.0.0trailer for bothaccrueandaccrue_admin. - Review the release PR diff and confirm each package shows
@version "1.0.0"in itsmix.exsand the package-local changelog updates inaccrue/CHANGELOG.mdandaccrue_admin/CHANGELOG.md. - Merge the reviewed release PR manually, or after checklist sign-off run Actions → Release PR automation → Run workflow and enter the PR number so auto-merge queues only via
workflow_dispatch(not on PR open/sync). Then let.github/workflows/release-please.ymlpublishaccrue. You can usescripts/ci/gh_merge_release_pr.shto discover the Release Please PR number before dispatching. - Confirm Hex package availability for
accruebefore proceeding. - Let
.github/workflows/release-please.ymlpublishaccrue_adminwithACCRUE_ADMIN_HEX_RELEASE=1. - Verify HexDocs for both packages and confirm
llms.txtis present in generated docs output. - Verify repo health files, package changelogs, and GitHub release notes for both tags.