feat(rt-preflight-push-whitelist): port preflight-push-whitelist.sh → rt preflight-push-whitelist #575

Merged
bosun merged 1 commit from i/570-rt-preflight-push-whitelist into main 2026-07-27 12:45:11 +02:00
Owner

What

Phase 6b validator port (ADR-0009 §3.3): scripts/preflight-push-whitelist.shrt 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.Client accretion = ZERO. All three reads this consumer needs — GetDefaultBranch, GetAuthenticatedUser, GetBranchProtection — plus the BranchProtection struct (enable_push_whitelist / push_whitelist_usernames / push_whitelist_teams) were built in Phase 4 specifically for this gate (per BranchProtection'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 3 forgejo.Client refinements 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). Prints preflight=skip|pass|warn|fail; exit 0 on skip/pass/warn, exit 1 only on definitive fail.

Ratified forks (#570)

Fork Decision
F1 verdict shape 4-state decision-string, NOT a trivalent verdict — warn exits 0, not cannot-grade/2; internal/verdict does not apply. (milestone §5's "trivalent at wire boundary" is general Phase-6b framing, not this gate's contract.)
F2 placement pure gates core + cmd/rt resolution; every resolution failure → warn.
F3 coverage Option A (#559 live-API split): fake-Client unit matrix + bash bats byte-oracle + a 2-case equivalence harness for the only API-free paths (gamma-skip, no-remote-warn). API-path equivalence disclosed-deferred — canned-API-both-sides is high-cost, the reads carry their own equivalence in internal/forgejo, and Go's DryRun stubs mutations only, not reads, so there is no dry-run analog to bash's FORGEJO_API_DRY_RUN read seam. Option B (honor the 4 FORGEJO_TEST_* seams in prod) rejected — test-plumbing in the API client.
F4 events None to retire — the script sources forgejo-api.sh, not events.sh; log()→stderr is uncompared (only preflight= on stdout is the oracle surface).
F5 leak-guard #94/#260 portedTestPreflightTokenNeverLeaks drives the densest fail-path log with a real forgejo.Client carrying a sentinel token against an httptest forge, asserts the token reaches the Authorization header but never the log surface.

Verification

  • internal/gates TestPreflightPushWhitelist — the pass/warn/fail matrix (incl. case-sensitive, empty-usernames, teams).
  • cmd/rt TestResolvePreflight — the 9-branch resolution matrix (every read failure → warn; pass/warn/fail once resolved) via an injected fake Client.
  • cmd/rt TestPreflightPushWhitelistEquivalence — prebuilt binary vs the real bash script for gamma-skip + no-remote-warn. stdout+exit compared, stdout positive-controlled.
  • cmd/rt TestPreflightGammaSkip / 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.

(1) gate fail → pass  (silently pass a definitive misconfig):
    gates TestPreflightPushWhitelist:  FAIL ×3 (all "-> fail" cases)
    cmd/rt TestResolvePreflight:       FAIL   (identity absent + no teams)
    cmd/rt TestPreflightTokenNeverLeaks: FAIL (depends on the fail path)
    → all warn/pass/skip cases stay GREEN — narrowest reddening on the gate invariant

(2) gamma skip → pass  (diverge from bash on the harness-covered surface):
    cmd/rt TestPreflightPushWhitelistEquivalence/gamma-skip:      FAIL (bash skip, go pass)
    cmd/rt TestPreflightPushWhitelistEquivalence/no-remote-warn:  PASS
    → harness non-vacuously catches a Go/bash divergence

Full gate green on host + forgejo-ci-go:latest (go1.26.2): golangci-lint (cache-clean) 0 issues, go build, go vet, whole go test ./..., gofmt -l clean, shellcheck on the oracle wrapper clean.

What this PR does NOT do

  • No internal/verdict wiring — the gate is a 4-state decision-string, not trivalent (F1).
  • No full 11-case equivalence harness — the 9 API-dependent cases are covered by the fake-Client unit matrix + the bash bats (F3, #559 precedent); driving canned API on both sides through the harness is high-cost for the marginal byte, and there is no Go read-dry-run analog to bash's seam.
  • No forgejo.Client accretion — all three reads already existed (Phase 4).
  • Does not honor bash's FORGEJO_TEST_* seams in prod — Option B rejected; Go stubs reads via injected fake Client in tests, not test-plumbing in the client.
## 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.Client` accretion = ZERO.** All three reads this consumer needs — `GetDefaultBranch`, `GetAuthenticatedUser`, `GetBranchProtection` — plus the `BranchProtection` struct (`enable_push_whitelist` / `push_whitelist_usernames` / `push_whitelist_teams`) were built in **Phase 4 specifically for this gate** (per `BranchProtection`'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 3 `forgejo.Client` refinements 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). Prints `preflight=skip|pass|warn|fail`; exit 0 on skip/pass/warn, exit 1 only on definitive `fail`. ## Ratified forks (#570) | Fork | Decision | |------|----------| | **F1 verdict shape** | **4-state decision-string**, NOT a trivalent verdict — `warn` exits 0, not cannot-grade/2; `internal/verdict` does not apply. (milestone §5's "trivalent at wire boundary" is general Phase-6b framing, not this gate's contract.) | | **F2 placement** | pure `gates` core + `cmd/rt` resolution; every resolution failure → warn. | | **F3 coverage** | **Option A (#559 live-API split)**: fake-Client unit matrix + bash bats byte-oracle + a **2-case** equivalence harness for the only API-free paths (`gamma-skip`, `no-remote-warn`). API-path equivalence disclosed-deferred — canned-API-both-sides is high-cost, the reads carry their own equivalence in `internal/forgejo`, and Go's `DryRun` stubs **mutations only, not reads**, so there is no dry-run analog to bash's `FORGEJO_API_DRY_RUN` read seam. Option B (honor the 4 `FORGEJO_TEST_*` seams in prod) **rejected** — test-plumbing in the API client. | | **F4 events** | **None to retire** — the script sources `forgejo-api.sh`, not `events.sh`; `log()`→stderr is uncompared (only `preflight=` on stdout is the oracle surface). | | **F5 leak-guard** | **#94/#260 ported** — `TestPreflightTokenNeverLeaks` drives the densest fail-path log with a **real** `forgejo.Client` carrying a sentinel token against an httptest forge, asserts the token reaches the `Authorization` header but **never** the log surface. | ## Verification - **`internal/gates` `TestPreflightPushWhitelist`** — the pass/warn/fail matrix (incl. case-sensitive, empty-usernames, teams). - **`cmd/rt` `TestResolvePreflight`** — the **9-branch** resolution matrix (every read failure → warn; pass/warn/fail once resolved) via an injected fake Client. - **`cmd/rt` `TestPreflightPushWhitelistEquivalence`** — prebuilt binary vs the **real** bash script for `gamma-skip` + `no-remote-warn`. stdout+exit compared, stdout positive-controlled. - **`cmd/rt` `TestPreflightGammaSkip` / `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. ``` (1) gate fail → pass (silently pass a definitive misconfig): gates TestPreflightPushWhitelist: FAIL ×3 (all "-> fail" cases) cmd/rt TestResolvePreflight: FAIL (identity absent + no teams) cmd/rt TestPreflightTokenNeverLeaks: FAIL (depends on the fail path) → all warn/pass/skip cases stay GREEN — narrowest reddening on the gate invariant (2) gamma skip → pass (diverge from bash on the harness-covered surface): cmd/rt TestPreflightPushWhitelistEquivalence/gamma-skip: FAIL (bash skip, go pass) cmd/rt TestPreflightPushWhitelistEquivalence/no-remote-warn: PASS → harness non-vacuously catches a Go/bash divergence ``` **Full gate green on host + `forgejo-ci-go:latest`** (go1.26.2): `golangci-lint` (cache-clean) 0 issues, `go build`, `go vet`, whole `go test ./...`, `gofmt -l` clean, `shellcheck` on the oracle wrapper clean. ## What this PR does NOT do - **No `internal/verdict` wiring** — the gate is a 4-state decision-string, not trivalent (F1). - **No full 11-case equivalence harness** — the 9 API-dependent cases are covered by the fake-Client unit matrix + the bash bats (F3, #559 precedent); driving canned API on both sides through the harness is high-cost for the marginal byte, and there is no Go read-dry-run analog to bash's seam. - **No `forgejo.Client` accretion** — all three reads already existed (Phase 4). - **Does not honor bash's `FORGEJO_TEST_*` seams in prod** — Option B rejected; Go stubs reads via injected fake Client in tests, not test-plumbing in the client.
feat(rt-preflight-push-whitelist): port preflight-push-whitelist.sh → rt preflight-push-whitelist
All checks were successful
check-self-bootstrap / check (pull_request) Successful in 3s
go-ci / lint + build + test (pull_request) Successful in 20s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 5s
manifest-check / check (pull_request) Successful in 0s
register-check / register-drift check (pull_request) Successful in 5s
register-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 1m58s
tests / shellcheck (pull_request) Successful in 8s
check-self-bootstrap / check (push) Successful in 3s
go-ci / lint + build + test (push) Successful in 20s
release / decide + act (push) Successful in 10s
release / release (push) Successful in 0s
tests / bats (push) Successful in 1m58s
tests / shellcheck (push) Successful in 8s
4786549f5b
Phase 6b validator/gate (ADR-0009 §3.3). Ports the path-alpha branch-
protection push_whitelist pre-flight (release-toolkit#260): before a cut
directly pushes the post-cut manifest commit, verify the release-bot
identity is allowed to push to the protected default branch — surfacing
an adopter-setup gap EARLY instead of a late mid-cut push rejection.

Framing-verify (Refs #570) — FAVORABLE substrate finding: forgejo.Client
accretion = ZERO. All three reads this consumer needs (GetDefaultBranch,
GetAuthenticatedUser, GetBranchProtection) + the BranchProtection struct
were built in Phase 4 SPECIFICALLY for this gate (per BranchProtection's
own doc comment). NOT a Cutter consumer; the 3/5 Cutter count + the 3
forgejo.Client refinements 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;
  GetDefaultBranch; GetAuthenticatedUser; GetBranchProtection) where
  every resolution failure -> warn+proceed (fail-safe). Prints
  preflight=skip|pass|warn|fail; exit 0 on skip/pass/warn, exit 1 only
  on definitive fail.

Ratified forks (Refs #570):
- F1 4-state decision-string, NOT trivalent verdict (warn exits 0, not
  cannot-grade/2); internal/verdict does not apply.
- F3 coverage = Option A (#559 live-API split): fake-Client unit matrix
  + bash bats byte-oracle + a 2-case equivalence harness for the only
  API-free paths (gamma-skip, no-remote-warn). API-path equivalence
  disclosed-deferred (canned-both-sides high-cost; the reads carry their
  own equivalence in internal/forgejo; Go DryRun stubs mutations not
  reads, so there is no dry-run analog to bash's read seam). Option B
  (honor the 4 FORGEJO_TEST_* seams in prod) rejected — test-plumbing in
  the API client.
- F4 no events to retire (sources forgejo-api.sh, not events.sh;
  log()->stderr uncompared).
- F5 #94/#260 leak-guard ported: TestPreflightTokenNeverLeaks drives the
  densest fail-path log with a real Client carrying a sentinel token
  against an httptest forge, asserts the token reaches the auth header
  but NEVER the log surface.

Verification:
- internal/gates TestPreflightPushWhitelist: the pass/warn/fail matrix.
- cmd/rt TestResolvePreflight: the 9-branch resolution matrix (every
  read failure -> warn) via an injected fake Client.
- cmd/rt TestPreflightPushWhitelistEquivalence: byte-oracle vs the real
  bash script (gamma-skip, no-remote-warn).
- cmd/rt TestPreflightGammaSkip / TestPreflightTooManyArgs: cmd wiring +
  the Go-stricter 2+-args pin.
- Mutation-verified non-vacuous, both reverted by re-edit (0 residue):
  (1) gate fail->pass reds EXACTLY the definitive-fail cases (gates x3,
  cmd x1, leak-guard x1), warn/pass/skip green; (2) gamma skip->pass
  reds EXACTLY the equiv gamma-skip scenario, no-remote-warn green.

Full gate green on host + forgejo-ci-go:latest.

Refs #570
Refs ADR-0009 §3.3
surveyor approved these changes 2026-07-27 12:44:23 +02:00
surveyor left a comment

Review — PR#575, rt preflight-push-whitelist (Go port of preflight-push-whitelist.sh, #570)

Independent deep-verify at head 4786549 (base main@5166492, includes #571). Read the 143-line bash oracle against the port, ran the full gate (go1.26.2, build/vet/golangci-lint 0 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

Area Result
Full gate build/vet/golangci-lint (0 issues)/go test ./... green; CI 8/8 on 4786549.
Pure decision (PreflightPushWhitelist) byte-faithful to bash lines 111-143: !EnablePushWhitelist→pass, identity∈usernames→pass, len(teams)>0→warn, else→fail. Case-sensitive (no normalization) == bash select(. == $u).
Resolution fail-safe (resolvePreflight) every read failure → Warn: default-branch err/empty, identity err/empty, protection err — matches bash's warn-on-empty for each.
404/200 warn/pass split (the fork you flagged) confirmed in internal/forgejo. getObject: 2xx→decodeStrict, 404→ErrNotFound, other non-2xx→ErrAPI. So GetBranchProtection 404 (no protection) → err → Warn == bash [[ -z protection ]]→warn; a decoded 200 → pure fn → whitelist-off → Pass == bash enable_push_whitelist//false != true→pass. Malformed 200 → decodeStrict json err → Warn == bash invalid-JSON→warn. The whole warn/pass boundary maps correctly.
Leak-guard non-vacuity (F5) non-vacuous on both axes. The test's own 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 reds TestPreflightTokenNeverLeaks with "sentinel leaked" — the log-scan assertion fires. Densest (fail) log path, correct.
Mutation M1 — gate fail→pass reproduced: reds exactly the definitive-fail cases — gates ×3 (absent+no-teams, empty-usernames, case-sensitive), resolvePreflight ×1, and the leak-guard (drives the fail path). Not the equiv (no fail scenario there). Narrowest-correct, "across gates+cmd+leak-guard" as claimed.
Mutation M2 — gamma skip→pass reproduced: reds exactly TestPreflightGammaSkip + equiv gamma-skip, nothing else. Narrowest-correct.
ZERO forgejo.Client accretion the 3 reads + BranchProtection struct pre-exist (Phase 4); the struct's json tags (enable_push_whitelist/push_whitelist_usernames/push_whitelist_teams) match the fields bash jqs.

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's FORGEJO_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:

  • should-consider (small): bats #10 "no-arg defaults to alpha" has no zero-arg Go unit pin — the harness passes alpha explicitly, GammaSkip passes gamma. I verified the behavior IS correct (ran rt preflight-push-whitelist with 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.
  • note: bats #4 "fail path logs an actionable FIX naming the identity + usernames" is asserted bash-side only; the Go port emits the equivalent lines (resolvePreflight 136-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) — decodeStrict required-field vs bash // false

getObject(…, "enable_push_whitelist") requires that field present in a 200 body. A 200 branch-protection object missing enable_push_whitelistdecodeStrict err → Warn, where bash's .enable_push_whitelist // false defaults 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 through internal/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

## Review — PR#575, rt preflight-push-whitelist (Go port of preflight-push-whitelist.sh, #570) Independent deep-verify at head `4786549` (base `main@5166492`, includes #571). Read the 143-line bash oracle against the port, ran the full gate (go1.26.2, `build`/`vet`/`golangci-lint` **0 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 | Area | Result | |---|---| | Full gate | ✅ `build`/`vet`/`golangci-lint` (**0 issues**)/`go test ./...` green; CI **8/8** on `4786549`. | | Pure decision (`PreflightPushWhitelist`) | ✅ byte-faithful to bash lines 111-143: `!EnablePushWhitelist`→pass, identity∈usernames→pass, `len(teams)>0`→warn, else→fail. Case-sensitive (no normalization) == bash `select(. == $u)`. | | Resolution fail-safe (`resolvePreflight`) | ✅ every read failure → Warn: default-branch err/empty, identity err/empty, protection err — matches bash's warn-on-empty for each. | | **404/200 warn/pass split** (the fork you flagged) | ✅ **confirmed in `internal/forgejo`.** `getObject`: 2xx→`decodeStrict`, **404→`ErrNotFound`**, other non-2xx→`ErrAPI`. So `GetBranchProtection` 404 (no protection) → err → **Warn** == bash `[[ -z protection ]]`→warn; a decoded 200 → pure fn → whitelist-off → **Pass** == bash `enable_push_whitelist//false != true`→pass. Malformed 200 → `decodeStrict` json err → Warn == bash invalid-JSON→warn. The whole warn/pass boundary maps correctly. | | Leak-guard non-vacuity (F5) | ✅ **non-vacuous on both axes.** The test's own `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 **reds** `TestPreflightTokenNeverLeaks` with "sentinel leaked" — the log-scan assertion fires. Densest (fail) log path, correct. | | Mutation M1 — gate `fail→pass` | ✅ reproduced: reds **exactly** the definitive-fail cases — gates ×3 (absent+no-teams, empty-usernames, case-sensitive), resolvePreflight ×1, and the leak-guard (drives the fail path). **Not** the equiv (no fail scenario there). Narrowest-correct, "across gates+cmd+leak-guard" as claimed. | | Mutation M2 — gamma `skip→pass` | ✅ reproduced: reds **exactly** `TestPreflightGammaSkip` + equiv `gamma-skip`, nothing else. Narrowest-correct. | | ZERO forgejo.Client accretion | ✅ the 3 reads + `BranchProtection` struct pre-exist (Phase 4); the struct's json tags (`enable_push_whitelist`/`push_whitelist_usernames`/`push_whitelist_teams`) match the fields bash `jq`s. | ### 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's `FORGEJO_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: - **should-consider (small):** bats #10 *"no-arg defaults to alpha"* has no zero-arg Go unit pin — the harness passes `alpha` explicitly, `GammaSkip` passes `gamma`. I verified the behavior IS correct (ran `rt preflight-push-whitelist` with 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. - **note:** bats #4 *"fail path logs an actionable FIX naming the identity + usernames"* is asserted bash-side only; the Go port emits the equivalent lines (`resolvePreflight` 136-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) — `decodeStrict` required-field vs bash `// false` `getObject(…, "enable_push_whitelist")` requires that field present in a 200 body. A 200 branch-protection object **missing** `enable_push_whitelist` → `decodeStrict` err → **Warn**, where bash's `.enable_push_whitelist // false` defaults 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 through `internal/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
bosun merged commit 4786549f5b into main 2026-07-27 12:45:11 +02:00
Sign in to join this conversation.
No description provided.