refactor(forge): cut forge credentials over to 2 GitHub Apps + Linear OAuth, drop all PATs (RIG-3090) - #827
Merged
mattwilkinsonn merged 2 commits intoSep 1, 2026
Conversation
… OAuth, drop all PATs (RIG-3090) Moves the whole forge credential model onto App/OAuth identities and deletes every personal-access-token path, in one cutover. End state: two GitHub App identities plus one Linear OAuth app, zero PAT secret names anywhere in the write or Linear path. This is the combined code of design tasks T2 (author writes onto the primary App), T3 (reviewer App), and T4 (drop the PATs; Linear actor=app) from the frozen record `docs/designs/server/compass-forge-app-credentials/design.md`. Deployment, IaC, live-oracle, and webhook-runbook work stay in their own issues (RIG-3094 T3.5, RIG-3095 T4-IaC, RIG-3096 T5, RIG-3098 T6). ### Two GitHub Apps - **Primary App** (`ForgeConfig.App`) serves everything but reviews: board reads, notify reads, author writes, board, and webhooks. - **Reviewer App** (new `ForgeConfig.ReviewerApp`) serves only the reviewer write client — a distinct GitHub identity so an agent approving a PR it authored dispatches `submit_review` on a different account, dissolving the author-cannot-approve-own-PR 422 at the credential layer (F1). - Author writes **reuse the shared primary App `*forge.GitHub` client** built for board/notify reads (RIG-2991), not a fresh client over the same token source: the client-side rate-budget/`resetAt` gate is per-client, so one client keeps reads and author writes on one budget gate against the single installation. The reviewer leg builds its own App token source + client. ### Linear OAuth (actor=app) - Linear write and notify lanes ride **one shared `linearagent.TokenSource`** (client-credentials, the production "Compass" app), built once in `Serve` and passed to both build sites — the one-instance rule (Linear revokes a client-credentials app's tokens on a scope-set change; the mint singleflight coalesces only within an instance). - A boot-time `Token(ctx)` mint check fails `Serve` fast on a bad pair or a disabled client-credentials toggle, rather than on the first write. ### Gate re-key - `forgeWritesEnabled` now keys on "both Apps configured" (each `AppID != 0` and its key secret declared), replacing the two-PAT-names predicate. Enabling writes therefore requires the primary App, which force-enables board ingestion — the unified shape (DEC-1/DEC-3, DL-305), amending the earlier independent-gates ruling. - `warnPartialForgeWriteSecrets` re-keyed to the App-based partial (exactly one App configured warns once). ### PATs deleted - Removed `GITHUB_FORGE_TOKEN`, `GITHUB_FORGE_REVIEWER_TOKEN`, `LINEAR_FORGE_TOKEN` secret names — fields, flags, env, defaults — and the now-dead `forgeTokenSource` type. New flags: `--forge-reviewer-app-id`/`-installation-id`/`-key-secret`, `--forge-linear-client-id`/`-client-secret` (with `$COMPASS_FORGE_*` env precedence). ### Tests - `TestForgeWriteAppsGate` (App-based enablement, incl. the force-couples-board-ingestion case), `TestBuildLinearNotifyLaneGate` and `TestBuildLinearTokenSourceGate` (token-source gating + the partial-config Warn), `TestWarnPartialForgeWriteSecrets` (App roles), `main_forge_test.go` reviewer + Linear flag mapping. - `TestForgeLanesShareOneBudgetGate` extended: the author write leg rides the armed shared client and fast-fails `ErrBudgetExhausted` with zero extra HTTP calls — the regression guard proving author writes and reads share one budget gate (not a separate one a fresh client would carry). No `panic`; every credential validated at boot (fail-fast). No fallback PAT path. Refs RIG-3090 Co-authored-by: Matt Wilkinson <matt@rigel.build>
|
Compass engineering docs preview: https://compass-forge-rig-3090-app-c.compass-eng-docs.pages.dev Deployed from |
…c (RIG-3090) Review-fix commit on the App-credential super-cutover (PR #827). - Bound the shared Linear `linearagent.TokenSource` HTTP client to a 30s timeout, matching NewGitHub/appTokenSource. The boot-time mint check ran an unbounded `Token(ctx)` over `http.DefaultClient` (no timeout) with the process-root ctx (no deadline), so a half-open TCP to api.linear.app could wedge Serve's whole boot — defeating the fail-fast the check exists for. The bound also caps the same instance the notify lane + write coordinate reuse. - Rewrite the `forgeTokenTTL` doc comment: it still described the deleted `forgeTokenSource` (a driver TokenSource with poll-pass re-resolve and Invalidate-on-401), none of which applies to its sole remaining consumer, the cachedWebhookSecret hot-path cache. Documents its real use. Refs RIG-3090 Co-authored-by: Matt Wilkinson <matt@rigel.build>
rigel-mintaka
marked this pull request as ready for review
September 1, 2026 03:06
mattwilkinsonn
approved these changes
Sep 1, 2026
mattwilkinsonn
deleted the
compass-forge/rig-3090-app-credential-cutover
branch
September 1, 2026 03:50
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Moves the whole forge credential model onto App/OAuth identities and deletes every personal-access-token path, in one cutover. End state: two GitHub App identities plus one Linear OAuth app, zero PAT secret names anywhere in the write or Linear path.
This is the combined code of design tasks T2 (author writes onto the primary App), T3 (reviewer App), and T4 (drop the PATs; Linear actor=app) from the frozen record
docs/designs/server/compass-forge-app-credentials/design.md. Deployment, IaC, live-oracle, and webhook-runbook work stay in their own issues (RIG-3094 T3.5, RIG-3095 T4-IaC, RIG-3096 T5, RIG-3098 T6).Two GitHub Apps
ForgeConfig.App) serves everything but reviews: board reads, notify reads, author writes, board, and webhooks.ForgeConfig.ReviewerApp) serves only the reviewer write client — a distinct GitHub identity so an agent approving a PR it authored dispatchessubmit_reviewon a different account, dissolving the author-cannot-approve-own-PR 422 at the credential layer (F1).*forge.GitHubclient built for board/notify reads (RIG-2991), not a fresh client over the same token source: the client-side rate-budget/resetAtgate is per-client, so one client keeps reads and author writes on one budget gate against the single installation. The reviewer leg builds its own App token source + client.Linear OAuth (actor=app)
linearagent.TokenSource(client-credentials, the production "Compass" app), built once inServeand passed to both build sites — the one-instance rule (Linear revokes a client-credentials app's tokens on a scope-set change; the mint singleflight coalesces only within an instance).Token(ctx)mint check failsServefast on a bad pair or a disabled client-credentials toggle, rather than on the first write.Gate re-key
forgeWritesEnablednow keys on "both Apps configured" (eachAppID != 0and its key secret declared), replacing the two-PAT-names predicate. Enabling writes therefore requires the primary App, which force-enables board ingestion — the unified shape (DEC-1/DEC-3, DL-305), amending the earlier independent-gates ruling.warnPartialForgeWriteSecretsre-keyed to the App-based partial (exactly one App configured warns once).PATs deleted
GITHUB_FORGE_TOKEN,GITHUB_FORGE_REVIEWER_TOKEN,LINEAR_FORGE_TOKENsecret names — fields, flags, env, defaults — and the now-deadforgeTokenSourcetype. New flags:--forge-reviewer-app-id/-installation-id/-key-secret,--forge-linear-client-id/-client-secret(with$COMPASS_FORGE_*env precedence).Tests
TestForgeWriteAppsGate(App-based enablement, incl. the force-couples-board-ingestion case),TestBuildLinearNotifyLaneGateandTestBuildLinearTokenSourceGate(token-source gating + the partial-config Warn),TestWarnPartialForgeWriteSecrets(App roles),main_forge_test.goreviewer + Linear flag mapping.TestForgeLanesShareOneBudgetGateextended: the author write leg rides the armed shared client and fast-failsErrBudgetExhaustedwith zero extra HTTP calls — the regression guard proving author writes and reads share one budget gate (not a separate one a fresh client would carry).No
panic; every credential validated at boot (fail-fast). No fallback PAT path.Refs RIG-3090
Co-authored-by: Matt Wilkinson matt@rigel.build