The short version
At a glance
Recommended outcome
Retire beta as a long-lived development branch. All feature, fix, hotfix, and infrastructure PRs target main. “Beta” remains a separately configured hosted environment and release channel, not a second source tree.
Why this is the right moment
The current refs show beta is the real integration stream: beta is 978 commits ahead of main, while main is 23 commits ahead of the merge base. The branch model has inverted in practice, so the release machinery should be inverted deliberately too.
Decision needed before implementation
Approve the semantic change: beta is a release channel, not a branch. The recommended release authority is the immutable Git tag and GitHub Release. The package version should stop deciding which branch may receive a PR; the release workflow should validate the requested version and inject release metadata into the build.
This preserves hosted beta testing, local-only experimental exposure, production promotion, and hotfix releases while removing merge-forward, porting, and beta-bump automation.
Included
- One-time main cutover from the current beta code line.
- PR routing, rulesets, changelog, version, and feature-exposure policy changes.
- Manual beta release, main staging, beta promotion, and hotfix release workflows.
- Release-event deployment contracts, environment approvals, rollback, and runbooks.
- Reuse and separation of the existing staging VPS paths.
Not included
- New product features or a redesign of GFC’s four application modes.
- Automatic release cadence, scheduled production deployments, or direct pushes to main.
- Turning every local experimental feature into hosted beta exposure.
- Deleting the hosted beta domain or its distinct credentials and telemetry.
What the repository says today
Current-state evidence
| Observed area | Evidence | Architectural implication |
| Branch reality | main is 1.6.30; beta is 1.7.0-beta.101. The refs differ by 23 main-only versus 978 beta-only commits. | Beta is already the integration trunk in practice. A simple branch rename would discard main-only production history unless the cutover merges both lines. |
| Release automation | post-merge-automation.yml bumps versions, pushes branches, and creates releases after PR merges. | Release creation is coupled to branch merges and PAT-backed writes. Replace it with explicit, idempotent manual release commands. |
| Beta routing | pr-porting.yml, beta changelog validation, and feature/* → beta policy maintain the second branch. | Remove porting and beta-only PR governance. Keep feature exposure as a product decision, not a branch decision. |
| Deployment | Beta deploys from prereleases or beta HEAD; production deploys from stable releases or main HEAD. Production already has a stage path. | Keep release events as deploy triggers, but make every release originate from a selected main commit and make staging independently dispatchable. |
| Governance | The active “Main Ruleset” covers the default branch and refs/heads/beta; legacy branch-protection API objects are absent. Required check names also need reconciliation with today’s CI matrix. | Make main the explicit protected surface, remove beta from the ruleset, and verify exact required check contexts during cutover. |
Open repository evidence
The target shape
The release contract
1PR into main
CI, security, exposure policy, review, and changelog checks run against one integration branch.
2Manual release
An Action selects an immutable main ref and creates a beta or stable GitHub Release.
3Release event
The release tag is the artifact identity. It is never rebuilt from a moving branch.
4Deploy + verify
The environment-specific workflow deploys, records the release, and runs smoke checks.
Invariant: a stable production release is either the exact commit already tested in beta, or an explicitly tested hotfix commit from main. No workflow silently merges, bumps, or pushes a branch as a side effect of deployment.
01
Post-migration workflows
Manual control, automatic traceability
Four paths, one source
A
Normal development → main
All feature/*, fix/*, infrastructure, and ordinary refactor branches open PRs to main. Hotfix branches also target main, with an explicit hotfix label and staging expectation.
- CI runs on every PR and main push; no CI push path remains for beta.
- Required checks are named from the actual matrix and verified in the repository ruleset.
- Feature exposure can be local-only, beta-enabled, staging-enabled, or production-enabled independently of branch routing.
Keep + simplify
B
Manual beta release → beta deploy
Create Beta Release is a workflow_dispatch workflow. It accepts a main ref, a prerelease version, and release notes source; it validates that the ref is reachable from main and creates vX.Y.Z-beta.N on that exact commit.
- The release event triggers
Deploy Beta; the deploy workflow does not create releases. - Beta keeps its domain, beta build target, invite gate, server target, Sentry environment, and separate telemetry.
- Repeated runs are idempotent: an existing tag fails safely unless an explicit replacement procedure is used.
New workflow
C
Manual main staging → staging environment
Deploy Main Staging accepts a commit, branch, or beta tag for a controlled preview. It deploys to staging-gfc.weatherboysuper.com without creating a GitHub Release or touching production.
- Use it for hotfix branches before their PR is merged and for production-candidate verification.
- Use isolated staging Firebase, Stripe/webhook, analytics, and admin credentials; never test billing against production.
- Record the tested ref, build target, server target, and smoke result in the workflow summary.
New workflow
D
Manual beta promotion → stable production release
Promote Beta to Production accepts an already deployed beta tag and a stable version. It verifies the beta release, checks that staging or beta smoke evidence exists, and creates stable vX.Y.Z on the same commit.
- The stable release event triggers
Deploy Production with a protected production environment approval. - Promotion never merges beta into main because beta is no longer a branch; main already contains the source commit.
- The deploy records the GitHub release, commit SHA, app version, Sentry release, and VPS release directory.
New workflow
E
Manual hotfix release → stable production release
After a hotfix/* PR is tested in staging and merged into main, Create Hotfix Release accepts the merged main SHA and patch version. It creates a stable release; that release triggers the same production deploy workflow.
- No automatic patch bump commit is pushed to main after merge.
- The workflow validates the release is newer than the currently live stable release and is based on main.
- Rollback is by selecting a previously known-good stable release and running the documented recovery workflow.
New workflow
Implementation inventory
What changes in the repository
| Area | Target action | Result |
| Branch policy | Rewrite scripts/lib/branch-policy.mjs and tests for main-only PR routing. Remove beta promotion and port classifications. | One PR catch-all; no branch-based release semantics. |
| Release metadata | Add a small release library and tests for tag/channel/version/ref validation. Make tag + release the authority; pass release metadata into builds and deployment manifests. | Stable and beta releases can share a commit without mutating package.json through post-merge bots. |
| Actions | Add manual create-beta, deploy-staging, promote-beta, and create-hotfix workflows. Keep deploy-beta and deploy-production release-triggered. | Every production change has a deliberate human click and a traceable release event. |
| Retirement | Remove or archive beta porting, post-merge branch writers, beta bumping, beta-to-main templates, and beta changelog machinery after the cutover release. | No hidden workflow can resurrect the two-branch model. |
| Feature exposure | Retain the target matrix, but replace “beta enablement” as a branch-merge concept with a reviewed exposure change on main. Keep local-only flags local. | Hosted beta stays useful without requiring an unstable branch. |
| Governance | Change the active ruleset to explicitly cover main only, align required check names with current CI, and require review/thread resolution/environment approval where appropriate. | Main is the enforced source of truth; releases are protected by workflow permissions and environments. |
The gap that needs a decision
Version and changelog model
Recommended authority
Use the GitHub Release tag as the release identity: v1.7.0-beta.1 for beta and v1.7.0 for stable. The release workflow validates the requested version against the latest stable release, the selected source ref, and the channel. Build and deploy metadata comes from the tag, not from whether a branch happens to contain -beta.N.
What this removes
Remove “main must be stable / beta must be prerelease” as a PR routing rule, automatic package bumps after merges, and the need to synchronize two changelogs. Keep one CHANGELOG.md with an unreleased section; a release workflow turns the selected notes into release notes.
Migration caution: the current app and deployment scripts compare tags to package.json. That validation must be deliberately replaced or retained as a generated release-metadata check; it cannot simply be deleted, or releases will lose version traceability.
02
Migration plan
Do this in order
Cutover sequence
7 gates
1
Freeze and snapshot the current system
Announce a short migration freeze for beta-targeting PRs and release automation. Record the current main and beta SHAs, latest beta release, latest stable release, live VPS release directories, current feature-exposure matrix, and open PRs.
- Do not delete or force-move beta yet.
- Mark existing beta PRs as “finish before cutover” or “retarget to main after cutover.”
- Capture a rollback reference for both hosted environments.
Gate 1
2
Build the canonical-main cutover ref
Create a temporary migration branch that preserves the current beta application state and merges the 23 main-only commits. Resolve production-only files, changelog/version conflicts, deployment manifests, and any feature-exposure differences as an intentional review.
- Do not use a destructive reset or silently discard main history.
- Run the full beta validation suite plus the production build and server tests.
- Produce a written “what changed at cutover” report attached to the migration PR.
Gate 2
3
Make main the enforced integration surface
Merge the cutover through the repository’s approved administrative path, then update the default-branch ruleset. Main should be the only branch named in branch, CI, release, and required-check policy.
- Keep beta read-only during the verification window.
- Update the default branch and ruleset conditions; remove
refs/heads/beta. - Verify actual required check contexts against the current CI matrix, including CodeQL and coverage.
Human approval
4
Install the new release machinery
Land the release metadata library, tests, manual workflow files, environment protections, and the release-triggered deploy guards. Keep the old workflows disabled or renamed until the new dry run passes.
- Require explicit source ref, channel, version, confirmation, and concurrency keys.
- Make workflows fail closed on non-main refs, duplicate tags, mismatched channels, or missing release evidence.
- Remove PAT-backed branch pushes from normal release paths.
Gate 4
5
Separate and verify main staging
Audit the existing staging nginx root, TLS, DNS, PM2 process, analytics port, Firebase project, Stripe/webhook secrets, Sentry environment, and data retention. Convert the path into a ref-selectable staging deployment rather than a side effect of production’s timed rollout.
- Use staging-only credentials and make destructive/test billing behavior impossible.
- Verify login, forecast save/load, monitor APIs, feature gates, error reporting, and rollback on staging.
- Document who may access staging and how its data is reset or retained.
Gate 5
6
Run a release rehearsal
From main, manually create a beta release, confirm the prerelease event deploys the exact tag to beta, run the beta smoke suite, promote the same tag to a stable release in a controlled window, and verify production deploy traceability.
- Use a rehearsal version or a documented real release decision; do not create an untracked tag.
- Exercise duplicate dispatch, cancelled run, failed SSH, and rollback behavior.
- Confirm no workflow pushes main, beta, or a version bump commit.
Gate 6
7
Retire beta as a branch
After the rehearsal and first successful main-first release, close or retarget remaining beta PRs, archive branch-specific documentation, remove the branch from the ruleset, and delete the remote beta branch only when the rollback references and release tags are verified.
- Keep the beta domain and beta environment names; remove only source-control ambiguity.
- Update contributor documentation so new work starts from main.
- Publish the final migration note with the cutover SHA, first main-first beta tag, and first stable promotion tag.
Gate 7
Reasoning
Decisions and assumptions
Decision: beta is a channel, not a branch
This is the central simplification. It preserves the beta audience and server boundary while eliminating branch drift, port PRs, beta changelog entries, and promotion merges.
Assumption: release tags are immutable
Once a beta or stable release is published, its tag points to one commit and is never moved. Corrections create a new prerelease or patch release.
Decision: no normal Action pushes to protected branches
Manual Actions create releases and deployment artifacts. They do not merge, bump, or synchronize branches. This makes main reviewable and makes the release audit trail legible.
Assumption: beta exposure remains a product gate
The exposure registry may keep local, beta, staging, and production targets. A feature not enabled for hosted beta can still be tested locally; its enablement is a reviewed main PR, not a beta-branch merge.
Recommendation: staging is production-like but isolated
Staging should exercise production routes and build behavior with separate data, credentials, analytics, and billing integrations. The current staging build’s beta gate can be retained temporarily, but it should not be the long-term definition of main staging.
What could change the plan
Risks and dependencies
Risks
- Divergent cutover: a 978-commit difference can hide production-only fixes or dependency changes. Gate it with a merge report and full validation.
- Accidental release: a manual workflow can still be clicked incorrectly. Use typed confirmation, protected environments, version/ref checks, and concurrency.
- Staging contamination: shared Firebase, Stripe, or analytics credentials can turn testing into a production incident. Treat isolation as a launch blocker.
- Feature exposure drift: moving code to main must not implicitly enable beta-only or production-disabled workstreams.
- Governance drift: stale required-check names can leave main either blocked or under-protected.
Dependencies
- GitHub repository admin access for the one-time cutover and ruleset edit.
- Protected GitHub environments for beta, staging, and production approvals.
- VPS access to verify nginx, symlinks, PM2, release directories, DNS, and TLS.
- Separate staging credentials and a documented test-data policy.
- A confirmed versioning convention and release-note ownership.
Proof of completion
Verification gates
| Checkpoint | Evidence required | Status |
| Main is the only PR integration target | Branch-policy tests; no open beta-targeting PRs; new sample PR routes to main. | Planned |
| Ruleset is correct | Ruleset explicitly includes main, excludes beta, requires current CI contexts, and rejects direct branch deletion/push according to policy. | Planned |
| Beta release is reproducible | Manual dispatch creates one immutable prerelease tag from main; release event deploys that exact SHA to beta; workflow summary records the SHA. | Planned |
| Staging is useful for hotfixes | Hotfix ref deploys to staging with isolated credentials; smoke suite and server-backed feature checks pass; no production state changes. | Planned |
| Promotion is exact | Stable release tag points to the already tested beta commit; production deploy is release-triggered and protected by environment approval. | Planned |
| Hotfix is safe | Hotfix PR is tested in staging, merged to main, manually released, deployed, smoke-tested, and rollback-tested. | Planned |
| Old system is gone | No post-merge branch writers, beta porting, beta bump, or beta-to-main promotion remains in active workflow paths; beta branch is retired only after rollback proof. | Planned |
What I want the maintainer to critique
- 1Does “beta as a channel, not a branch” match how you want to work day to day?
- 2Should the first main-first beta release promote automatically only after a human approval, or should promotion always be a separate manual click?
- 3Is the proposed tag-authoritative version model acceptable, or should a release-preparation PR still update
package.json before the manual release?
- 4Can the current VPS provide genuinely isolated staging Firebase, Stripe, analytics, and admin paths?
- 5Which existing beta-only release or feature should be the rehearsal case for the migration?