Skip to content

chore(release): cycles run in phases by kind, cut on demand - #8489

Draft
kixelated wants to merge 5 commits into
mainfrom
quest/m3/release/cycle-phases
Draft

kixelated wants to merge 5 commits into
mainfrom
quest/m3/release/cycle-phases

Conversation

@kixelated

@kixelated kixelated commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

Finishes quest/m3/release/cycle-phases.md.

Problem

A cut waits for every breaking change, so it grows large (main was 3,190 commits ahead of release on 2026-11-09), and a deployment that follows release can only take all of it. The maintainer decided (2026-11-09) that after the next cut each cycle runs additive work, then API breaks, then wire changes, with main cut on demand in the first two phases and patch versions while the cycle is additive. Nothing in the repo described or enforced that.

Approach

  • CONTRIBUTING.md (Releases, Versions): the three phases, what an API break and a wire change are, how the maintainer opens a phase, and the two kinds of cut. An additive cut publishes patches and runs only the readiness checks. An API or wire cut advances the shared minor with just version, and also needs the migration audit and the API shape, feature, and Cargo feature reviews. The release-to-main merge now comes before every cut, not only breaking ones.
  • version.json gets a phase field (null, additive, api, or wire). just version rejects any other value. It stays null until the 0.22.0 cut, the same way minor does.
  • Enforcement: while phase is additive, the cut PR's API diff runs with --deny, and a new Nightly job runs just api-diff --deny on main. In any other phase that Nightly job skips before the nix setup. Backports still deny, and release's missing version.json counts as null.
  • Questline (quest/README.md, quest/m3/README.md): the phase definitions and the milestone layout after the next cut. m1 is additive work, m2 API breaks, m3 wire changes, m4 and m5 later work, and m6 the deferred bucket. The re-split at the next cut and the shift at each cycle-ending cut are both spelled out.
  • Cut recurrence (quest/m3/release/cut.md): the cut now recurs per cut. Each cut re-creates itself with no Required and a maintainer ## Waiting item naming the next cut's kind. A breaking cut first re-creates the three reviews and the migration audit as its Required. A wire cut requires m2 and m3, sets phase back to additive, and shifts the milestones. The cut gate moves to m3.
  • The review quests and the migration audit say they recur once per API or wire cut. This quest's file and its links are deleted.

Choices made without asking

  • Hang catalog JSON and stored or exported container formats are wire changes. A deployed peer parses them without rebuilding. A change to an unpublished -wip version also counts as wire work.
  • Enforcement runs nightly, not per PR. CI is moving expensive checks off main PRs (m0 CI cost), and cargo-semver-checks documents every crate twice. A daily signal catches an early break within a day for one run per day. I rejected just api-diff --deny in the merge queue (the job has a 120-minute timeout and would serialize the queue) and a check on the ! in PR titles (Check never blocks, and it misses unmarked breaks).
  • Wire ordering relies on planning alone. No cheap check tells a wire change from other work across the many drafts and both languages. Wire work waits in m3 until the maintainer opens the wire phase, and the wire cut finalizes the wire version.
  • JS packages and binding wrappers take a patch at an additive cut by a manual bump on main before the cut PR. That matches today's backports. Go derives its patch in CI, and the FFI packages follow their Rust crate through release-plz.
  • phase is edited by hand, not by just version. An API cut advances the minor but keeps the api phase, so the recipe can't infer the phase. The 0.22.0 cut and each wire cut set it to additive. The maintainer opens the api and wire phases with their own PRs.
  • Milestones move down one, adding m6. The quest said "today's later-work milestones moving down one". The next cut's re-split therefore keeps m4, empties m5, and moves today's m5 to m6.

Impact

  • Public API: none. Wire: none.
  • version.json gains "phase": null. sh/version.py now requires the key.
  • The published version numbers change shape after 0.22.0: fewer shared minors, more patches.
  • CI: Nightly gains an api-diff job that is a no-op until phase is additive.

Alternatives

  • Order the phases by Required between milestones. An epic's Required doesn't block its children in quest unblocked (checked on quest/m3/release/). Additive work is also perpetual, so m2 requiring m1 would never unblock.
  • Re-create the reviews every cycle in m2. With on-demand API cuts, a cycle can have several breaking cuts, and each one needs its own reviews.

Follow-ups

None recommended.

Notes

moq-relay config::tests::repository_configs_load fails on main until moq-dev/moq#8486 merges. It is unrelated, and this diff doesn't select it.

Checks

  • just check: passed.
  • python3 sh/version.test.py: 7 tests passed.
  • actionlint, zizmor, quest check, and quest check --base origin/main: passed.

Models

Size: M
Implementer: Claude Code, claude-opus-5-5, medium effort

(Written by Claude Opus 5.5 with medium)

Review

Rewritten by quest review after each round.

  • Reviewer: Codex, gpt-6.1-sol, medium effort
  • Session: 01a1c84a-dac2-7cf3-bf08-2e276bb44e32, round 2
  • Head: 33ea85a5e277, named in full once a round reports no findings
  • Base: origin/main at 114b14f
  • Score: 8/10, The API-enforcement contradiction is fixed, but the wire cut still needs its completion condition narrowed to avoid waiting on its own release epic.
  • Findings, open until the next round settles them:
    • Medium, quest/m3/release/cut.md:85: Exclude release execution from the wire cut's waiting condition
  • The last round's findings, at fa3c02d:
    • Fixed: medium, .github/workflows/api-diff.yml:38: Distinguish a breaking cut from its next-cycle phase; CONTRIBUTING.md's Releases section and quest/m3/release/cut.md:57–59,87–89 defer the additive phase reset to a main PR after the cut merges into release, preserving report-only API checks for breaking cuts.
    • Open: medium, quest/m3/release/cut.md:82: Keep the wire cut outside its prerequisite milestone; Although the explicit m3 prerequisite was removed, quest/m3/release/cut.md:85 still requires no other m3 quest to remain while its containing release epic and post-cut publication work remain there.

@kixelated kixelated changed the title A cycle runs in phases by kind, cut on demand chore(release): cycles run in phases by kind, cut on demand Nov 11, 2026
@kixelated

Copy link
Copy Markdown
Collaborator Author

Stopping this claim: the maintainer scoped this spawn to m0-m2, and this m3 quest was started by mistake. The work so far (the phase rewrite in fa3c02db9e) stays on this draft for the maintainer to pick up or close; nothing more is being pushed.

(Written by Claude Opus 5.5)

This branch has not been deployed

No deployments
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.

1 participant