|
| 1 | +# Secrets & Environments Scoping — Design & Plan |
| 2 | + |
| 3 | +> Status: **Proposed** (no code changed yet). Authored 2026-06-02. |
| 4 | +> Authoritative target model: `docs/design/background-agent-platform/04-secrets-byok-litellm/secrets-management.md`, |
| 5 | +> `06-environments-and-docker/*`, `12-data-models/conceptual-schema.md`. |
| 6 | +> Executable task tracker: `.sisyphus/plans/secrets-and-environments-scoping-plan.md`. |
| 7 | +> Deep schema reference: `./target-architecture.md`. |
| 8 | +
|
| 9 | +This document is the front door for closing the gaps in **environment** and |
| 10 | +**secrets** management across the full scope hierarchy — **org → team → user → |
| 11 | +repo → environment → run**. It also covers **activating Teams in Better Auth**, |
| 12 | +which is a prerequisite for half of the missing secret scopes. |
| 13 | + |
| 14 | +--- |
| 15 | + |
| 16 | +## 1. Why this exists |
| 17 | + |
| 18 | +The platform today has the *coarse* half of the secret hierarchy (org + repo) |
| 19 | +with strong per-tenant crypto, but is missing the *fine* half (team, user, |
| 20 | +environment, run), any value-versioning / rotation history, any per-secret |
| 21 | +authorization, and the entire **environment** subsystem that an |
| 22 | +"environment-scoped secret" would even attach to. RBAC roles are defined but |
| 23 | +not wired in. Teams are disabled. |
| 24 | + |
| 25 | +The design pack (`docs/design/background-agent-platform/`) already specifies the |
| 26 | +target. This plan makes the codebase match it, in a safe, phased, zero-downtime |
| 27 | +order that **coordinates with the in-flight `multi-tenant-backend-confidence-plan`** |
| 28 | +(another agent is actively editing the same files — see §6). |
| 29 | + |
| 30 | +--- |
| 31 | + |
| 32 | +## 2. Current state (verified against code) |
| 33 | + |
| 34 | +| Area | What exists | Evidence | |
| 35 | +|---|---|---| |
| 36 | +| Tenancy root | `organization` + `member` (single `role` text) + `invitation` | `src/db/auth-schema.ts:137-191` | |
| 37 | +| Teams | **Disabled** | `src/lib/auth.ts:40` → `teams: { enabled: false }` | |
| 38 | +| RBAC roles | Defined (`ac`/`owner`/`admin`/`member`) but **never wired** into the org plugin | `src/lib/permissions.ts:1-42`; absent from `auth.ts:39-58` | |
| 39 | +| Context resolution | `{ userId, organizationId }` only; fail-closed on bad active org; auto-creates personal workspace | `src/lib/resolve-context.ts:5-65` | |
| 40 | +| Secrets storage | One table `tenant_secret`: org-level (`excludedRepos` blocklist) + repo-scoped (override) | `src/db/app-schema.ts:51-92` | |
| 41 | +| Secret resolution | `getEffectiveForRepo` = `(org − exclusions) + repo`, repo wins | `src/lib/secret-repo.ts:344-425` | |
| 42 | +| Secret crypto | Per-org AES-GCM-256, HKDF-derived key, IV+tag separate, `ENCRYPTION_KEY_PREVIOUS` rotation | `src/lib/crypto.ts`, `app-schema.ts:60-63` | |
| 43 | +| Secret authz | `requireAuth` only — **any** org member / valid org key can read-metadata, write, delete every org secret; no role gate | `src/index.ts:819-821` | |
| 44 | +| Secret audit | create/update/delete now wired | `src/index.ts:898` | |
| 45 | +| Environments | **None** — no entity, no autodiscovery; "environment" = repo/branch/model on the DO `session` row | grep empty | |
| 46 | +| BYOK delivery | Raw provider keys injected into sandbox env; no gateway/virtual key | `src/do/session-do.ts:1496,1521` | |
| 47 | +| Migrations | `wrangler d1 migrations apply` over `drizzle/*.sql`; **`_journal.json` is out of sync** (lists 0000-0003,0007; files have 0004-0008) | `drizzle/meta/_journal.json`, `drizzle/` | |
| 48 | + |
| 49 | +--- |
| 50 | + |
| 51 | +## 3. The scope matrix (the core gap) |
| 52 | + |
| 53 | +Design pack secret scopes: `org, team, repo, environment, run, user` |
| 54 | +(`secrets-management.md:34-41`). |
| 55 | + |
| 56 | +| Scope | Designed | Built | Blocked on | |
| 57 | +|---|:---:|:---:|---| |
| 58 | +| **org** | ✓ | ✓ | — | |
| 59 | +| **repo** | ✓ | ✓ | — (`excludedRepos` + override) | |
| 60 | +| **team** | ✓ | ✗ | Teams not enabled → no team entity | |
| 61 | +| **user** | ✓ | ✗ | Just unmodeled (`createdByUserId` is provenance, not a scope) | |
| 62 | +| **environment** | ✓ | ✗ | No `environment_profiles` entity to bind to | |
| 63 | +| **run** | ✓ | ✗ | No durable run / run-token object | |
| 64 | + |
| 65 | +**4 of 6 scopes missing, and they are the four most precise ones.** The |
| 66 | +missing scopes are blocked by missing *entities*, not just storage columns — so |
| 67 | +this is an architecture change, not a column add. |
| 68 | + |
| 69 | +--- |
| 70 | + |
| 71 | +## 4. Target architecture (summary) |
| 72 | + |
| 73 | +Full schemas, precedence rules, and RBAC model in **`./target-architecture.md`**. |
| 74 | +Headlines: |
| 75 | + |
| 76 | +1. **Teams (Better Auth)** — flip `teams.enabled`, regenerate `auth-schema.ts` |
| 77 | + (`team`, `teamMember` tables + `invitation.teamId`), wire the dead |
| 78 | + `permissions.ts` roles into the org plugin, extend `AppContext` with an |
| 79 | + optional `teamId`. |
| 80 | +2. **Generalized secret scope tuple** — evolve `tenant_secret` from two bespoke |
| 81 | + columns into `(scope_type, scope_id)` covering all six scopes, with one |
| 82 | + documented precedence chain replacing the two-layer merge. Absorbs the |
| 83 | + existing `repoOwner/repoName/excludedRepos` model losslessly. |
| 84 | +3. **Secret governance** — add `secret_versions` (value rotation history, distinct |
| 85 | + from the crypto `key_version`) and `secret_access_policies` (per-secret RBAC); |
| 86 | + add `secret` + `environment` as RBAC resources; split `read_metadata` vs |
| 87 | + `use_runtime`. |
| 88 | +4. **Environment entity** — add `environment_profiles` (+`_versions`, +`_issues`) |
| 89 | + per the pack, giving env-scoped secrets something to bind to and unblocking the |
| 90 | + `production secrets: false` style key restriction. Autodiscovery |
| 91 | + (Detective/Builder/Verifier) is a documented follow-on. |
| 92 | +5. **Run scope + BYOK gateway** — run-scoped, ephemeral secrets and a LiteLLM |
| 93 | + gateway / virtual-key layer are **cross-dependencies** on the run-token work |
| 94 | + already tracked in the architecture review's slices; this plan defines the |
| 95 | + schema seam and defers the gateway itself. |
| 96 | + |
| 97 | +--- |
| 98 | + |
| 99 | +## 5. Phased roadmap (execution order) |
| 100 | + |
| 101 | +Ordered so every phase leaves the app working and demoable, security/identity |
| 102 | +foundations first, the risky shared-table migration last (to clear the active |
| 103 | +agent's in-flight work). |
| 104 | + |
| 105 | +| Phase | Title | Depends on | Collision risk | |
| 106 | +|---|---|---|---| |
| 107 | +| **0** | Migration hygiene + coordination | — | reconcile `_journal.json`; agree migration ownership | |
| 108 | +| **1** | **Activate Teams in Better Auth** | 0 | low (auth-schema + auth.ts) | |
| 109 | +| **2** | Wire RBAC + `secret`/`environment` resources; gate routes by role | 1 | low | |
| 110 | +| **3** | Environment entity (`environment_profiles` + API + SDK + page) | 0 | none (new tables) | |
| 111 | +| **4** | Generalized secret scope model (team/user/env scopes, versions, policies, precedence, injection) | 1,2,3 + **active plan landed** | **HIGH** — shares `tenant_secret`/`secret-repo.ts`/`session-do.ts` | |
| 112 | +| **5** | Run-scoped secrets + BYOK gateway seam | 4 + run-token work | coordinate | |
| 113 | +| **6** | Observability/audit completeness + UI surfaces | 1-5 | low | |
| 114 | + |
| 115 | +Phase 4 is the keystone and is intentionally sequenced **after** the |
| 116 | +`multi-tenant-backend-confidence-plan` lands its repo-scoped secret work, to |
| 117 | +avoid two agents migrating the same table. |
| 118 | + |
| 119 | +--- |
| 120 | + |
| 121 | +## 6. Coordination with the active agent (READ FIRST) |
| 122 | + |
| 123 | +`boulder.json` shows agent `atlas` executing |
| 124 | +`.sisyphus/plans/multi-tenant-backend-confidence-plan.md`, which is **actively |
| 125 | +modifying** `src/db/app-schema.ts` (`tenant_secret`), `src/lib/secret-repo.ts`, |
| 126 | +`src/do/session-do.ts`, and adding D1 migrations. |
| 127 | + |
| 128 | +Hard rules to avoid corruption: |
| 129 | +- **Phases 0-3 are safe to start now** — they touch `auth-schema.ts`, |
| 130 | + `auth.ts`, `permissions.ts`, `resolve-context.ts`, and *new* env tables, none |
| 131 | + of which the active plan owns. (Phase 1 touches `resolve-context.ts`, which the |
| 132 | + active plan reads but does not migrate — diff, don't clobber.) |
| 133 | +- **Phase 4 MUST NOT start** until the active plan's repo-scoped secret tasks |
| 134 | + (T1-T3) are merged. Migrating `tenant_secret` concurrently from two agents will |
| 135 | + produce conflicting migrations and a broken journal. |
| 136 | +- **All migrations** must be reconciled through Phase 0 first; any new migration |
| 137 | + number must be claimed in `.sisyphus/notepads/` to prevent number collisions. |
| 138 | + |
| 139 | +--- |
| 140 | + |
| 141 | +## 7. Decisions to resolve |
| 142 | + |
| 143 | +These are flagged in the plan with recommended defaults; none block authoring. |
| 144 | + |
| 145 | +1. **Secret precedence on key collision** — proposed default (broad → wins): |
| 146 | + `org → team → repo → environment → user → run`, last wins. (See |
| 147 | + `target-architecture.md §4`.) Confirm before Phase 4. |
| 148 | +2. **`tenant_secret` migration shape** — scope-tuple (recommended, clean, |
| 149 | + extensible) vs additive nullable columns (lower-risk, messier). Recommend |
| 150 | + scope-tuple, executed as an additive-then-cutover migration. |
| 151 | +3. **Default team on org create** — auto-create a "Default" team per org in the |
| 152 | + `databaseHooks.user.create.after` path, or leave orgs teamless until first |
| 153 | + team is made? Recommend auto-create for a frictionless default. |
| 154 | +4. **Vault boundary** — stay D1-ciphertext (documented simplification) vs |
| 155 | + external vault. Recommend stay D1 now; revisit with the BYOK gateway (Phase 5). |
| 156 | +5. **`AppContext` shape** — add `teamId?: string` and thread an `activeTeamId` |
| 157 | + through sessions, or resolve team per-request from membership? Recommend |
| 158 | + `activeTeamId` on the Better Auth session, mirrored into `AppContext`. |
| 159 | + |
| 160 | +--- |
| 161 | + |
| 162 | +## 8. Files in this design set |
| 163 | + |
| 164 | +- `README.md` — this file (state, gap matrix, roadmap, coordination, decisions). |
| 165 | +- `target-architecture.md` — schemas, precedence semantics, RBAC model, env entity, contracts. |
| 166 | +- `.sisyphus/plans/secrets-and-environments-scoping-plan.md` — executable, task-tracked plan. |
0 commit comments