Skip to content

release: snapshot one-click rc cut — a cut-rc workflow that bumps, versions, gates and publishes from ONE SHA, ending the two-treadmill freeze dance #7447

Description

@os-zhuang

Maintainer ruling (2026-08-10, directed in session session_01BPWqbmEFU8gJepBJTHESXd): approved for immediate implementation and dispatch. Background: cutting rc.6 today required chasing objectui main across four pin-bump laps (8aad9fd9b9fa49cfeb3781b6188dbcd3e02, every lap overtaken) and would have needed ~40 min of coordinated freezes across two repos. The root causes are design, not discipline: (1) the release path checks pin freshness against a moving objectui HEAD; (2) the standing Version Packages PR (#6208) is force-refreshed on every main push, so its CI can never finish on a busy day, and the version→publish gap invites content/record skew.

The design (adopted)

New workflow .github/workflows/cut-rc.ymlrc prereleases only. One human dispatch, one snapshot, zero freezes:

  1. Trigger: workflow_dispatch ONLY, input version = the expected resulting version (e.g. 17.0.0-rc.6), environment: release, concurrency group cut-rc (no parallel cuts). This preserves the 2026-08-07 ruling verbatim: no push, bot, queue or schedule can synthesize the event; the human types the version.
  2. Snapshot: checkout objectstack main, record SNAPSHOT_SHA. Resolve objectui main HEAD once (git ls-remote), record OBJECTUI_SHA. Everything downstream uses these two values; both repos may keep moving freely.
  3. Pin bump: clone objectui (full enough for the digest walk — see traps below), run scripts/bump-objectui.sh $OBJECTUI_SHA with OBJECTUI_ROOT set, then pnpm objectui:build, playwright install chromium-headless-shell (allowed in CI — this is not a dispatch container), pnpm sdui:manifest (the ADR-0082 D4 ratchet; mandatory second half of every pin move, sdui.manifest.json 的来源未定:声明一致性 ratchet 目前只在手工 pnpm sdui:manifest 时跑,CI 里从来不跑(#4690 的遗留决定) #5960), pnpm check:console-sha.
  4. Pin self-consistency, NOT liveness: inside the run, assert .objectui-sha == $OBJECTUI_SHA (inline compare). ⛔ Do NOT call check:objectui-pin-fresh here and do NOT change that script's semantics — objectui moving during the run must not fail the cut. The Release process: prevent frontend (objectui) changesets being missed when the console pin lags #3340 invariant this preserves is "everything shipped is covered by the changeset record" (true by construction: the bump changeset covers OLD_PIN..$OBJECTUI_SHA); commits past the snapshot are simply next release's record.
  5. Pre-version gates (they read .changeset/*, so they run BEFORE versioning): check-adr-0087-registration, check-changeset-no-major, check-empty-changeset, check-nul-bytes.
  6. Version: pnpm run version (⚠️ the repo script — changeset version && sync-protocol-version.mjs && sync-template-versions.mjs — never raw changeset version). Pre mode (.changeset/pre.json, tag rc) is committed state and applies automatically. Then assert computed version (packages/cli/package.json) == the dispatch input; mismatch fails before anything irreversible.
  7. Land the version commit on main FIRST, publish it SECOND — preserving release workflow: publish pushes tags + npm but its version commit never reaches main — twice now (rc.3 c6a52d3, rc.4 a10cbc77); landing the commit must be part of the publish lane #6170's invariant ("the publish lane may only ever ship a commit that is ALREADY on main") to the letter:
    • Primary route: push the single squashed version commit (bump + version output; file surface is package.jsons / CHANGELOGs / .changeset/* / .objectui-sha — disjoint from lane PRs by construction) directly to main using a bypass credential, with a bounded fetch-rebase-retry loop for races. Admin prerequisite (maintainer, one-time): add a bypass actor for this push — either the Actions app on the ruleset bypass list scoped to this workflow, or a fine-grained PAT stored as RELEASE_PUSH_TOKEN for an account on the bypass list.
    • If bypass turns out impossible in this org: STOP and report needs_decision with the measured constraint — the fallback (publish from a release ref + catch-up PR) is the rc.3/rc.4 accident shape (release workflow: publish pushes tags + npm but its version commit never reaches main — twice now (rc.3 c6a52d3, rc.4 a10cbc77); landing the commit must be part of the publish lane #6170) and is NOT to be adopted silently.
  8. Publish: changeset publish + tag push from the just-landed main commit, NPM_TOKEN via the release environment — mirror release.yml's publish job for secrets/guard patterns (read its publish: job before writing yours; its dispatch guards are the house style).
  9. Docs: docs/releases-maintenance.md gains a "cutting an rc (snapshot flow)" section; the existing Version-PR flow is re-scoped as GA only (board-clearing, No producer files the final console bump card at the release-cut moment — the #6162 chore is keyed to a settled window, and the card it does file never reaches the release board #7275-A cut precondition, human changelog review all stay there). One cross-reference line, no edits to release.yml's lanes.

Measured traps the implementer inherits (all hit today, all with receipts in session)

  • bump-objectui.sh with no sha argument pins the LOCAL objectui checkout's HEAD — stale by construction (chore: pin the console to objectui main 8aad9fd50b16 (approvals inbox + bell-badge breakdown) #7308 note 1). Always pass the resolved sha.
  • The changeset digest exits 2 on an unwalkable shallow range; deepen the objectui clone until OLD_PIN is reachable (--depth=250 sufficed at ~70 commits; fetch-depth: 0 is simplest in CI).
  • A fresh workspace must build local dists before pnpm objectui:build: the console's vite alias table (objectui#4103) resolves @objectstack/client → local packages/client/dist, whose DTS needs @objectstack/core built. pnpm --filter "@objectstack/client..." build (dependency closure) is the fix; @objectstack/spec build is also required for generated-artifact reads.
  • pnpm sdui:manifest needs a Playwright chromium; in CI install it normally. (The scratchpad symlink workaround in docs/releases-maintenance.md is for agent dispatch containers only.)
  • Expected ratchet state today: 62 spec-only divergences == accepted baseline, 0 missing blocks — four consecutive laps confirmed no declaration drift from objectui 8aad9fd..bcd3e02.

Boundaries (⛔)

  • No second publish path reachable without workflow_dispatch + environment: release. The 2026-08-07 ruling and release.yml's lane-split comments are binding context — read them first.
  • release.yml's three existing lanes and check-objectui-pin-fresh.mjs semantics are OUT of scope — GA keeps them unchanged.
  • content/docs/releases/** untouched (standing rule).
  • Releases nothing → skip-changeset label on the PR, per the Check Changeset gate's route 2.

Acceptance

  • Workflow YAML passes actionlint/self-review; every guard failure path exits before changeset publish.
  • A dry-run mode (inputs.dry_run) that stops after step 6 and uploads the would-be version diff as an artifact — so the first real dispatch is not the first execution.
  • Docs section written; PR body walks the sequence and names the admin prerequisite for the maintainer.

Refs: #6170 / #3340 (invariants), #5960 (ratchet on-demand ruling), #7308 (previous bump lap + traps), #7275 (GA cut precondition, stays), #6162 (steward bump cards — unaffected; they keep feeding the advisory pin lag), #4898 (empty-changeset stall), 2026-08-07 manual-publish ruling (release.yml header).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions