Skip to content

refactor(forge): cut forge credentials over to 2 GitHub Apps + Linear OAuth, drop all PATs (RIG-3090) - #827

Merged
mattwilkinsonn merged 2 commits into
mainfrom
compass-forge/rig-3090-app-credential-cutover
Sep 1, 2026
Merged

refactor(forge): cut forge credentials over to 2 GitHub Apps + Linear OAuth, drop all PATs (RIG-3090)#827
mattwilkinsonn merged 2 commits into
mainfrom
compass-forge/rig-3090-app-credential-cutover

Conversation

@rigel-mintaka

Copy link
Copy Markdown
Contributor

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

… 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>
@linear-code

linear-code Bot commented Sep 1, 2026

Copy link
Copy Markdown

RIG-3090

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-forge-rig-3090-app-c.compass-eng-docs.pages.dev

Deployed from compass-forge/rig-3090-app-credential-cutover at 25aa03a.

…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
rigel-mintaka marked this pull request as ready for review September 1, 2026 03:06
@mattwilkinsonn
mattwilkinsonn merged commit 0802f35 into main Sep 1, 2026
14 checks passed
@mattwilkinsonn
mattwilkinsonn deleted the compass-forge/rig-3090-app-credential-cutover branch September 1, 2026 03:50
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.

2 participants