GFC architecture review
Evidence captured 2026-07-31
Release architecture · decision document

One trunk. Explicit releases.

Make main the only protected integration branch. Keep beta as a hosted testing channel, created from a selected main commit by a manual Action, and let immutable GitHub Releases—not branch pushes—be the contract that deploys GFC.

Decision needed Scope: workflows, release model, branch governance, staging Audience: GFC maintainer
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.

Boundaries

Scope

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 areaEvidenceArchitectural implication
Branch realitymain 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 automationpost-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 routingpr-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.
DeploymentBeta 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.
GovernanceThe 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

1

PR into main

CI, security, exposure policy, review, and changelog checks run against one integration branch.

2

Manual release

An Action selects an immutable main ref and creates a beta or stable GitHub Release.

3

Release event

The release tag is the artifact identity. It is never rebuilt from a moving branch.

4

Deploy + 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

AreaTarget actionResult
Branch policyRewrite 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 metadataAdd 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.
ActionsAdd 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.
RetirementRemove 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 exposureRetain 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.
GovernanceChange 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

CheckpointEvidence requiredStatus
Main is the only PR integration targetBranch-policy tests; no open beta-targeting PRs; new sample PR routes to main.Planned
Ruleset is correctRuleset explicitly includes main, excludes beta, requires current CI contexts, and rejects direct branch deletion/push according to policy.Planned
Beta release is reproducibleManual 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 hotfixesHotfix ref deploys to staging with isolated credentials; smoke suite and server-backed feature checks pass; no production state changes.Planned
Promotion is exactStable release tag points to the already tested beta commit; production deploy is release-triggered and protected by environment approval.Planned
Hotfix is safeHotfix PR is tested in staging, merged to main, manually released, deployed, smoke-tested, and rollback-tested.Planned
Old system is goneNo 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?