You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
♻️ This top comment is evergreen, and will evolve to match the current state of this effort.
Overview
Streamlined branching strategy and release automation for the schema repository. Replaces the old dev -> staging -> main manual flow and per-release long-lived branches (e.g. main_2026_02_12) with a two-branch model, automated CI safety nets, and PR-driven releases.
main: integration branch. All normal work lands here. Always releasable.
vnext: accumulates major changes between releases. Continuously rebased onto main by CI. Merges to main at release time (regular merge, not squash).
Versioning model (settled in Phase 2.B)
Humans own the full <major>.<minor>.<patch> per package in pyproject.toml. Any bump merged to main cuts a per-package GitHub Release (tag <pkg>-v<version>) and, in Phase 3, a public PyPI publish.
Every PR that changes a package carries a towncrier changelog fragment, enforced by CI; release PRs fold fragments into CHANGELOG.md.
No-bump merges produce internal-only builds versioned <version>.postN+main.<sha> (or +vnext.<sha>) for CodeArtifact. >= consumers resolve them; public PyPI rejects them by construction.
V2 phase details (superseded by the phase issues, kept for history)
Phase 2, Versioning & release-trigger logic
2.A: Establishes per-package version scheme (<major>.<minor> git-controlled, <patch> CI-computed from CodeArtifact) and the compute-version composite action.
2.B: Implements release-event detection and the vnext->main release process. Requires decisions on changelog approach and patch destination (CA only vs PyPI).
Superseded: 2.B settled on human-owned full semver (patch included), towncrier fragments, per-package releases, and .postN internal builds. See #533 for the resolved decisions.
Phase 3
Implements build + publish automation. Destination depends on context: CA dev (vnext), CA (main patches), PyPI (major/minor bumps). Patch->PyPI decision is TBD and may affect this scope.
Superseded: see the current trigger x destination matrix in #509.
Phase 4
Originally an editorial pass on docs/branching-strategy.md + docs/versioning.md: diagrams, walkthroughs, FAQ, no new procedures.
Note: branch mechanics live in CONTRIBUTING.md; there is no docs/branching-strategy.md.
Superseded: opted out as a discrete docs phase, that work landed alongside Phase 3's workflows instead (PR #638). The number was reused for vnext dev builds to CodeArtifact once split out of Phase 3 (blocked on ops-team#299); see #510 for the current scope.
Branching strategy V1 (superseded by V2)
[!NOTE]
Branch names used below were chosen for clarity, but can be almost anything. Assume the requisite branch protections/settings will be configured to make all of this feasible.
This is a draft suggestion of a simpler, more automatable branching flow for the schema repository. Consider this as a starting point for changes, please call out concerns/questions.
Two branches
We maintain two long-lived branches:
main, the integration branch. This is where all normal work lands. It is always in a green, releasable state, but it is not a snapshot of the last release - it tracks what the next release will look like.
breaking, a parallel branch where breaking changes accumulate between releases. It stays continuously in sync with main so it never drifts. This is opposed to the current approach main_2026_02_12, where its a long-lived breaking branch per release.
graph LR
A[feature/*] -->|squash merge\n nonbreaking PR| B[main]
A -->|squash merge \nbreaking PR | C[breaking]
B -->|CI auto-rebase\n on every merge| C
C -->|merge at release\n + tag v1.x.x| B
Loading
How normal work flows
All regular work follows the same pattern:
Branch off main
Open a PR back to main
CI runs automatically and checks your changes are compatible with the breaking branch
If all checks passes and you are up-to-date with main, your PR is squash-merged, your branch history is yours to work however you like, but a single clean commit lands on main
Once merged, breaking is automatically rebased onto the new main
You never need to touch breaking directly.
flowchart TD
A[PR opens to main] --> B[CI: dry-run check\nsimulate squash onto main\ntest breaking rebase]
B --> C{conflict?}
C -->|no| D[Squash merge to main]
C -->|yes| E[Author rebases feature branch\nonto breaking,\npushes to own branch to pre-emptively resolve]
E -->|re-run check| B
D --> F[CI: auto-rebase breaking onto main force-push]
F --> G{failsafe check: rebase fails?}
G -->|yes| H[Open GitHub issue\nassign PR author]
G -->|no| I[Done]
Loading
If your PR fails the compatibility check
Occasionally a change conflicts with work that is staged in breaking. If this happens, CI will post a comment on your PR explaining exactly what to do. The short version: you rebase your own feature branch onto breaking to resolve the conflict, push your branch, and the check re-runs automatically.
You are only ever asked to fix your own branch.
If your PR fails compatibility check post-merge
While unlikely, this failsafe check in CI will automagically open a GitHub issue and assign it to you. Until resolved, the breaking branch will be out of sync with main.
Breaking changes
If your work contains breaking changes, target breaking instead of main when you open your PR. Everything else works the same way: squash merge, CI checks, and so on.
Monthly release
Once a month, the breaking branch is merged into main and the result is tagged as the release. There is no separate release branch, the tag on main is the release.
By shifting merge conflicts left in our process, this should always result in a clean, simple merge (making it easy to automate more of the core release steps in the future).
flowchart LR
A[breaking\n] -->|merge into main| B[main\nnow includes breaking changes]
B -->|tag v1.x.0| C[release\ntagged commit on main]
Loading
What this means for contributors
Always branch from main
Target main for normal changes, breaking for breaking changes
Use whatever commit style you like on your branch, only the final squashed commit lands on shared branches
If CI flags a conflict, follow the instructions in the comment, it will tell you exactly what to run
♻️ This top comment is evergreen, and will evolve to match the current state of this effort.
Overview
Streamlined branching strategy and release automation for the schema repository. Replaces the old
dev->staging->mainmanual flow and per-release long-lived branches (e.g.main_2026_02_12) with a two-branch model, automated CI safety nets, and PR-driven releases.Live documentation: CONTRIBUTING.md (branch mechanics) and docs/versioning.md (version scheme + release process).
Two-branch model
main: integration branch. All normal work lands here. Always releasable.vnext: accumulates major changes between releases. Continuously rebased ontomainby CI. Merges tomainat release time (regular merge, not squash).Versioning model (settled in Phase 2.B)
<major>.<minor>.<patch>per package inpyproject.toml. Any bump merged tomaincuts a per-package GitHub Release (tag<pkg>-v<version>) and, in Phase 3, a public PyPI publish.CHANGELOG.md.<version>.postN+main.<sha>(or+vnext.<sha>) for CodeArtifact.>=consumers resolve them; public PyPI rejects them by construction.Phases
main+ PyPI releases)vnextdev builds to CodeArtifactV2 phase details (superseded by the phase issues, kept for history)
Phase 2, Versioning & release-trigger logic
2.A: Establishes per-package version scheme (
<major>.<minor>git-controlled,<patch>CI-computed from CodeArtifact) and thecompute-versioncomposite action.2.B: Implements release-event detection and the
vnext->mainrelease process. Requires decisions on changelog approach and patch destination (CA only vs PyPI).Superseded: 2.B settled on human-owned full semver (patch included), towncrier fragments, per-package releases, and
.postNinternal builds. See #533 for the resolved decisions.Phase 3
Implements build + publish automation. Destination depends on context: CA dev (vnext), CA (main patches), PyPI (major/minor bumps). Patch->PyPI decision is TBD and may affect this scope.
Superseded: see the current trigger x destination matrix in #509.
Phase 4
Originally an editorial pass on
docs/branching-strategy.md+docs/versioning.md: diagrams, walkthroughs, FAQ, no new procedures.Note: branch mechanics live in
CONTRIBUTING.md; there is nodocs/branching-strategy.md.Superseded: opted out as a discrete docs phase, that work landed alongside Phase 3's workflows instead (PR #638). The number was reused for
vnextdev builds to CodeArtifact once split out of Phase 3 (blocked on ops-team#299); see #510 for the current scope.Branching strategy V1 (superseded by V2)
This is a draft suggestion of a simpler, more automatable branching flow for the schema repository. Consider this as a starting point for changes, please call out concerns/questions.
Two branches
We maintain two long-lived branches:
main, the integration branch. This is where all normal work lands. It is always in a green, releasable state, but it is not a snapshot of the last release - it tracks what the next release will look like.breaking, a parallel branch where breaking changes accumulate between releases. It stays continuously in sync withmainso it never drifts. This is opposed to the current approachmain_2026_02_12, where its a long-lived breaking branch per release.graph LR A[feature/*] -->|squash merge\n nonbreaking PR| B[main] A -->|squash merge \nbreaking PR | C[breaking] B -->|CI auto-rebase\n on every merge| C C -->|merge at release\n + tag v1.x.x| BHow normal work flows
All regular work follows the same pattern:
mainmainbreakingbranchmain, your PR is squash-merged, your branch history is yours to work however you like, but a single clean commit lands onmainbreakingis automatically rebased onto the newmainYou never need to touch
breakingdirectly.flowchart TD A[PR opens to main] --> B[CI: dry-run check\nsimulate squash onto main\ntest breaking rebase] B --> C{conflict?} C -->|no| D[Squash merge to main] C -->|yes| E[Author rebases feature branch\nonto breaking,\npushes to own branch to pre-emptively resolve] E -->|re-run check| B D --> F[CI: auto-rebase breaking onto main force-push] F --> G{failsafe check: rebase fails?} G -->|yes| H[Open GitHub issue\nassign PR author] G -->|no| I[Done]If your PR fails the compatibility check
Occasionally a change conflicts with work that is staged in
breaking. If this happens, CI will post a comment on your PR explaining exactly what to do. The short version: you rebase your own feature branch ontobreakingto resolve the conflict, push your branch, and the check re-runs automatically.You are only ever asked to fix your own branch.
If your PR fails compatibility check post-merge
While unlikely, this failsafe check in CI will automagically open a GitHub issue and assign it to you. Until resolved, the
breakingbranch will be out of sync withmain.Breaking changes
If your work contains breaking changes, target
breakinginstead ofmainwhen you open your PR. Everything else works the same way: squash merge, CI checks, and so on.Monthly release
Once a month, the
breakingbranch is merged intomainand the result is tagged as the release. There is no separate release branch, the tag onmainis the release.By shifting merge conflicts left in our process, this should always result in a clean, simple merge (making it easy to automate more of the core release steps in the future).
flowchart LR A[breaking\n] -->|merge into main| B[main\nnow includes breaking changes] B -->|tag v1.x.0| C[release\ntagged commit on main]What this means for contributors
mainmainfor normal changes,breakingfor breaking changesmainorbreaking