* feat(github): API-snapshot PR cards — CI status + mergeability (MUL-5265)
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>
* fix(github): address PR snapshot review blockers
Co-authored-by: multica-agent <github@multica.ai>
* fix(github): bound snapshot refresh scheduling
Co-authored-by: multica-agent <github@multica.ai>
* fix(github): concurrent check-run index migration + singleflight token 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>
---------
Co-authored-by: Bohan-J <bohan@devv.ai>
Co-authored-by: multica-agent <github@multica.ai>
* feat(vcs): gate self-hosted Git providers to self-host deployments only (MUL-3772)
The Forgejo/Gitea/GitLab integration is intended for self-hosted Multica, where
Multica can reach a Git instance on the operator's own network. On the managed
multi-tenant cloud it adds an SSRF surface (connect validates a user-supplied
instance URL from the server) and would store third-party Git tokens for all
tenants under one key, while only serving the small subset of users whose
instance is publicly reachable. Product decision: offer it on self-host only.
- Add an explicit deployment switch MULTICA_VCS_INTEGRATION_ENABLED (default
off). Connect, rotate, and webhook now require BOTH the switch on AND a valid
MULTICA_VCS_SECRET_KEY — the switch is the product boundary, not key presence
alone. When off, connect/rotate return 404 and the webhook returns a bare 404
(no config leak), independent of the frontend.
- /api/config exposes vcs_integration_available (mirrors the switch, omitted
when false) so the Settings UI hides the whole "Git providers" section on
cloud instead of surfacing an operator-only "missing key" hint.
- docker-compose.selfhost.yml defaults the switch on; .env.example documents it.
- Docs (en/zh) lead with a callout: available on self-hosted Multica only, not
Multica Cloud, and clarify "self-hosted" means Multica itself, not just Git.
#5006 / #5883 stay in place — the schema and backend capability are retained;
this only gates availability. No cloud VCS connection can exist (connect always
required the key, which the cloud never set), so nothing needs migrating.
Verified: go build/vet + VCS/config handler tests on a fresh migrated DB
(incl. a new disabled-deployment 404 test); pnpm typecheck (core + views) and
the integrations-tab + core schema/config vitest suites pass.
Co-authored-by: multica-agent <github@multica.ai>
* fix(vcs): complete self-host integration gating (MUL-5138)
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: Bohan-J <bohan@devv.ai>
Co-authored-by: multica-agent <github@multica.ai>
Adds self-hosted Git provider support (Forgejo, Gitea, GitLab) alongside GitHub:
per-workspace token connection, a provider-dispatched webhook, PR/MR and CI
mirroring, and the shared issue auto-link / auto-close machinery. Off until
MULTICA_VCS_SECRET_KEY is set, so existing deployments are unaffected.
Co-authored-by: Bohan <bohan@devv.ai>