fix(github): fence repository pagination to selected connection (#48)

Closed
TE
5d ago
+360-28

Changelog

Fixed: An interrupted GitHub repository listing can no longer advance its cursor onto a different installation when connections are added, revoked, reordered, or rescoped.

Change

  • Replace index-based {installation, page} cursors with bounded v2 opaque cursors encoding an exact selected connectionId, page, and SHA-256 fence.
  • Compute the fence from the authenticated principal, normalized query, current ordered connection identities, selected-connection state and team integration policy. A changed selection returns 409 cursor_stale and requests a fresh list.
  • Re-check policy/connection/current membership after the GitHub request on every page, including pages with no matching repositories. Reusing a stale cursor never silently changes accounts.
  • Keep the existing GitHub App, connection/token owner, auth, one-upstream-page limit, and response shape. Legacy index cursors return 400 and require a fresh list; clients already handle request.invalid for malformed cursors.

Regression

The first commit (dcbc9b820e3e97e558c50e7d8d2c3b76e26a4cd1) adds backend/apps/api/test/github-repos-cursor.test.ts: same-connection next page, next exact installation, changed order/revoke/insert, different principal/query/policy, invalid/oversized legacy cursors, and no connections. It initially fails to import the missing helper. The implementation is the next commit. Run cd backend && bun --filter @cmux/api test -- github-repos-cursor (or the matching backend Vitest invocation).

Observed / Not run

Observed in #27025 head cbbf1f3ca66063543866aa4bcdc4194686e7951e: the repo cursor used a numeric position in Object.values(state.connections); revocation/reordering can select another account mid-pagination. This PR is stacked on #27025 for an isolated review and changes two files. Live provider calls, deployment and account mutations were untouched. Local backend Vitest/TS typecheck are unavailable here; CI checks are the verification gate.

Related: cmux-platform issue #48. Stable account-aware resource-selection projection across the platform API remains with #48’s existing owner lane.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Fixes GitHub repository pagination so an interrupted listing can no longer silently switch to a different connection when connections are added, revoked, reordered, or rescoped.

  • Replaces index-based {installation, page} cursors with v2 opaque cursors that carry the exact selected connectionId, page, and a fence hash of the principal, query, policy, and ordered connection list.
  • Returns 409 cursor_stale when the fence changes, and 400 request.invalid for legacy or malformed cursors; clients already handle request.invalid.
  • Re-checks policy and connection membership after every GitHub request, including pages with no matching repositories.
  • Adds integration coverage that calls the authorized ConnectionDO paging method directly (the public route stays off in the test deployment) and verifies a stale cursor is refused before any further upstream call.