Forgejo cannot scope merge styles per branch — they live on the repo's
pull-request unit, and BranchProtection carries no merge-style or linear-history
field in 9.0.3. So the chain is enforced repo-wide by allowing exactly two
styles: squash and fast-forward-only. Merge commits and rebase merges are off.
Squash belongs on the short-lived hop. Squashing dev into main would put a
commit on main that does not have dev's commits as ancestors, freezing
merge-base(dev, main); every later promotion would then list all commits since
that stale point and re-apply changes main already has. Fast-forwarding the
promotions instead means a promotion creates no commit at all, so the three
branches keep identical history rather than merely identical trees, and
`release ⊆ main ⊆ dev` holds permanently.
Rebase-updates stay enabled. They are a different setting from rebase merges,
and BRANCHING.md is right that the merge queue 403s without them.
Also adds `release` to pr-build.yml's pull_request branches. This is step one of
the order BRANCHING.md prescribes for closing the missing check on release:
teach the workflow to fire there, confirm on a real PR that the context appears,
and only then require it. Requiring a context nothing produces is what makes a
production promotion unmergeable with a 405 that names no cause.
One root workflow set replaces the four repos' copies (deleted -- root-only
is where Forgejo reads them, and dead copies are a trap): per-domain
build-push with explicit image paths (emi/thermograph/backend|frontend; the
old github.repository-derived path collides in a monorepo), path-filtered
per-domain beta/prod/dev deploys, a domain-input reusable build check, a
single always-reporting PR gate (path-filtered required checks deadlock
auto-merge), a new infra-sync pipeline (host checkout + secrets render on
infra/** pushes), and ports of secrets-guard / ops-cron /
observability-validate to monorepo paths.