Skip to content

MUL-5265: GitHub API-snapshot PR cards — CI status + mergeability#5889

Merged
Bohan-J merged 4 commits into
mainfrom
agent/j/c94b5693
Jul 24, 2026
Merged

MUL-5265: GitHub API-snapshot PR cards — CI status + mergeability#5889
Bohan-J merged 4 commits into
mainfrom
agent/j/c94b5693

Conversation

@Bohan-J

@Bohan-J Bohan-J commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Implements MUL-5265 — PR cards now show two independent, honest facts, each tri-state, sourced from the GitHub API as the single source of truth (Plan C, decided in MUL-5180). Webhooks, page visits and a bounded TTL sweep are refresh triggers only; nothing is inferred from webhook payloads anymore. Builds on clean main after the #5855 revert and removes the old suite-level aggregation display path.

Architecture

server/internal/integrations/ghsnapshot/

  • client.go — App JWT → installation access token (cached per installation, early refresh) → GraphQL. Rate-limit aware. Private key and tokens are never logged.
  • snapshot.go — one pullRequest GraphQL query (cost 1): headRefOid, mergeable, mergeStateStatus, statusCheckRollup + every check/status context. Contexts are paginated and CheckRun/StatusContext are normalized into one per-check shape.
  • refresh.go — outbound queue with (installation, repo, PR) dedup + single in-flight per PR, bounded worker pool, Retry-After/rate-limit backoff, jitter; the head-SHA-guarded atomic batch replace; the bounded chase window; page-visit + TTL sweep triggers.

Snapshot is stored as an atomic batch replace per PR (guarded UPDATE of github_pull_request snapshot columns + full DELETE/INSERT of github_pull_request_check_run) in one transaction. ListPullRequestsByIssue now aggregates the run-level snapshot; the old check_suite write path, its queries and its tests are removed (check_suite/check_run/status webhooks are now pure triggers).

Acceptance criteria

# Criterion Where
1 head-SHA anti-stale write UpdateGitHubPRSnapshot ... WHERE head_sha=$head; TestApplySnapshotHeadSHAGuard (DB)
2 contexts pagination (>100) FetchPRSnapshot cursor loop; TestFetchPRSnapshotPaginatesContexts
3 limiting: dedup + single in-flight + concurrency + Retry-After Manager.Enqueue/process; TestEnqueueCoalesces, TestProcessRateLimitedSetsPause, TestGraphQLRateLimited, TestRateLimitFromResponse
4 clean degradation with no private key nil client ⇒ inert manager; TestManagerDisabledNoOps, TestNewClientFromEnv
5 statusCheckRollup=null ≠ passed; mergeable=UNKNOWN shows neither deriveChecksStatus/deriveMergeStatus; TestFetchPRSnapshotNullRollup, TestSnapshotDecided + frontend tests
6 key/token never logged errors/logs carry no secret; asserted in TestNewClientFromEnv
7 bounded chase + TTL (no unbounded polling) maxChaseAttempts, terminal-stop; TestScheduleChaseBounded
8 failure + conflict coexist; "Ready to merge" only on CLEAN two independent elements; frontend pull-request-status + pull-request-list tests

UI

Two independent elements shown only for open/draft PRs: CI (✓ all passed with count / ✗ N failed · names / ● x/y running / ○ no checks yet) and mergeability (✓ ready to merge — only when merge state is clean / ⚠ has conflicts / ⊘ blocked·behind·unstable). Progress strip removed. Stale snapshots grey out with a time hint. Four locales.

Verification

  • Backend: go build ./..., go vet, gofmt clean; ghsnapshot suite (incl. DB-backed head-SHA guard) and handler GitHub tests pass on a freshly-migrated DB.
  • Frontend: @multica/core 1032 and @multica/views 2941 tests pass; pnpm typecheck for core+views passes.
  • Docs: @multica/docs MDX compiles for all four locales (the only next build failure is the pre-existing, content-unrelated /404 prerender).

Deploy / config (operator action)

Requires a GitHub App private key in the server env (GITHUB_APP_ID + GITHUB_APP_PRIVATE_KEY) and read-only Pull requests + Checks + Commit statuses permissions, subscribed to Pull request, Check suite, Check run, Status. Each existing installation must approve the added permissions. Without the key the feature degrades off (card hides the CI/merge region); PR linking and merge→Done are unaffected. Docs updated in all four languages.

Note: the now-unused github_pull_request_check_suite / github_pending_check_suite tables are left in place (no destructive migration) and can be dropped in a follow-up.

@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
multica-docs Ready Ready Preview, Comment Jul 24, 2026 9:58am

Request Review

Bohan-J and others added 2 commits July 24, 2026 16:51
…265)

Fetch each linked PR's CI checks and mergeability from the GitHub GraphQL
API as the single source of truth (Plan C). Webhooks, page visits and a
bounded TTL sweep are refresh triggers only; nothing is inferred from
webhook payloads anymore.

Backend (server/internal/integrations/ghsnapshot):
- installation-token cache + GraphQL client (private key / tokens never logged)
- one paginated pullRequest query -> normalized per-check snapshot
- outbound queue: (installation,repo,PR) dedup + single in-flight per PR,
  bounded worker pool, Retry-After / rate-limit backoff, jitter
- head-SHA-guarded atomic batch replace (a slow response for an old head
  can never overwrite a newer head's snapshot)
- bounded chase window (30s->5m, stops on terminal/closed) + page-visit +
  TTL refresh; clean degradation when no App private key is configured

Removes the old suite-level webhook aggregation display path (query +
handlers + tests). check_suite / check_run / status are now pure triggers.

Frontend: PR card shows two independent tri-state elements (CI status +
mergeability). "Ready to merge" only when merge state is clean; no-checks
and unknown-mergeable never assert a positive verdict; progress strip
removed; four locales; stale marker.

Docs: github-integration + environment-variables (four languages) — now
required App private key, read-only Checks/Commit-statuses permissions,
new event subscriptions, capability boundaries and troubleshooting.

Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
…n mint

Address Elon's third-round review on the MUL-5265 PR snapshot pipeline.

Must-fix — migration built a non-concurrent index. The
github_pull_request_check_run table declared PRIMARY KEY (pr_id, ordinal)
inside CREATE TABLE, which builds a unique index synchronously and violates
the repo rule that every migration-created index (including on a new table)
use CREATE UNIQUE INDEX CONCURRENTLY in its own single-statement file. Split:
222 now creates the table without a primary key; new 223 adds the
(pr_id, ordinal) unique index CONCURRENTLY. The atomic delete-all/insert
write path already guarantees ordinal uniqueness, so a plain unique index is
sufficient; the index also serves the pr_id-prefix list aggregation and the
workspace/PR cleanup deletes.

Nit — token mint now singleflights per installation. installationToken
released the lock before minting, so the N workers of one installation could
mint N tokens on a cold cache or a simultaneous renew. Concurrent callers for
the same installation are now collapsed via singleflight into one HTTP mint;
added a -race concurrent-mint test asserting a single mint under 16 callers.

Verified: fresh DB migrates through 223 (table has no PK, concurrent unique
index present); ghsnapshot suite + new test pass under -race; migration lint
and handler github/workspace-delete tests pass; sqlc produced no diff;
go build / vet / gofmt / git diff --check clean.

Co-authored-by: multica-agent <github@multica.ai>
@Bohan-J
Bohan-J merged commit ecce589 into main Jul 24, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant