⚠️ Definition of Done: this issue must be completed in full, in a single PR. Do not split this
work across multiple PRs, and do not defer any Deliverable below to a follow-up issue. A PR that
satisfies only some of the Deliverables, stubs a required test, or leaves a checkbox
partially-done does NOT resolve this issue and will be closed.
Context
src/api/routes.ts's canSessionAccessPath (lines 6097-6124) is a global pre-route allowlist that
runs in middleware (line 1207) for every non-operator browser session, before any route handler
executes. Three routes call requireRepoMaintainer/requireRepoWriteAccess internally (implying
they're meant to be reachable by a maintainer's browser session) and each carries a doc comment
claiming parity with an already-allowlisted sibling route — but none of the three has a matching
predicate in canSessionAccessPath:
automation-state (line 2874) — comment says "Maintainer-gated like /settings" (line 2872);
/settings has isRepoSettingsPath in the allowlist, automation-state does not.
ams-miner-cohort (line 3133) — comment says "mirrors maintainer-noise above" (line 3131);
maintainer-noise has isRepoMaintainerNoisePath in the allowlist, ams-miner-cohort does not.
pulls/:number/chat-qa (line 3331) — comment explicitly says it "exposes ... to
apps/loopover-ui's maintainer panel" (line 3323). Confirmed live:
apps/loopover-ui/src/components/site/app-panels/chat-qa-panel.tsx:59-67 calls this exact
endpoint with credentials: "include" — real browser-session usage, not just a doc claim.
All three routes carry a /* v8 ignore next -- unauthorized requests are rejected by the auth middleware before reaching the handler. */ comment (lines 2877, 3136) asserting the
gate instanceof Response branch is unreachable — but for a real non-operator maintainer session,
it's the opposite branch (the coarse 403 from the missing allowlist entry) that fires, so the route
handler is never reached at all.
Verified untested: test/integration/api.test.ts:989-991 only exercises automation-state via a
static bearer token, never a session cookie. test/unit/maintainer-chat-qa.test.ts uses
createSessionForGitHubUser but only against /v1/app/maintainer-dashboard, never against the
chat-qa POST route itself. test/unit/access-boundary.test.ts — the exact regression-test file
for this class of bug (its own comment: "Before the fix this returned 403 insufficient_role at the
session allowlist" for validate-linked-issue/agent/pending-actions) — has no case for any of
these 3 routes.
Consequence: a logged-in maintainer using a real browser session (not the internal operator bearer
token) gets 403 insufficient_role on automation-state and ams-miner-cohort, and — critically —
using the Chat Q&A panel in the actual web dashboard fails outright for every maintainer.
Requirements
- Add a matching predicate for each of the 3 routes to
canSessionAccessPath, mirroring the pattern
already used for isRepoSettingsPath (for /settings) and isRepoMaintainerNoisePath (for
maintainer-noise).
- Do not weaken the existing allowlist for any other route while making this change.
Deliverables
All four Deliverables are required in the same PR.
Test Coverage Requirements
src/** is measured by codecov/patch (99%+ target, branch-counted). Each new test must exercise
both the "own repo" (allowed) and "other repo" (still rejected) branches for all 3 routes, matching
access-boundary.test.ts's existing template exactly.
Expected Outcome
A maintainer using a real browser session (not an internal operator token) can reach
automation-state, ams-miner-cohort, and the Chat Q&A panel for their own repo, matching what each
route's own doc comment already claims should work.
Links & Resources
src/api/routes.ts:6097-6124 (canSessionAccessPath), :1207 (middleware call site)
src/api/routes.ts:2872-2877 (automation-state), :3131-3136 (ams-miner-cohort), :3323-3331
(pulls/:number/chat-qa)
apps/loopover-ui/src/components/site/app-panels/chat-qa-panel.tsx:59-67 (real browser call site)
test/unit/access-boundary.test.ts (existing regression-test file and template for this exact bug
class)
- See the related issue filed alongside this one for
registration-readiness and
gittensor-config-recommendation (a lower-confidence variant of the same root cause)
Context
src/api/routes.ts'scanSessionAccessPath(lines 6097-6124) is a global pre-route allowlist thatruns in middleware (line 1207) for every non-operator browser session, before any route handler
executes. Three routes call
requireRepoMaintainer/requireRepoWriteAccessinternally (implyingthey're meant to be reachable by a maintainer's browser session) and each carries a doc comment
claiming parity with an already-allowlisted sibling route — but none of the three has a matching
predicate in
canSessionAccessPath:automation-state(line 2874) — comment says "Maintainer-gated like/settings" (line 2872);/settingshasisRepoSettingsPathin the allowlist,automation-statedoes not.ams-miner-cohort(line 3133) — comment says "mirrors maintainer-noise above" (line 3131);maintainer-noisehasisRepoMaintainerNoisePathin the allowlist,ams-miner-cohortdoes not.pulls/:number/chat-qa(line 3331) — comment explicitly says it "exposes ... toapps/loopover-ui's maintainer panel" (line 3323). Confirmed live:
apps/loopover-ui/src/components/site/app-panels/chat-qa-panel.tsx:59-67calls this exactendpoint with
credentials: "include"— real browser-session usage, not just a doc claim.All three routes carry a
/* v8 ignore next -- unauthorized requests are rejected by the auth middleware before reaching the handler. */comment (lines 2877, 3136) asserting thegate instanceof Responsebranch is unreachable — but for a real non-operator maintainer session,it's the opposite branch (the coarse 403 from the missing allowlist entry) that fires, so the route
handler is never reached at all.
Verified untested:
test/integration/api.test.ts:989-991only exercisesautomation-statevia astatic bearer token, never a session cookie.
test/unit/maintainer-chat-qa.test.tsusescreateSessionForGitHubUserbut only against/v1/app/maintainer-dashboard, never against thechat-qaPOST route itself.test/unit/access-boundary.test.ts— the exact regression-test filefor this class of bug (its own comment: "Before the fix this returned 403 insufficient_role at the
session allowlist" for
validate-linked-issue/agent/pending-actions) — has no case for any ofthese 3 routes.
Consequence: a logged-in maintainer using a real browser session (not the internal operator bearer
token) gets 403
insufficient_roleonautomation-stateandams-miner-cohort, and — critically —using the Chat Q&A panel in the actual web dashboard fails outright for every maintainer.
Requirements
canSessionAccessPath, mirroring the patternalready used for
isRepoSettingsPath(for/settings) andisRepoMaintainerNoisePath(formaintainer-noise).Deliverables
canSessionAccessPathallows a maintainer session to reachautomation-state.canSessionAccessPathallows a maintainer session to reachams-miner-cohort.canSessionAccessPathallows a maintainer session to reachpulls/:number/chat-qa.test/unit/access-boundary.test.ts, following its own establishedtemplate (session cookie via
createSessionForGitHubUser, asserting 200 for a maintainer'sown repo and 403
forbidden_repofor a different maintainer's repo), for all 3 routes.All four Deliverables are required in the same PR.
Test Coverage Requirements
src/**is measured bycodecov/patch(99%+ target, branch-counted). Each new test must exerciseboth the "own repo" (allowed) and "other repo" (still rejected) branches for all 3 routes, matching
access-boundary.test.ts's existing template exactly.Expected Outcome
A maintainer using a real browser session (not an internal operator token) can reach
automation-state,ams-miner-cohort, and the Chat Q&A panel for their own repo, matching what eachroute's own doc comment already claims should work.
Links & Resources
src/api/routes.ts:6097-6124(canSessionAccessPath),:1207(middleware call site)src/api/routes.ts:2872-2877(automation-state),:3131-3136(ams-miner-cohort),:3323-3331(
pulls/:number/chat-qa)apps/loopover-ui/src/components/site/app-panels/chat-qa-panel.tsx:59-67(real browser call site)test/unit/access-boundary.test.ts(existing regression-test file and template for this exact bugclass)
registration-readinessandgittensor-config-recommendation(a lower-confidence variant of the same root cause)