docs(product): forge scope enforcement — API writes + git operations (RIG-2679 B) - #601
docs(product): forge scope enforcement — API writes + git operations (RIG-2679 B)#601rigel-mintaka wants to merge 3 commits into
Conversation
|
Compass engineering docs preview: https://compass-server-rig-2679-forg.compass-eng-docs.pages.dev Deployed from Changed pages: |
…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
20634fb to
f2900f5
Compare
84059e4 to
e6b2a3e
Compare
…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>
e6b2a3e to
40cd38d
Compare
|
Heads up from RIG-2577 T10 (#832, now open): that PR removed the transitional This PR adds
Before merge, please re-point the record to |
…ve-family sweep (RIG-3113) The final task (T10) of the RIG-2577 design-docs reorg. T8 dissolved `product/` into `agent/`/`server/`/`ui/`/`meta/` but left the native-app family under the transitional `product/` governed root while RIG-2542's client-only direction settled. RIG-2542 is closed (Done, 2026-08-23) and T8/T9 have merged (#825), so this empties and removes `product/`. ## What moved The 8 remaining records `product/` → `ui/` (all UI/native-shell surface): - `compass-native-app/`, `compass-native-client-mode/`, `compass-native-client-only/`, `compass-native-packaging/` - `compass-multi-window/`, `compass-stack-cross-process-teardown/`, `compass-gtk4-migration/` - `compass-tauri-shell.md` (stays flat at the `ui/` root; the corpus's only supersession pointer) ## Re-points (same PR, per CONTRIBUTING §"Moving a record is not a freeze violation") - **Ledger:** 18 `DECISIONS.md` Record cells `product/<r>` → `ui/<r>`. - **Supersession pointer:** `compass-tauri-shell.md`'s `Status: Superseded by compass-native-app/design.md` is already the final same-bucket record-relative form; verified it resolves under the generalized `resolveRecordRelative` (no `../` escape) now that both endpoints sit in `ui/`. - **Cross-bucket citations to the 8:** `infra/ci/compass-agent-image-publish`, `infra/ci/compass-dogfood-e2e`, `infra/release/compass-release-bundling`, `infra/release/compass-distribution`, `infra/runtime/compass-runner-arbitrary-uid`, plus the two moved records' own self/sibling citations. - **Code/config:** `go/e2e/client_mode_test.go`, `tools/renovate/config.json5`. ## De-govern + delete - Removed `"product"` from `GOVERNED_ROOTS` in `tools/design-ledger-gate/index.ts` (now `[ui, agent, server, meta, infra, observability, repo]`) and updated its doc comment. - Deleted the now-empty `docs/designs/product/` directory. - Dropped CONTRIBUTING §6's `product/ is transitional` subsection and the "plus the transitional `product/` root" clause; the transitional state is over. - Updated the gate's own test fixtures (`index.test.ts`) that used `product/` as a governed-bucket stand-in to `ui/` (a de-governed bucket would flip their `touchesRecord(...).toBe(true)` assertions). ## Verification - `design-ledger-gate`: OK — 284 ledger rows, 120 record headers valid, exit 0. - Gate test suite: 81 pass / 0 fail. - eng-docs `gather`: 187 docs + index + sidebar, exit 0. - Relative `.md` link resolution across all 135 design records: 0 dangling. - The ~108 remaining `docs/designs/product/...` mentions are pre-existing historical prose in frozen records (old `product/DECISIONS.md` ledger path relocated at T2, deleted `compass-0.x` version records, the old `PRODUCT_DIR` gate constant, and the separate `docs/specs/product/` tree) — none reference a moved record, and rewriting frozen records' decision prose is out of scope. ## Coordination note Two open draft PRs add new NON-native records into `product/` against the pre-T8 flat taxonomy: #601 (`compass-forge-scope-enforcement` → belongs in `server/`) and #662 (`compass-first-run-tour` → belongs in `ui/`). Both must re-point their record to the correct bucket before merge (mis-filed against the dissolved taxonomy regardless). Flagged on both PRs. Refs RIG-3113. Refs RIG-2577. Ledger-impact: 18 Record-cell path re-points (product/ → ui/); no decision rows added, flipped, or removed. Spec-impact: none. Co-authored-by: Matt Wilkinson <matt@rigel.build>
The deferred A8 forge scope control, in two enforcement legs sharing one per-account allowlist (
account_forge_scopes).A1-A4 — forge-API write chokepoint
requireForgeScopein every write arm ofExecuteForgeCallAsAccount— after coordinate resolution, on create arms after the F3 idempotency-memo check, before any provider call — rejecting out-of-scope targets as the in-bandForgeCallError{code:"not_found"}, byte-identical to the 403/404 flatten (no probe oracle). Gated byForgeConfig.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
repositoriesfield to the workstream repo + the account's granted repo set, so an out-of-scopegit push/clonefails 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-CLIhosts.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 (ProvisionAgentWorkspaceRequestcarries 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)
agent_accountscolumn, 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