Skip to content

feat: auto-merge the mechanical release PR and show its timeline - #821

Merged
HugoXK merged 3 commits into
mainfrom
ops/819-release-auto-merge
Nov 15, 2026
Merged

HugoXK merged 3 commits into
mainfrom
ops/819-release-auto-merge

Conversation

@HugoXK

@HugoXK HugoXK commented Nov 15, 2026 •

Copy link
Copy Markdown
Owner

Written-by: HugoXK

Fixes #819

risk-tier: T3; basis: .github/workflows/daily-release.yml

Stacked on #809. This branch starts from #809's admin-card commit; until #809 merges, the PR diff also shows that commit. Merge #809 first, then this PR shows only the auto-merge/timeline commit.

What changed

  • .github/workflows/daily-release.yml
    • the open step now records the PR number (pr=) in its outputs;
    • a new step enables auto-merge on opened/reused mechanical release PRs with the same RELEASE_OPEN_TOKEN: gh pr merge "$PR" --auto --squash --match-head-commit "$PUSHED";
    • an already-enabled auto-merge is a no-op; a failure to enable it fails the night and leaves the PR for a human rather than reporting green;
    • --admin remains forbidden.
  • src/lib/releaseStatus.ts and src/views/pages.ts: the release snapshot parses plan.plannedAt, pull.mergedAt and release.publishedAt; the Release & deployment card renders a compact plan → merge → publish timeline.
  • test/dailyRelease.test.ts: the structural test allows the one pinned auto-merge command and forbids gh pr create / gh release / --admin; the auto-merge step must target opened/reused.
  • test/releaseStatus.test.ts: a snapshot with plan/merge/publish timestamps renders the timeline.
  • docs/architecture.md and changelog.d/819-release-auto-merge.changed.md.

Why

v0.3.6 had every check green at 19:26:43Z but waited for a manual merge at 20:17:17Z; the Release published at 20:19:38Z. The mechanical release PR is already T0, review-exempt and constrained to the mechanical diff, so it can opt into GitHub auto-merge and let the required checks decide. Auto-merge removes the human click; it does not fix GitHub's late schedule delivery, which remains #820.

Safety

The repo already has allow_auto_merge, squash-only merging, and required ci-ok / review-gate / pr-lint. The mechanical identity (release/vX.Y.Z, release: vX.Y.Z, allowed diff shape) is enforced by risk-gate, pr-lint and review-gate. Auto-merge adds no admin bypass and no new required review. --match-head-commit pins the branch tip.

Ran:

  • npm ci
  • npx tsx --test test/dailyRelease.test.ts test/releaseStatus.test.ts test/admin.test.ts — 78/78 pass
  • npm run check at 161a26f — lint 0, typecheck clean, 1989/1989 tests, build clean
  • node scripts/version.mjs check — 0.3.6 ok
  • node scripts/changelog.mjs check — 42 fragments ok
  • node scripts/risk-gate.mjs --base origin/main — risk-tier: T3; basis: .github/workflows/daily-release.yml

Not run:

  • No real release auto-merge; it needs the next mechanical release PR.
  • No production deploy.

Risk and rollback

Risk: if RELEASE_OPEN_TOKEN is compromised, an attacker could open a mechanical release PR and auto-merge it, but the allowed diff cannot change source code; the damage is release/version/changelog pollution. The token is scoped to this repository, and ci-ok / review-gate / pr-lint still run. Rollback: remove the auto-merge step; the PR returns to manual merge.

Docs

docs/architecture.md records the auto-merge opt-in and the plan → merge → publish timeline.


Published tip (2026-11-15): 9a44b29426c7070ee1b416c3dbd10f5602de0a57, tree c3bb5a2c88806db00249476fd144929e8ccc4d31, parents 161a26f3 + 7d1e3ef1, 7 files +82/−9. Both conflicts are paragraphs added at the same anchor: docs/architecture.md keeps both sides, src/views/pages.ts keeps this branch's additions. public/app.css, test/sectionNav.test.ts and changelog.d/808-admin-unify.changed.md are no-ops after the merge because #808 already made those edits.

@HugoXK-bot HugoXK-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict: PASS at 161a26f
Written-by: HugoXK-bot
Posted-by: HugoXK-bot

The step does what the release routine needs and nothing more: it opts the mechanical release PR into auto-merge and lets the required checks decide, and it cannot merge anything it did not open.

  1. Gating. .github/workflows/daily-release.yml:411-417 runs only when inputs.dry_run != true and the opener state is opened or reused; a refused opener exits earlier (:408), so a refused run cannot reach this step.
  2. Head pinning. PUSHED comes from the push step, which sets it to the remote head on both paths including a reused branch, so the emptiness guard at :421-424 cannot fire on the reuse path with a stale value. :430 passes --match-head-commit "$PUSHED", so a force-push between the push and the merge aborts instead of merging a different tree.
  3. Repository capability. Read from the API this turn: allow_auto_merge=true, allow_squash_merge=true, allow_merge_commit=false, so --auto --squash matches what the repository will accept; the merge still waits for ci-ok, review-gate and pr-lint, all of which are required on main.
  4. No bypass. There is no --admin in the workflow, and the test asserts the exact command line and its absence.

Non-blocking, for a later tidy-up: when releaseStatus exists but compareRunning returns null the card renders an empty detail with stateBadge('unknown') (same shape as #809), and #780's dead duplicate branch is still there; neither belongs to this PR.

Ran at 161a26f:

  • npm run check — pass, 1989/1989 tests, typecheck and build included
  • node scripts/risk-gate.mjs --base cc305a6030ae11f6de4766ad19e36934112a7020 --head HEAD — risk-tier: T3; basis: .github/workflows/daily-release.yml
  • node scripts/pr-lint.mjs --pr 821 — pr-lint: ok (#821, T3, 10 files)
  • node scripts/changelog.mjs check — 42 fragments ok
  • node scripts/version.mjs check — 0.3.6 ok
  • node scripts/secret-scan.mjs — no suspected value

Not run: a live release dispatch, GitHub's auto-merge queue itself (that needs a real release PR).

Conclusion: safe to enqueue. The step only enables auto-merge on the PR the routine just opened or refreshed, pins the head sha, and leaves every required check as the actual gate.

@HugoXK-bot HugoXK-bot added the review:approved Issue reviewed and approved by the Fledge Platform Reviewer label Nov 15, 2026
Merge live main. Both conflicts are paragraphs added at the same anchor, so
both sides are kept in docs/architecture.md, and src/views/pages.ts keeps the
pull request's additions.

Written-by: Fledge Platform Dev-1

@HugoXK-bot HugoXK-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict: PASS at 9a44b29
Written-by: HugoXK-bot
Posted-by: HugoXK-bot

Read the tip 9a44b29426c7070ee1b416c3dbd10f5602de0a57 (7 files +82/−9, T3; .github/workflows/daily-release.yml blob 72bd5fe6 matches the contents API). This supersedes my PASS at 161a26f3 only because the forward merge moved the tip; the change I read there is the change I read here.

Findings, none blocking:

  • .github/workflows/daily-release.yml:418-438 — auto-merge is gated on steps.open.outputs.state being opened or reused (:422), refuses when PR or PUSHED is empty (:429-432), and pins the tip with --match-head-commit "$PUSHED" (:437). No --admin, and the token is the same RELEASE_OPEN_TOKEN that opened the PR.
  • .github/workflows/daily-release.yml:378-391 with scripts/release-open.mjs:269,365 — pr is written by the opener for both the reused and opened results, and the open step only runs when steps.push.outputs.pushed is non-empty, so the two values the new step requires cannot both be absent on an allowed path.
  • .github/workflows/daily-release.yml:437 with test/dailyRelease.test.ts:164-175 — the structural test keeps gh pr create/gh release/git tag forbidden, allows exactly one git push (the release branch), and now asserts the auto-merge line and the absence of --admin.
  • src/lib/releaseStatus.ts:33-36,94-99 — the three timestamps are optional and read only when they are strings, so an older snapshot still parses; src/views/pages.ts:2488-2495,2509 renders only the parts that exist and keeps them behind one data-release-timeline line.
  • Enabling auto-merge does not bypass anything: GitHub still waits for ci-ok, review-gate and pr-lint at the pinned head.

Ran (exact tip tree; blob hash above verified):

  • npm run check — lint 11 baseline warnings, typecheck clean, 2183/2183 tests, build ok
  • node scripts/pr-lint.mjs --pr 821 — ok (#821, T3, 7 files)
  • node scripts/changelog.mjs check — 63 fragments ok
  • node scripts/version.mjs check — 0.3.6 ok
  • node scripts/secret-scan.mjs — no suspected value in the tracked files

Not run: a real nightly run against GitHub (the workflow cannot be exercised here), Docker, production.

CI at this tip: everything green except review-gate failure 12:21:37Z, which is the pre-verdict state this review replaces.

@HugoXK
HugoXK merged commit 9f2ef3a into main Nov 15, 2026
34 of 35 checks passed
@HugoXK
HugoXK deleted the ops/819-release-auto-merge branch November 15, 2026 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review:approved Issue reviewed and approved by the Fledge Platform Reviewer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: auto-merge the mechanical release PR and show the release timeline

2 participants