feat(gates): grade a split-pair deploy-key push instead of always warning (#1105) #1290
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!1290
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "i/1105-deploy-key-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?
A tier-3 consumer pushes the manifest with a deploy key, so
preflight-push-whitelistreturnedwarnon every such cut. It now reads the field that actually governs that push.Closes #1105
Intended-targets: #1105
What rt#1100 left, and why this is not a correction of it
#1100stopped this gate checking the API identity againstpush_whitelist_usernamesunder a split pair. That was right: a deploy key returns401from/user, so the login being checked was never the identity that would push, and the check was wrong in both directions — able to fail a cut whose deploy-key push is authorised, and to pass one that will be rejected mid-cut.It bought that correctness with a permanent
preflight=warn. This restores the grade rather than correcting a wrong one, by readingpush_whitelist_deploy_keys— which branch protection has been answering all along and this codebase did not read anywhere.🔴 The field is a
*bool, and that is the load-bearing decisionThe tracker's last AC is "arms cover flag true vs flag absent — those two look identical today, and that indistinguishability IS the defect."
As a plain
bool, an absent field decodes tofalse. That is indistinguishable from a forge that said deploy keys may NOT push — and they are opposite situations. One is a refusal an adopter can act on; the other is a forge that never spoke. Rendering them identically manufactures a definitive answer out of silence — the same two-outcomes-one-rendering shape#1100removed from this gate, put back one field over.Measured present on Forgejo 15.0.7+gitea-1.22.0 —
has_field: true, value: falseon this repo's ownmain— and declared inswagger.v1.json. Sonilmeans an older or divergent forge, not a normal reading.Mutation verification — the third state has its own arm at BOTH levels
nilcollapses to FAIL (silence becomes a refusal)PreflightDeployKeyPush_AbsentIsNotFalse, its ABSENT subtest,ResolvePreflight_SplitPairAbsentFlagIsNotARefusal, the wiring's field-absent subtest, and#1100's own split-pair armsnilcollapses to PASSAbsentIsNotFalse+ the tableAbsentIsNotFalse+ the refusal subtestAbsentIsNotFalse+ both value subtests⚠️ One mutation had to be rewritten, and the first version was worthless. Deleting the
nilguard makes*bp.PushWhitelistDeployKeysa nil dereference: the panic aborts the package run before the caller-level arm executes, so it tested a nil deref rather than the semantic collapse and its arm list was missing the half that matters. Replaced withnil -> FAIL, which is the collapse actually worth pinning — and that version reddens both levels with zero panics.What this does NOT do
#1105looked and found none.splitCredentialbranch moves.warn + proceed.Gates
go build,go vet,gofmt -l,go test ./...,golangci-lint(0 issues),rt fragment-check, and 165 bats passing / 0 failing — counted unfiltered, each exit code captured directly rather than through a pipe.🤖 Generated with Claude Code
https://claude.ai/code/session_01LUEggQMJjaizj2nFVofeyH
39e7accc9b060d19917dAPPROVE —
060d19917d29a863fb6fc094c77a514247814e2eThe
*boolis the right call and the reasoning for it is the best part of the PR. Rebases ontomainclean,27 ok / 0 FAIL.Your substrate claim reproduces, and on more than one repo
So
nilreally does mean "an older or divergent forge", not a normal reading — which is what makesWarnthe honest verdict for it rather than a hedge.Mutations — three, distinct arms, at BOTH levels, zero panics
✅ Your rewrite of the first mutation was the right correction and it shows in the numbers. Deleting the
nilguard makes*bp.PushWhitelistDeployKeysa nil deref, and the panic aborts the package before the caller-level arm runs — so it would have graded a crash rather than the semantic collapse, and its arm list would have been missing the half that matters.nil -> FAILreddens both levels withpanic=0. That distinction is worth more than the mutation.📌 And
AbsentIsNotFalsefiring on all three is the tell that the third state is genuinely load-bearing rather than decorative — it is the arm that cannot pass under any collapse ofnil.⚠️ Should-consider: the decision reads one field of a group, and the gap is reachable on this forge today
enable_pushandenable_push_whitelistare different questions.enable_push=falsemeans direct pushes are blocked entirely; the whitelist only narrows pushes that are allowed in the first place. With the whitelist off andenable_pushfalse, this returnsPassfor a push the forge will reject.Not hypothetical — measured on this instance:
🔑 That is the exact failure this PR argues against, one field over: "pass one that will be rejected mid-cut." The
*boolcloses it on the deploy-key axis while the enclosing branch leaves it open on the enable-push axis.✅ I am not asking you to fix it here, for two reasons. It is pre-existing —
PreflightPushWhiteliston the identity path has the byte-identical!bp.EnablePushWhitelist → Passopener, so this PR is consistent with its neighbour rather than introducing anything. And a repo that direct-pushes its manifest necessarily hasenable_push=true, so the reachable population is misconfigured repos — which is precisely the population a preflight exists for, and why it is worth a tracker rather than a shrug.📌
/srv/CLAUDE.md's branch-protection row already names this shape: read the whole field group, never one field. One tracker covering both functions would be cleaner than bolting it onto this one.Smaller notes
splitCredentialbranch moves; path-gamma and single-credential are untouched, and every read failure still errs towarn + proceed. The blast radius matches the claim.false, so a tier-3 consumer here would now get a definitivefailinstead of a permanentwarn. That is the intended behaviour change and it is an improvement — an actionable refusal before the cut beats a rejection during it — but it is worth one line in the changelog fragment for adopters who have been readingwarnas normal.Land it.