Why
#116 Phase 2 made environments fully workspace-scoped — switch a workspace, get a fresh empty environments list. That's correct for workspace-owned envs (per-project credentials) but wrong for shared ones (the "staging credentials" set used across every project).
Maintainer suggested: the workspace declares which environments it includes, instead of putting a sharing flag on each env. The workspace owns the inclusion list; environments stay pure data.
Proposal — workspace-side inclusion list
Environments live in a single shared store. Each workspace carries an includedEnvironmentIds array that picks which ones are visible while the workspace is active.
Operator surface
- Environments rail mode: lists every shared env; each row has a checkbox "Included in ". Toggling adds/removes the id from
includedEnvironmentIds.
- Workspace switcher menu: small "n envs" label on each workspace entry so the operator sees how many are in scope.
- New env created while in workspace X auto-adds to X's inclusion list (default-include for the workspace that created it).
- Globals (
bowire_global_vars) stay shared across every workspace — they're always-in-scope by definition.
Migration
- Phase 2 store at
bowire_ws_<id>_environments migrates by:
- Collecting every entry from every workspace's bucket.
- De-duplicating by name (later wins) into the shared
bowire_environments_shared.
- Setting each workspace's
includedEnvironmentIds to the ids that came from its own bucket.
- Marker
bowire_envs_shared_migrated_v1 prevents re-running.
Active-env resolution
getActiveEnvId() reads the workspace-scoped value as today.
- New constraint: the active id must be in
includedEnvironmentIds. If it isn't (e.g. the operator removed an env from the workspace's list while it was the active one), getActiveEnvId() returns '' and the UI shows "No environment".
Acceptance
Composes with
Out of scope
- Read-only / shared-by-other-workspace environments. A workspace either includes an env or doesn't; no inclusion-without-edit. If the operator wants read-only sharing, that's a separate roles/permissions concern.
- Org-level / team-level environments — workspaces are local-first today. Multi-tenant sharing is a different layer.
Why
#116 Phase 2 made environments fully workspace-scoped — switch a workspace, get a fresh empty environments list. That's correct for workspace-owned envs (per-project credentials) but wrong for shared ones (the "staging credentials" set used across every project).
Maintainer suggested: the workspace declares which environments it includes, instead of putting a sharing flag on each env. The workspace owns the inclusion list; environments stay pure data.
Proposal — workspace-side inclusion list
Environments live in a single shared store. Each workspace carries an
includedEnvironmentIdsarray that picks which ones are visible while the workspace is active.{ // Single shared store (no workspace prefix). "bowire_environments_shared": [ { "id": "env_staging", "name": "Staging", "vars": { "API_KEY": "…" } }, { "id": "env_production", "name": "Production", "vars": { "API_KEY": "…" } }, { "id": "env_demo", "name": "Demo", "vars": { "API_KEY": "…" } } ], // Workspace declares which envs it includes. "bowire_workspaces": [ { "id": "personal", "name": "Personal", "includedEnvironmentIds": ["env_staging", "env_demo"] }, { "id": "ws_payments", "name": "Payments", "includedEnvironmentIds": ["env_staging", "env_production"] } ] }Operator surface
includedEnvironmentIds.bowire_global_vars) stay shared across every workspace — they're always-in-scope by definition.Migration
bowire_ws_<id>_environmentsmigrates by:bowire_environments_shared.includedEnvironmentIdsto the ids that came from its own bucket.bowire_envs_shared_migrated_v1prevents re-running.Active-env resolution
getActiveEnvId()reads the workspace-scoped value as today.includedEnvironmentIds. If it isn't (e.g. the operator removed an env from the workspace's list while it was the active one),getActiveEnvId()returns''and the UI shows "No environment".Acceptance
includedEnvironmentIdslives on each workspace entry.getEnvironments()filters to the active workspace's inclusion list.saveEnvironments()writes to the shared store; per-workspace mutation only touchesincludedEnvironmentIds.Composes with
getMergedVars()which already routes throughgetEnvironments(), so this composes for free.Out of scope