Release process (CalVer via release-it)

Release process (CalVer via release-it)

All workspace packages share one lockstep version yyyy.ww.patch (ISO week, e.g.
2026.34.0). Releases are cut with release-it + @csmith/release-it-calver-plugin

  • @release-it/bumper, configured in .release-it.json. Nothing is published to
    npm; versioning exists for git tags, GitHub Releases, CHANGELOG.md, and tag-driven
    deploys.

Procedure

  1. Run pnpm run release:dry first — verify the computed ISO week number and that
    all 26 package.json files appear in the bumper’s out list.
  2. Cut stable from main: pnpm run release → commits chore(release): vX,
    tag vX, GitHub Release, CHANGELOG entry, lockstep version bump.
  3. Beta: from a beta branch (or main between stable cuts), pnpm run release:beta
    → yyyy.ww.patch-beta.N tags (the calver plugin ignores release-it’s
    --preRelease, so scripts/release-beta.ts computes the suffix explicitly).
    Merge beta → main before the next stable cut.
  4. All deploys go through .github/workflows/deploy-pyinfra.yml — the
    single deploy path (pyinfra artifact + activate, Infisical env render,
    report-only drift gate, post-deploy smoke + config-audit). It fires on:
    • push to beta (BRI-435 converge-always: merging to beta IS the
      beta deploy — docs-only paths are ignored),
    • CalVer tags: vX.Y.Z -> production, vX.Y.Z-beta.N -> beta,
    • workflow_dispatch: rollback/redeploy only (ref + optional
      artifact_sha256 redeploy an existing artifact). It replaces whatever
      is live for that env.
      Every trigger is guarded: the resolve-env job refuses any sha that is
      not already on the env’s branch (beta for beta, main for prod), so a
      feature-branch commit can never be deployed. To test on beta, merge to
      beta.
      Every run builds the artifact in-run via build-artifact.yml
      (workflow_call), so deploy never races a separate builder.
      The drift gate no longer blocks: host drift warns in the run summary
      and the deploy converges the host (PYINFRA_ENV_MODE=write lets it
      reconcile the env file too).
  5. Cut a beta tag after each merge to beta. The push already deployed;
    the tag is a stable point-in-time marker: it ties a deploy run’s
    head_sha to a named release on beta.usebrioso.com, so “what was on
    staging when X shipped?” stays answerable when the branch tip has
    moved on. git fetch origin beta and inspect the tag’s git log -1
    (or the GitHub Release) instead of guessing from commit SHAs.

Fail-first PR checks (BRI-434)

Deploy problems surface on the PR, not on deploy night:

  • build-artifact.yml runs build + boot-smoke + smoke-test-artifact on
    PRs touching deployable paths (apps/**, packages/**, deploy/**,
    scripts/**, config/**, .github/**, toolchain manifests). PR builds
    resolve no GitHub Environment — they compile against the checked-in
    fixture env deploy/artifact-env/smoke.env.example (audit-build-env.ts
    still enforces required key NAMES) and skip the Release publish (the
    smoke jobs consume the run-scoped artifact upload).
  • deploy-pr-checks.yml (pull_request_target, base-branch code only,
    PRs into beta/main): secrets-check hard-fails when a GitHub secret
    NAME in config/deploy-secrets.yaml is missing from the target
    environment, audits the Infisical render, and asserts the Modal worker
    secret’s required keys; drift-report posts the live
    pyinfra --dry --json report as a warning + job summary (informational).
  • mise run deploy:rehearse locally remains the pre-merge dry run.

Retired deploy surfaces (BRI-435)

Deleted: deploy-beta.yml (bash beta deploy; its deploy-beta.sh was
already gone), pyinfra-deploy-beta.yml (Phase-3 soak, superseded by
deploy-pyinfra.yml), modal-deploy.yml, deploy-modal-audiocpp.yml,
beam-deploy.yml (dispatch-only deploys of retired runtimes),
build-runpod.yml (retired image build). Still live: deploy.yml
(frozen manual audit trail — do not dispatch), deploy-modal-worker.yml,
deploy-secrets-sync.yml, pyinfra-drift.yml (daily drift schedule).