Commit Graph

4 Commits

Author SHA1 Message Date
SatoshiQaziMuhammed b289bbc0ae ops: make the drift check see rulesets, not just branch protection (#36)
The check reported branch protection fully green while a repository ruleset on
the same branch required two status checks that no workflow produces —
'Build, Lint & Test' and 'All checks passed', names left behind when the jobs
were renamed. Nothing could merge without --admin, and the report said
everything was fine.

That is the worst shape a check can take: confident and blind. A branch can be
governed by classic protection and a ruleset at once, independently, and the
stricter wins. Reading one and calling it the answer is how a broken gate stays
invisible for months while the team learns to route around it.

--check now reads active rulesets too, and compares each required check against
what recent pull requests actually report. A rule waiting on a check that never
arrives does not slow a branch — it closes it.

Sampling uses recent PR heads rather than main's head, because the aggregate
gate only runs on pull_request; sampling main would report it as never seen and
manufacture the opposite false alarm.
2026-08-01 21:04:26 -07:00
SatoshiQaziMuhammed 78fd8b0514 ops: stop requiring an approval that nobody can give (#34)
The rule said one approving review. On a four-person team where the author is
usually the only person awake, that meant every merge went through
`gh pr merge --admin` — so the requirement was not producing review, it was
producing a habit of bypassing the branch rule. A control everyone routes around
is worse than no control: it still shows up green in an audit.

Zero required approvals, and the rest of the protection stays exactly as it was.
Nothing lands on main without a PR; nothing merges with red CI. That is the part
a team this size actually gets value from, and it costs no waiting.

What was being protected here was never code review. It was that nothing reaches
production unnoticed, and that funds cannot move on one person's say-so. Both
live elsewhere: production waits on the environment gate, and fund movement is
going behind on-chain multisig — where a stolen account cannot forge a second
signature the way a collected token can forge a second approval.

Drift check passes against live settings.
2026-08-01 10:31:51 -07:00
pezkuwichain 7c9fbfb4b2 ci: one approval per deploy, not two (#30)
All four deploy jobs carried `environment: production`, which reads as the
stricter design — GitHub holds each one until approved. It does not behave that
way. Approval requests are batched by eligibility, and these jobs are never
eligible together: deploy-supabase waits only on the notification while the rest
wait on image builds. So a run asks twice, minutes apart, and nothing announces
the second round.

That happened on 2026-07-31. The first approval shipped the Supabase functions
and migrations; the run went back to waiting and stayed there. For as long as it
sat, the schema had moved and the app serving it had not.

Deployment protection now lives on a single `approve-deploy` job that the four
deploy jobs depend on. One approval releases the whole set.

That trades GitHub's own per-job enforcement for a dependency edge, and an edge
can be dropped by a later edit. ops/check-deploy-gate.py makes it a guarantee
again: it fails CI if a deploy job does not depend on the gate, if a second job
declares the environment and brings the split back, or if a job uses always()
without asserting the gate succeeded — which looks correct and silently
un-holds it. Verified against all three by mutating the workflow.

Also documented in ops/README.md: a run waiting on approval cannot be stopped
with `gh run cancel`; its pending deployment must be rejected. And that
bump-version still pushes to main before the gate — left alone, but written
down rather than left to be rediscovered.
2026-08-01 00:55:13 -07:00
pezkuwichain 7482421f3d ops: capture branch protection and the deploy environment as code
Both were configured by hand through the GitHub API today. Nothing in the repo
reflected them, so they were invisible here, unversioned, and removable without
a trace — which undercuts their purpose, since they exist so that nothing reaches
main or production unreviewed.

apply-repo-settings.sh is now the source of truth for both, and idempotent: each
call PUTs the full desired state rather than stacking changes. --check reports
drift without touching anything, which is what makes this useful later — a
setting silently relaxed is otherwise only discovered when it matters.

Documented alongside it: why CI Gate  is the only required check (it is an
aggregate that already inspects the others, and listing each job would mean a
rename quietly drops a requirement), and why enforce_admins stays false (an
incident needs an escape hatch — rare, visible, recorded).

This is the same class of problem as the migrations fixed earlier today: state
that only exists because someone once clicked something, with no record of what
or why.
2026-07-31 02:07:21 -07:00