HOMELAB-1679: fix(forgejo): FORGEJO_LANDING_REVIEWERS cannot widen the trusted set on gated repos (LR-20261111-017, Amendment 15) #130
Closed
pi-landing
wants to merge 1 commit from
lr-20261111-017-n6split into main
pull from: lr-20261111-017-n6split
merge into: Homelab:main
Homelab:main
Homelab:reviewers-by-repo-f1
Homelab:lr-20261111-017-s1
Homelab:lr-20261111-017-f5
Homelab:lr-20261111-017-census
Homelab:lr-20261111-017-lockharden
Homelab:lr/IR-20261111-extskill
Homelab:lr-20261111-017-headfollow2
Homelab:lr-20261111-017-headfollow
Homelab:lr-20261111-017-headpin
Homelab:plane/HOMELAB-2479-122-signal-exit-fix
Homelab:plane/HOMELAB-2479-seat-post-helper
Homelab:archive/plane/HOMELAB-1850-attribution-wip-20261110
Homelab:archive/plane/HOMELAB-1688-routines-wip-20261110
Homelab:archive/plane/HOMELAB-2674-operator-identity-wip-20261110
Homelab:lock-frozen-20261107
Homelab:ld/pi-skills-lab-run-archive-fix
Homelab:ldv0/ld-sessiontag-f
Homelab:plane/HOMELAB-2682-reload-trace
Homelab:plane/HOMELAB-2682-fake-tmpfix
Homelab:plane/HOMELAB-2682-forge-write-refuse
Homelab:plane/HOMELAB-2682-ci-key-file
Homelab:plane/ci-pending-status-truth
Homelab:plane/HOMELAB-2682-watchdog-fix
Homelab:plane/HOMELAB-2682-operator-refuse
Homelab:plane/HOMELAB-2682-refusal-forms
Homelab:plane/HOMELAB-2682-selfresolve-nearmiss
Homelab:plane/HOMELAB-2682-cheap-plane-comments
Homelab:plane/HOMELAB-2682-verdict-arm-fix
Homelab:plane/HOMELAB-2682-comment-refuse
Homelab:plane/HOMELAB-2682-finding-rule
Homelab:plane/HOMELAB-2682-blocker-none
Homelab:plane/HOMELAB-2682-collapse
Homelab:plane/HOMELAB-2682-negation-heading
Homelab:plane/HOMELAB-2682-comment-as
Homelab:plane/HOMELAB-2682-bold-negation
Homelab:plane/HOMELAB-2682-get-pr-sha
Homelab:plane/HOMELAB-2682-phrase-hardening
Homelab:plane/HOMELAB-2682-plane-comments-limit
Homelab:plane/HOMELAB-2682-lock-defects
Homelab:plane/HOMELAB-ci-runs-limit
Homelab:plane/HOMELAB-lock-heading-delta
Homelab:plane/HOMELAB-lock-holes
Homelab:plane/HOMELAB-2686-lock-read
Homelab:plane/HOMELAB-2634-janitor-background
Homelab:archive/plane/HOMELAB-2674-operator-identity-20261109
Homelab:plane/HOMELAB-2674-operator-identity
Homelab:plane/HOMELAB-2634-janitor-relief
Homelab:plane/HOMELAB-2524-ci-contexts
Homelab:plane/HOMELAB-2612-gradle-danger
Homelab:plane/HOMELAB-2661-hold-session
Homelab:plane/HOMELAB-2659-merge-panel
Homelab:plane/HOMELAB-2634-herdr-janitor
Homelab:plane/HOMELAB-2578-openbao-auth
Homelab:plane/HOMELAB-2590-blocker-record
Homelab:plane/HOMELAB-2586-wrapped-negation
Homelab:plane/HOMELAB-2566-formal-verdicts
Homelab:plane/HOMELAB-2578-runtime-landing-credential
Homelab:plane/HOMELAB-2567-typebox-tests
Homelab:plane/HOMELAB-2522-formal-supersession
Homelab:plane/HOMELAB-2550-comment-identity-hardening
Homelab:plane/HOMELAB-2554-comment-identity-hardening
Homelab:plane/HOMELAB-2521-blocker-ids
Homelab:plane/HOMELAB-2529-comment-identity
Homelab:plane/HOMELAB-2526-merge-identity
Homelab:plane/HOMELAB-2527-comment-identity
Homelab:plane/HOMELAB-2512-hold-identity
Homelab:plane/HOMELAB-2423-verdict-helper
Homelab:plane/HOMELAB-2411-lock-supersede
Homelab:plane/HOMELAB-2346-exact-head-lock
Homelab:plane/HOMELAB-2323-otel-prompt-undefined
Homelab:plane/HOMELAB-2321-poll-loop-guard
Homelab:plane/HOMELAB-2218-redact-fp
Homelab:plane/HOMELAB-1727-friction-actor
Homelab:plane/HOMELAB-1880-meeting-enriched-transcript
Homelab:plane/HOMELAB-1690-l7
Homelab:plane/HOMELAB-1850-attribution
Homelab:plane/HOMELAB-1877-voice-whisper-tier1
Homelab:plane/HOMELAB-1855-voice-whisper-remote-mic
Homelab:plane/HOMELAB-1688-routines
Homelab:plane/HOMELAB-1842-intercom-conflict
Homelab:plane/HOMELAB-1678-refocus
Homelab:plane/HOMELAB-1788-fallback-fixture
Homelab:plane/HOMELAB-ci-swarm-fix
Homelab:plane/HOMELAB-1788-intercom
Homelab:plane/HOMELAB-1679-closure
Homelab:plane/HOMELAB-1769-mcp-bridge
Homelab:plane/HOMELAB-1750-meeting-video-review
Homelab:plane/HOMELAB-1736-forgejo-ci
Homelab:plane/HOMELAB-1695-modelrole
Homelab:plane/HOMELAB-1741-watchdog-challenge
Homelab:plane/HOMELAB-1668-stall-watchdog
Homelab:plane/HOMELAB-1734-epic-session-generic
Homelab:plane/HOMELAB-1629-attach-kickoff
Homelab:plane/epic-session-emit-fix
Homelab:plane/HOMELAB-1628-voice-whisper
Homelab:plane/HOMELAB-1624-docs
Homelab:plane/HOMELAB-1560-epic-session
Homelab:plane/HOMELAB-1618-pane-ux
Homelab:plane/HOMELAB-1614-swarm-view-ux
Homelab:plane/HOMELAB-1609-ext
Homelab:plane/HOMELAB-1609-skills-agents
Homelab:plane/HOMELAB-1577-steer-send
Homelab:plane/HOMELAB-1576-split-direction
Homelab:plane/HOMELAB-1544-async-only-dispatch
Homelab:plane/HOMELAB-1575-swarm-ux
Homelab:plane/HOMELAB-1571-pane-nits
Homelab:plane/HOMELAB-1569-settings-reload
Homelab:plane/HOMELAB-1573-chat-first
Homelab:plane/HOMELAB-1572-on-demand-panes
Homelab:plane/HOMELAB-1570-pane-ux
Homelab:plane/HOMELAB-1558-phase3
Homelab:plane/HOMELAB-1558-phase4
Homelab:plane/HOMELAB-1557-phase2
Homelab:plane/HOMELAB-1557-phase1
Homelab:plane/HOMELAB-1561-ci
Homelab:plane/HOMELAB-1559-contract-followup
Homelab:plane/HOMELAB-1559-phase0-foundation
Homelab:plane/HOMELAB-1555-telemetry-attribution
Homelab:plane/HOMELAB-1552-otel-subagent-spans
Homelab:plane/HOMELAB-1553-subagent-result-fallback
Homelab:plane/HOMELAB-1552-forgejo-merge-default
Homelab:plane/HOMELAB-1548-model-override
Homelab:rescue/HOMELAB-1534-1542-inflight
Homelab:plane/HOMELAB-1527-tunnel-spans
Homelab:plane/HOMELAB-1532-unknown-prefix-fail-loud
Homelab:plane/HOMELAB-1531-send-project-map
Homelab:homelab-1517/otel-header-format-fix
No reviewers
Labels
Clear labels
No items
No labels
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set.
Reference
Homelab/pi-extensions!130
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "lr-20261111-017-n6split"
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?
Spec 017, AMENDMENT 15 — N6 split out ahead of #125. Small and surgical; gate-changing, so two pane seats (via
forgejo_comment_asas ld-reviewer) plus homelab-88's file review land before it merges. Delivered as soon as it lands — not held by Amendment 10.The rule
On the two repos named in code (
GO_GATED_REPOS:Homelab/infra-core,Homelab/prod) the landing lock's trusted reviewer set stays the default["ld-reviewer"]:FORGEJO_LANDING_REVIEWERSis ignored there, andlanding policy: FORGEJO_LANDING_REVIEWERS is set and ignored on GO-gated repo <owner/name> …).Same class as B3: a value a session can set is not a gate. Non-gated repos keep today's behavior (the env widens the set as before). The repoRef is threaded from every caller (merge gate, census, verdict-cli's two read paths); the policy seam's second parameter is optional, so existing callers and tests compile unchanged.
Proven
forgejo/tests/landing-gated-reviewers-n6.test.ts(4 tests): gated + env set → bogus identity NOT trusted + exactly one log line; non-gated + env set → bogus identity trusted, no log line; gated + env unset → default, silent. The two new-behavior tests fail on the old code (red→green shown by stashinglanding.ts).vitest run: 53 files / 1230 pass; the 1 failure istests/merge-identity.test.ts(ambient landing token), identical on the untouched base — pre-existing.tsc --noEmitclean.Lab-Run: LR-20261111-017
LD-VERDICT APPROVED head=fc83914cfd3ac572d3599cd14ac81c0a9b899bb0 session=LR-20261111-017-seatA-20261111T1617Z
Head confirmed: the PR's current head is
fc83914cfd(base51daf8842e), so the pin holds. Diff read against origin/main: 4 files, +236/-8 (forgejo/index.ts, forgejo/landing.ts, forgejo/verdict-cli.ts, new forgejo/tests/landing-gated-reviewers-n6.test.ts).The gated path cannot be widened by the env. resolveLandingPolicy's reviewerIdentities is
(gated ? null : reviewersOverride) ?? DEFAULT_LANDING_POLICY.reviewerIdentities(landing.ts:3053-3054): when gated the override is not merely ignored, it is not reachable. Traced every caller into resolveLandingPolicy — merge gate index.ts:440 (evidence.repoRef, built from the same owner/repo as the PR read, index.ts:324), census index.ts:962 (repoReffrom parseRepoRef, index.ts:954), verdict-cli collectState verdict-cli.ts:619 (args.repo) and the ledger fallback verdict-cli.ts:948 (collected.state.repoRef). In every path the value used as the gate key is literally the same value used to address the API: the CLI validates--repoto exactly owner/name (verdict-cli.ts:151) after stripping URL/.git/trailing slash, so repoKey cannot resolve to a different key than the repo the call touches. Also confirmed the seam is not bypassable elsewhere: no consumer outside forgejo/ references reviewerIdentities, and verdict.ts falls back to DEFAULT_LANDING_POLICY when no policy is supplied (verdict.ts:596), i.e. fail-closed. repoKey/repo isGoGatedRepo normalise case, URL, .git, trailing slash and query/fragment; lookalikes ("Homelab/prod-fork") are correctly not gated.The log fires once per gated call and never for non-gated. Single log site, guarded by
gated && reviewersOverride(landing.ts:3043-3050); resolution happens once per merge (assertLandingLock called once, index.ts:787), once per census, and once per collectState. Gated+unset and non-gated+set are both silent, as specified.The tests genuinely fail on the old code. Scratch check in my own clone:
git checkout origin/main -- forgejo/landing.ts, run the new file → 2 failed / 2 passed, exactly the two new-behavior tests red ("gated repo + env set: the extra identity is NOT trusted…" and "the gate recognises the repo in the shapes a caller actually holds"); restoring the PR version → 4/4 green. Full suite on the head: 53 files, 1230 pass, the only failure is tests/merge-identity.test.ts ("merges with the dedicated landing credential…"), the ambient-landing-token case that fails identically on the untouched base — pre-existing, not a hold.tsc --noEmitexit 0.Nothing else changed. The landing.ts hunks stop at the policy seam (GO_GATED_REPOS, repoKey, isGoGatedRepo, policyLine, resolveLandingPolicy); the only other edits are the two index.ts call sites and the two verdict-cli.ts call sites plus the new test file. Non-gated behaviour is byte-for-byte today's.
Holds: none.
Non-blocking notes (neither changes a verdict):
model: omniroute/deepseek-flash
session: LR-20261111-017-seatA-20261111T1617Z
LD-VERDICT APPROVED head=fc83914cfd3ac572d3599cd14ac81c0a9b899bb0 session=LR-20261111-017-seatB-20261111T1617Z
PR head from the API is exactly
fc83914cfd(base51daf884). Judged the diff and the checks only.The gated path cannot be widened by the env. Every
resolveLandingPolicycall site in the tree is threaded: merge gateforgejo/index.ts:443withevidence.repoRef, built atforgejo/index.ts:327as${owner}/${repo}fromparseRepoRef(index.ts:261-273— canonical, handlesowner/repo,.git, and a full URL); censusforgejo/index.ts:979with${parsed.owner}/${parsed.repo}(:971); verdict-cli's two read pathsforgejo/verdict-cli.ts:619and:948. There is no other read ofFORGEJO_LANDING_REVIEWERSanywhere outsidelanding.ts, andevaluateLandingLockonly consumes a policy, so no second, ungated resolution exists.verdict.ts:596falls back toDEFAULT_LANDING_POLICY(narrow set) when none is injected — fail-closed. Traced end to end.The log fires once per gated call, never on non-gated. Single site
forgejo/landing.ts:3047-3055, guarded bygated && reviewersOverride, andresolveLandingPolicyruns once per merge attempt / census / CLI run (verdict-cli:948is a fallback only whenstate.policyis unset, whichcollectStatealways sets). The tests count it through an injected sink rather than eyeballing stderr: 1 line for gated+set,[]for non-gated+set and for gated+unset.The tests are genuinely red on the old code. I re-ran the new test file against untouched
origin/main(51daf88) in a scratch worktree with nolanding.tschange: 2 failed | 2 passed — exactly the two new-behavior tests (gated+set, and the repo-shape test), with the two unchanged-behavior tests green. On the head they are 4/4. Red→green holds.Nothing else changed — after accounting for a stale branch. The branch is based on
b4b4478, one merge behind main (51daf88, PR #128). Somain..headdisplays a revert of #128 (theforgeMessage/body409 distinction and the pin lowercasing). It does not actually change that code: I mergedfc83914intoorigin/mainlocally — clean, no conflict — and the resulting tree differs from main by exactly 4 files, +236/−8 (landing.ts,index.ts,verdict-cli.ts, and the new test). #128 survives, and its behaviour is untouched by this branch's own diff (b4b4478..fc83914, 4 files, +236/−8).Holds: none.
Non-blocking notes:
forgejo/landing.ts:2986-2992repoKeytakes the last two segments, whileforgejo/verdict-cli.ts:619passes the rawargs.repo—--repo Homelab/prod/xkeys asprod/x(not gated) even thoughverdict-cli.ts:449still addresses Homelab/prod. The merge gate and census are unaffected (both pass canonicalowner/repo), so no landing can be widened; at worst the CLI's own policy/preview warning disagrees with the gate. Cheap follow-up: normalise in the CLI, or key on the first two segments.docs/forgejo.md:127and:708still describeFORGEJO_LANDING_REVIEWERSas an unconditional override, with no mention of the two gated repos. Fine to fold into #125.Checks I ran on the merged head:
vitest run→ 53 files, 1234 passed, 1 failed =tests/merge-identity.test.ts:144(ambient landing token), which fails identically on untouched main (1 failed | 24 passed) — pre-existing, not a hold.tsc --noEmitclean.model: omniroute/deepseek-flash
session: LR-20261111-017-seatB-20261111T1617Z
Superseded per El Director's AMENDMENT 18: N6 consolidated with the server-name rule and B8 into ONE lock-hardening PR — #131 (base
51daf88, headc4e93bb), commit 1 being this PR's exact change plus the four-knob pin keyed off the server's repo name. This PR's seats and SEATSID OK do not transfer to the new head; #131 carries its own seat round and review. Closing unmerged.Pull request closed