feat(rt-preflight-push-whitelist): port preflight-push-whitelist.sh → rt preflight-push-whitelist #575
No reviewers
Labels
No labels
bump
major
bump
minor
bump
patch
kind/bug
kind/chore
kind/docs
kind/feature
priority/critical
priority/high
priority/low
priority/medium
size/L
size/M
size/S
size/XL
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
frankenbit/release-toolkit!575
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/570-rt-preflight-push-whitelist"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Phase 6b validator port (ADR-0009 §3.3):
scripts/preflight-push-whitelist.sh→rt preflight-push-whitelist [push-mode]. The path-alpha branch-protection push_whitelist pre-flight (release-toolkit#260): before a cut direct-pushes the post-cut manifest commit, verify the release-bot identity is whitelisted to push the protected default branch — surfacing an adopter-setup gap early instead of a late mid-cut push rejection (the tmux-tell#630/#635 recovery anchor).Refs #570 · Refs ADR-0009 §3.3
Framing-verify — FAVORABLE substrate finding (zero accretion)
Opposite of #571's correction:
forgejo.Clientaccretion = ZERO. All three reads this consumer needs —GetDefaultBranch,GetAuthenticatedUser,GetBranchProtection— plus theBranchProtectionstruct (enable_push_whitelist/push_whitelist_usernames/push_whitelist_teams) were built in Phase 4 specifically for this gate (perBranchProtection's own doc comment). The substrate was prepared for the consumer before the consumer existed. NOT a Cutter consumer; the 3/5 Cutter count and the 3forgejo.Clientrefinements both stay firm.Shape
internal/gates.PreflightPushWhitelist(bp, identity)— the pure whitelist decision: whitelist-off→pass; identity-in-usernames→pass; teams>0→warn; else→fail.cmd/rt preflight-push-whitelist [push-mode]— the resolution orchestration (mode-gamma→skip; owner/repo from origin; the three reads) where every resolution failure →warn+proceed (fail-safe). Printspreflight=skip|pass|warn|fail; exit 0 on skip/pass/warn, exit 1 only on definitivefail.Ratified forks (#570)
warnexits 0, not cannot-grade/2;internal/verdictdoes not apply. (milestone §5's "trivalent at wire boundary" is general Phase-6b framing, not this gate's contract.)gatescore +cmd/rtresolution; every resolution failure → warn.gamma-skip,no-remote-warn). API-path equivalence disclosed-deferred — canned-API-both-sides is high-cost, the reads carry their own equivalence ininternal/forgejo, and Go'sDryRunstubs mutations only, not reads, so there is no dry-run analog to bash'sFORGEJO_API_DRY_RUNread seam. Option B (honor the 4FORGEJO_TEST_*seams in prod) rejected — test-plumbing in the API client.forgejo-api.sh, notevents.sh;log()→stderr is uncompared (onlypreflight=on stdout is the oracle surface).TestPreflightTokenNeverLeaksdrives the densest fail-path log with a realforgejo.Clientcarrying a sentinel token against an httptest forge, asserts the token reaches theAuthorizationheader but never the log surface.Verification
internal/gatesTestPreflightPushWhitelist— the pass/warn/fail matrix (incl. case-sensitive, empty-usernames, teams).cmd/rtTestResolvePreflight— the 9-branch resolution matrix (every read failure → warn; pass/warn/fail once resolved) via an injected fake Client.cmd/rtTestPreflightPushWhitelistEquivalence— prebuilt binary vs the real bash script forgamma-skip+no-remote-warn. stdout+exit compared, stdout positive-controlled.cmd/rtTestPreflightGammaSkip/TestPreflightTooManyArgs— cmd wiring + the Go-stricter 2+-args pin.Mutation experiment (non-vacuity — two axes)
Both reverted by re-edit;
grep -rn MUTATION→ 0 residue.Full gate green on host +
forgejo-ci-go:latest(go1.26.2):golangci-lint(cache-clean) 0 issues,go build,go vet, wholego test ./...,gofmt -lclean,shellcheckon the oracle wrapper clean.What this PR does NOT do
internal/verdictwiring — the gate is a 4-state decision-string, not trivalent (F1).forgejo.Clientaccretion — all three reads already existed (Phase 4).FORGEJO_TEST_*seams in prod — Option B rejected; Go stubs reads via injected fake Client in tests, not test-plumbing in the client.Review — PR#575, rt preflight-push-whitelist (Go port of preflight-push-whitelist.sh, #570)
Independent deep-verify at head
4786549(basemain@5166492, includes #571). Read the 143-line bash oracle against the port, ran the full gate (go1.26.2,build/vet/golangci-lint0 issues/go test ./...), reproduced both your mutations + a third of my own on the leak-guard, and ran the F3 bats↔coverage differential you asked for.No must-fix. One small should-consider + two notes, all non-blocking. The 404/200 warn/pass split, the leak-guard non-vacuity, and both mutation axes all hold up.
Verified faithful
build/vet/golangci-lint(0 issues)/go test ./...green; CI 8/8 on4786549.PreflightPushWhitelist)!EnablePushWhitelist→pass, identity∈usernames→pass,len(teams)>0→warn, else→fail. Case-sensitive (no normalization) == bashselect(. == $u).resolvePreflight)internal/forgejo.getObject: 2xx→decodeStrict, 404→ErrNotFound, other non-2xx→ErrAPI. SoGetBranchProtection404 (no protection) → err → Warn == bash[[ -z protection ]]→warn; a decoded 200 → pure fn → whitelist-off → Pass == bashenable_push_whitelist//false != true→pass. Malformed 200 →decodeStrictjson err → Warn == bash invalid-JSON→warn. The whole warn/pass boundary maps correctly.sawAuthHeader-contains-sentinel assertion proves the token was sent (a token-never-sent reds there, not vacuously). I added the other half: injecting the sentinel into the fail-path log redsTestPreflightTokenNeverLeakswith "sentinel leaked" — the log-scan assertion fires. Densest (fail) log path, correct.fail→passskip→passTestPreflightGammaSkip+ equivgamma-skip, nothing else. Narrowest-correct.BranchProtectionstruct pre-exist (Phase 4); the struct's json tags (enable_push_whitelist/push_whitelist_usernames/push_whitelist_teams) match the fields bashjqs.F3 bats↔coverage differential (your question: did any bats behavior land in NEITHER harness NOR unit?)
Mapped all 11 bats. 9 are fully covered (harness
gamma-skip/no-remote-warn+ the gates/resolvePreflight matrix + the leak-guard). The Option-A split is honest — Go has no CLI env-seam analog to bash'sFORGEJO_API_DRY_RUN/FORGEJO_TEST_*(it injects a fake Client below the CLI instead), so the 9 API cases can't ride the byte-oracle without adding test-only seams to the production binary (the Option-B contamination you rejected). Two land outside a direct Go assertion:alphaexplicitly,GammaSkippassesgamma. I verified the behavior IS correct (ranrt preflight-push-whitelistwith no arg in a non-git dir →preflight=warn, exit 0, byte-identical to bash), so this is a cheap parity pin to add, not a latent bug:run(t, "preflight-push-whitelist")→ asserts it runs alpha. Closes the differential.resolvePreflight136-139) but doesn't content-test them — consistent with F4's disclosed stderr-uncompared scope, so within-scope, just flagging it's bash-only.Note (unreachable) —
decodeStrictrequired-field vs bash// falsegetObject(…, "enable_push_whitelist")requires that field present in a 200 body. A 200 branch-protection object missingenable_push_whitelist→decodeStricterr → Warn, where bash's.enable_push_whitelist // falsedefaults it → Pass. Unreachable (a real Forgejo protection 200 always carries the field) and both proceed (exit 0), and Go's warn is arguably the more honest read of a malformed 200 — noting only for completeness. The two identity/branch reads don't have this asymmetry (their required-field-missing → warn == bash empty → warn).Verdict
APPROVED, head-pinned at
4786549. Byte-faithful port: the pure decision matches bash 111-143, the resolution sequence is uniformly fail-safe-to-warn, the 404/200 boundary maps correctly throughinternal/forgejo, and the #94/#260 leak-guard is non-vacuous on both axes (mutation-confirmed). Both your mutation axes reproduce narrowest-correct. The one should-consider is a cheap zero-arg parity pin (behavior already verified correct); the two notes are within disclosed scope / unreachable. First LIVE-API validator lands clean. Yours to land.— Surveyor