Skip to content

feat(ci): gate production deploys on approved release intent - #210

Open
haydenshively wants to merge 4 commits into
mainfrom
feature/release-intent-gated-deploys
Open

feat(ci): gate production deploys on approved release intent#210
haydenshively wants to merge 4 commits into
mainfrom
feature/release-intent-gated-deploys

Conversation

@haydenshively

@haydenshively haydenshively commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

Release intent moves from release-<bot> labels to a Releases <bot> line in the PR description, which the squash commit carries onto main (the repo's squash-message setting is already PR_BODY). A new Gate job in deploy-production.yml binds the pushed commit to its merged PR and deploys a bot only if a reviewer other than the author approved the PR after the intent was added, judged at the merged_at snapshot. Refused bots get a ⚠️ comment and deploy nothing.

This closes the same gap prime-monorepo's per-app release-branch design (its PR #4865) targeted, without release branches: CI is the deployer here, so the gate runs in front of the deploy on reviewed main workflow code. Release branches would need a ruleset bypass actor, which cannot be GITHUB_TOKEN, so they need an App or deploy key; both were weighed and declined. Rationale: docs/decisions/TIB-2026-09-09-release-intent-gated-deploys.md.

Changes

  • packages/ci-scripts (new, tsx, no build): Releases <bot> parser on prime's golden fixture (HTML comments stripped first), PR body edit-history replay (fails closed at GitHub's 100-revision retention cap), pure gate, and manifest.json as the single list of CI-deployable bots (replaces six hardcoded enumerations). 59 unit tests.
  • deploy-production.yml: GateDeploy matrix (job-named <bot>). Production workflow_dispatch removed (would bypass the gate); the deploy-production concurrency group removed (PR-number tags need no same-day counter; GitHub drops pending runs per group).
  • deploy-bot.yml: takes package / github_environment / release_pr / publish_image from the manifest, absorbs deploy-quoter-bot-production.yml (Railway CLI 5.30.4, 30-minute timeout), and chains Release (<bot>-<PR#>, only after a successful deploy, idempotent on rerun), Notify, and Image (Docker Hub, for bots the manifest marks publishImage). The image lives in the per-bot chain so a sibling bot's failed deploy cannot skip it.
  • release-slack-notify.yml gains workflow_call. It had never run: GitHub fires no release event for releases created with GITHUB_TOKEN.
  • deploy-staging.yml: manifest-driven matrix.
  • pr-release-label-sync.yml (new): derives cosmetic release-<bot> labels from the body and restores hand-removed ones; nothing reads them.
  • publish-quoter-bot-npm.yml: job-name proof accepts quoter-bot / Release and the old Release-quoter-bot for historical runs. npm versions still come from package.json.
  • Docs: TIB-2026-09-09, addendum on TIB-2026-07-15, INDEX, READMEs, and the two .claude/commands/ci-*.md (stale packages/{bot} paths and version-bump assumption).

Findings recorded in the TIB

  • No merge queue is configured and no status checks are required on main, though checks.yml, deploy-staging.yml, and TIB-2026-07-15 assume a queue.
  • The release Slack chain has been dead since it shipped (see above).

Independent review

Codex (gpt-5.6-sol, high) reviewed the implementation. Incorporated: hidden-HTML-comment intent, the 100-edit history cap, the aggregate-matrix dependency that could skip the quoter image, and label re-sync on labeled/unlabeled. Deferred: same-bot rapid releases can be dropped or reordered by the per-bot concurrency group (pre-existing, not a regression) → BOTS-128.

Verification

  • pnpm --filter @repo/ci-scripts run typecheck, pnpm -r run typecheck (0 errors after the contracts build), pnpm lint, pnpm format:check, pnpm knip: clean.
  • pnpm vitest run: all pass except the fork/e2e suites that require RPC_URL_8453 (environmental).
  • Every workflow file parses as YAML.

To verify on the first live run (cannot be tested locally)

  • The rendered job name for the quoter-bot release is quoter-bot / Release (the npm publisher's proof depends on it; the old name is still accepted).
  • The staging matrix caller's contents: write / id-token: write grants are needed for the callee's skipped Release / Image jobs to validate; if GitHub accepts a narrower grant, tighten it.
  • First Slack release post ever.

Rollout: no admin action needed. Environment branch policies stay main. First release after merge: a PR whose body says Releases <bot>, approved after that line is present.

🤖 Generated with Claude Code

haydenshively and others added 2 commits September 9, 2026 20:41
Release intent moves from `release-<bot>` labels to a `Releases <bot>`
line in the PR description, frozen into the squash commit. A new Gate
job in deploy-production.yml binds the pushed commit to its merged PR
and deploys a bot only if a reviewer other than the author approved the
PR after the intent was added, judged at the merged_at snapshot. Refused
bots get a comment and deploy nothing.

- packages/ci-scripts: parser (prime-monorepo's golden fixture), body
  edit-history replay, pure gate, and manifest.json as the single list
  of CI-deployable bots
- deploy-bot.yml takes package/environment from the manifest, absorbs
  the quoter-bot variant, and chains Release (<bot>-<PR#>) and the
  Slack notify after a successful deploy
- release-slack-notify.yml gains workflow_call: releases created with
  GITHUB_TOKEN never fired its release trigger, so it had never run
- production workflow_dispatch and the deploy-production concurrency
  group are removed; labels are synced from the body as a visual cue
- publish-quoter-bot-npm.yml accepts the new job name and the old one
  for historical runs

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- Drop HTML comments before parsing intent: GitHub does not render them,
  so a hidden `Releases <bot>` would never be seen by the approver
- Refuse every intent on a PR whose body has 100+ edits: GitHub evicts
  older revisions, so a removal/re-add that should reset the intent
  clock can no longer be proven absent
- Move the Docker Hub image publish into deploy-bot.yml behind a
  manifest `publishImage` flag, so a sibling bot's failed deploy in a
  multi-bot release cannot skip quoter-bot's image
- Re-sync labels on labeled/unlabeled so a hand-removed release label is
  restored to match the description

Same-bot rapid-release ordering (pre-existing per-bot concurrency
behavior) is deferred as BOTS-128.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Sep 10, 2026

Copy link
Copy Markdown

BOTS-128

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@haydenshively
haydenshively added this pull request to stack #212 September 10, 2026 03:38
@haydenshively haydenshively self-assigned this Sep 10, 2026
@haydenshively
haydenshively marked this pull request as ready for review September 10, 2026 03:58
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 10, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-10T04:17:32.050722Z 0147667 Draft marked ready
🔒 Security Review Completed 2026-09-10T04:23:32.780554Z 0147667 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Devin Review

Comment thread packages/ci-scripts/src/release-intent/common.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0147667a89

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/ci-scripts/src/release-intent/common.ts
Comment thread packages/ci-scripts/src/release-intent/helpers.ts Outdated
Comment thread .github/workflows/deploy-bot.yml Outdated
- Fail the deploy when the manifest package matches no workspace member
  (`--fail-if-no-match`), so a stale name cannot tag a deploy that never
  ran
- Drop an unterminated `<!--` through end of text, which GitHub renders
  as a comment, so hidden intent cannot ride a later approval
- Verify on every run that the newest body revision equals the current
  body: `userContentEdits.diff` is a full snapshot today (checked live
  on PR #210), and the gate now refuses rather than misdates if that
  ever changes

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants