0001. Two-branch model with tag-driven releases
Context and problem statement
HAI-Co² has two deploy targets: a dev environment (always tracking the latest integration) and a production environment (only the curated, stable release). The flow from a contributor’s first PR to a running production build needs to be obvious to outsiders, reproducible (so any tagged version can be rebuilt and redeployed), and machine-enforceable in CI. GitHub Flow (one default branch + ephemeral feature branches) is the popular default, but it conflates integration with release: every push to the main branch is potentially a release. GitFlow, conversely, prescribes five branch types and is heavier than this project needs.Decision drivers
- Single tag → reproducible image → exact deploy. No “rebuild on demand.”
- A reviewer should be able to look at one diff (
developvs.main) and see what is about to ship to production. - The CI workflow must be one file, not a fork-per-environment.
- Public contributors must be able to land changes without coordinating with a release manager.
Considered options
- GitHub Flow: single
main, ephemeral branches, deploy frommain. - GitFlow:
develop+main+release/*+hotfix/*+feature/*. - Trunk-based with environment tags only: single
main, deploy by tag. - Two-branch model:
develop+main, tag-driven releases. PRs land ondevelop; promotion tomainis a fast-forward +vX.Y.Ztag.
Decision outcome
Chosen option: two-branch model with tag-driven releases, because it preserves a clear “what is about to ship” diff (develop vs. main) without the ceremony of GitFlow’s full branch set.
Tag scheme:
The single
CI workflow drives both. Tag names must be strict SemVer; anything else is rejected by resolve-release.
Positive consequences
- One PR = one diff against
develop. Predictable. mainis always exactly what is in production.git log mainis the release history.- Tags are immutable; production deploys are byte-equivalent to dev deploys for the same SHA.
- Public contributors only need to know one rule: target
develop.
Negative consequences
- Two long-lived branches require an explicit promotion step (the
develop → mainfast-forward + tag push). - When the repo is private (free tier), the GitHub merge-button cannot enforce Require linear history, so the promotion has to be done locally. This is documented in CONTRIBUTING.md and disappears once the repo is public.
Pros and cons of the options
GitHub Flow
- + Minimal ceremony; what most OSS projects use.
- − No staging surface; every push is a release candidate.
GitFlow
- + Explicit release / hotfix branches.
- − Five branch types is overkill for a two-environment project.
Trunk-based + environment tags
- + No long-lived branches.
- − No “what is about to ship” diff; harder to triage what is in prod.
Two-branch model (chosen)
- + Predictable; documented in one short table; aligns with how dev and prod are actually deployed.
- − Requires the promotion fast-forward to stay linear.
Links
CONTRIBUTING.md, branching and release sections.README.md, “Branches, tags & releases” table..github/workflows/ci.yml, theresolve-releasejob that enforces this scheme.docs/going-public.md, when the GitHub branch protection takes over from the local convention.