Skip to content

docs(product): forge scope enforcement — API writes + git operations (RIG-2679 B) - #601

Open
rigel-mintaka wants to merge 3 commits into
mainfrom
compass-server/rig-2679-forge-scope-enforcement
Open

docs(product): forge scope enforcement — API writes + git operations (RIG-2679 B)#601
rigel-mintaka wants to merge 3 commits into
mainfrom
compass-server/rig-2679-forge-scope-enforcement

Conversation

@rigel-mintaka

@rigel-mintaka rigel-mintaka commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

The deferred A8 forge scope control, in two enforcement legs sharing one per-account allowlist (account_forge_scopes).

A1-A4 — forge-API write chokepoint

requireForgeScope in every write arm of ExecuteForgeCallAsAccount — after coordinate resolution, on create arms after the F3 idempotency-memo check, before any provider call — rejecting out-of-scope targets as the in-band ForgeCallError{code:"not_found"}, byte-identical to the 403/404 flatten (no probe oracle). Gated by ForgeConfig.EnforceScopes: off for Dogfood, mandatory for Beta.

A5 / T4-T5 — git clone/push/pull scope (Matt's 2026-08-26 expansion)

Scope the credential, not the git call: mint a GitHub App installation token narrowed via the repositories field to the workstream repo + the account's granted repo set, so an out-of-scope git push/clone fails at GitHub itself — zero enforcement code inside the agent-controlled container. The self-clone invariant keeps the agent's own workstream repo always clonable (else provisioning succeeds but the agent is dead on arrival); zero/insufficient-grant provision fails loud. Both credential surfaces (.git-credentials + gh-CLI hosts.yml) carry the narrowed token from provision (t=0), not only after first refresh. A new T4.5 (proto) task carries the minted token across the Server→Runner process boundary (ProvisionAgentWorkspaceRequest carries no credential today) with a server-driven refresh push keyed by a per-live-container scheduler registry; T5 hardens refresh (margin, backoff, keep-old-token-on-failure, atomic rewrite, both surfaces).

Review history

Prior design-critic + code-review passes on the forge-API core (A1-A4) folded. Git-op leg (A5/T4/T4.5/T5) added; a design-critic red-team folded (self-clone invariant, Server→Runner wire path, loud PAT-fallback); then a three-round code-review loop reached all-clear at the high+medium floor — round 1 (1 high / 3 med / 2 low: SEA-1527 miscitation → OQ-8, hosts.yml refresh surface, refresh-scheduler state, read/write asymmetry, 404/403, owner-alias), round 2 (provision-time hosts.yml gap + co-resident gh-host preservation + projection phrasing), round 3 all-clear. Sub-floor items (same-host token precedence, citation normalization) dispositioned inline.

Open questions deferred to the freeze gate (Matt)

  • OQ-1 (load-bearing): enforcement default fail-open vs fail-closed.
  • OQ-6 (load-bearing): git-op mechanism (credential-narrowing rec) + PAT fallback (fine-grained-PAT option (c) + loud-Warn) + docs(product): rework forge agent-notification to webhook-only (RIG-2732) #634 sequencing sub-fork (recommend contingent split).
  • OQ-7 (load-bearing): git-op allowlist = write set + implicit workstream read, vs a distinct read superset (carries the clone-narrowing regression against SEA-1527 as first-class).
  • OQ-8 (load-bearing, new): where the workstream-repo input comes from — the git-op leg requires re-introducing a per-agent workstream-repo association SEA-1527 removed (provision/spawn field, agent_accounts column, or store-side spawn-target record); reverses a prior Matt ruling, so it is his call. Blocks T4/T4.5/T5.

Status: at freeze gate pending the OQ rulings above.

Ledger-impact: deferred to freeze (adds one Comms & tools row; amends DL-200's inherited no-scope-rejection A8 clause without superseding it). Driver assigns the row id at freeze (≥ DL-264 on current main).

Refs RIG-2679
Spec-impact: none

@linear-code

linear-code Bot commented Aug 25, 2026

Copy link
Copy Markdown

RIG-2679

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-server-rig-2679-forg.compass-eng-docs.pages.dev

Deployed from compass-server/rig-2679-forge-scope-enforcement at 40cd38d.

Changed pages:

rigel-mintaka added a commit that referenced this pull request Aug 25, 2026
…9 B)

Review PR #601 (2 low, advisory on a design record):
- L1: reword the Ledger-impact amend attribution — the A8 no-scope posture
  lives in DL-200's implementing comment (forge.go:16-18) tracing to the
  board 'Resolved decision 2' ruling, not in the DL-200 row text.
- L2: name the exhaustiveness test's residual (a mis-classified write arm)
  and add the T2 signature cross-check (read/unimplemented sets hold only
  no-caller handlers) that closes it structurally, in §A3 and T2.

Ledger-impact: deferred to freeze
…2679 B)

The deferred A8 forge-write scope control: a server-side per-account allowlist
(account_forge_scopes table) checked by requireForgeScope in every write arm of
ExecuteForgeCallAsAccount — after coordinate resolution, on create arms after
the F3 idempotency-memo check, before any provider call — rejecting out-of-scope
targets as the in-band ForgeCallError{code:"not_found"}, byte-identical to the
403/404 flatten (no probe oracle). Gated by ForgeConfig.EnforceScopes: off for
Dogfood, mandatory for Beta.

design-critic pass folded (6 findings). OQ-1 (fail-open vs fail-closed default)
deferred to Matt at freeze. Ledger row proposed (Ledger-impact declared; applied
at freeze), next free id DL-242.

Status: Draft.

Ledger-impact: deferred to freeze (adds one Comms & tools row; amends DL-200's
inherited no-scope-rejection A8 clause without superseding it).
…9 B)

Review PR #601 (2 low, advisory on a design record):
- L1: reword the Ledger-impact amend attribution — the A8 no-scope posture
  lives in DL-200's implementing comment (forge.go:16-18) tracing to the
  board 'Resolved decision 2' ruling, not in the DL-200 row text.
- L2: name the exhaustiveness test's residual (a mis-classified write arm)
  and add the T2 signature cross-check (read/unimplemented sets hold only
  no-caller handlers) that closes it structurally, in §A3 and T2.

Ledger-impact: deferred to freeze
@rigel-mintaka
rigel-mintaka force-pushed the compass-server/rig-2679-forge-scope-enforcement branch from 20634fb to f2900f5 Compare August 26, 2026 06:16
@rigelbuild-bot rigelbuild-bot changed the title docs(product): design server-side forge-write scope enforcement (RIG-2679 B) docs(product): forge scope enforcement — API writes + git operations (RIG-2679 B) Aug 26, 2026
@rigel-mintaka rigel-mintaka changed the title docs(product): forge scope enforcement — API writes + git operations (RIG-2679 B) docs(product): design server-side forge-write scope enforcement (RIG-2679 B) Aug 26, 2026
@rigel-mintaka
rigel-mintaka force-pushed the compass-server/rig-2679-forge-scope-enforcement branch 2 times, most recently from 84059e4 to e6b2a3e Compare August 26, 2026 06:42
…ecord (RIG-2679 B)

Folds Matt's 2026-08-26 scope expansion (scope the repos an agent can clone/push/pull, not just the forge-API write path) plus a design-critic red-team into the forge-scope-enforcement record.

### Git-operation scope (new A5 + Plan T4/T4.5/T5)

Scope the credential, not the git call: mint a GitHub App installation token narrowed via the `repositories` field to the workstream repo + the account's `account_forge_scopes` set, so an out-of-scope `git push`/`clone` fails at GitHub itself — zero enforcement code inside the agent-controlled container.

- **Self-clone invariant**: the agent's own workstream repo is always clonable/pullable (else provisioning succeeds but the agent is dead on arrival); push stays write-gated; extra repos need grants; zero/insufficient-grant provision fails LOUD.
- **T4.5 (new, proto)**: the minted token must cross the Server→Runner process boundary — `ProvisionAgentWorkspaceRequest` carries no credential today; adds a server-authoritative `WorkspaceCredential` field + a server-driven refresh push on the hub control channel.
- **T5 hardened refresh**: pre-expiry margin, retry-with-backoff, keep-old-token-on-failure, atomic tmp+mv rewrite (the `GHHostsScript` shape).

### Critic dispositions folded

Loud PAT-fallback Warn (classic vs fine-grained PAT distinction corrected), half-landed cross-check Warn, x-access-token username, deploy-key + fine-grained-PAT Alternatives dismissals.

### New load-bearing forks for the freeze gate

OQ-6 gains a fine-grained-PAT option (c) + a #634-sequencing sub-fork (iii, recommend contingent split); OQ-7 added (git-op allowlist = write set + implicit workstream read, vs a distinct read superset).

Spec-impact: none (design record only; Ledger-impact declared in-record, applied at freeze). Refs RIG-2679

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@rigel-mintaka
rigel-mintaka force-pushed the compass-server/rig-2679-forge-scope-enforcement branch from e6b2a3e to 40cd38d Compare August 26, 2026 06:49
@rigelbuild-bot rigelbuild-bot changed the title docs(product): design server-side forge-write scope enforcement (RIG-2679 B) docs(product): forge scope enforcement — API writes + git operations (RIG-2679 B) Aug 26, 2026
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