Project environments¶
Slice L introduced first-class environments to the Secrets Bridge data
model. This page explains what they are, the non_prod / prod
distinction that gates direct reveal, and how to wire them up
operationally.
What an environment is¶
An environment is a lifecycle boundary inside a project — dev,
staging, uat, prod, or any other name your team uses. The
schema (api/pkg/storage/migrations/0001_initial_schema.up.sql) has
carried environments since v0.1; Slice L1 added three classifying
fields:
| Column | Purpose |
|---|---|
kind |
Hard safety boundary. non_prod or prod. The PolicyEngine refuses to honour direct_reveal_allowed=true against kind='prod' regardless of policy or permission. PROD direct-reveal is impossible by construction. |
risk_level |
SMALLINT 0-4. Future fine-grain policy knob; today it drives a UI badge intensity only. |
description |
Operator notes. No semantic load. |
The existing type column (dev / staging / uat / prod / other) stays —
operators use it as a free lifecycle label that doesn't have to
agree with kind. The two are independently mutable for the case
where a staging-labelled env actually carries prod data: set
type='staging' but kind='prod' and the PROD invariant fires.
kind vs type — why both¶
The free-string type was useful as a lifecycle label, but it was
also doing double duty as the implicit risk signal. Operators
naturally write type='uat' for non-production environments — and a
single typo or rename can silently un-gate a sensitive flow. kind
is the hard, two-value classifier that the engine actually consults.
A reasonable mental model:
typeis what the env is called.kindis what the engine treats it as.
Operators who want to demote a prod-looking environment (e.g. a load
test env that mirrors prod schema but holds dummy data) can set
type='prod' and kind='non_prod'. The PolicyEngine then permits
direct reveal there even though the lifecycle label says prod.
Backfill rule¶
Migration 0022_environments_kind_classification runs the only safe
automatic mapping on existing rows:
UPDATE environments SET kind = CASE
WHEN type = 'prod' THEN 'prod' ELSE 'non_prod'
END WHERE kind IS NULL;
Anything labelled type='prod' becomes kind='prod'; everything
else lands in non_prod. Operators who labelled a true-prod
environment with a non-prod type MUST correct via
PUT /api/v1/environments/:id after running the migration.
API surface¶
| Endpoint | Auth | Purpose |
|---|---|---|
POST /api/v1/environments |
admin | Create an env. kind derives from type when omitted. |
GET /api/v1/environments |
session | Flat list across projects. |
GET /api/v1/environments/:id |
session | Single env. |
PUT /api/v1/environments/:id |
admin | Update description + risk_level ONLY. kind and name are immutable post-creation. |
DELETE /api/v1/environments/:id |
admin | Hard delete. Cascades through env_id FKs. |
GET /api/v1/projects/:id/environments |
session | List per project. |
Why kind and name are immutable¶
Once an environment row exists, kind and name are pinned. Two
reasons:
- The PolicyEngine caches resolution results keyed by environment;
flipping
kindafter a policy decision has been audited would create a discrepancy between the audit log and the live classification. - An operator who can flip a
prodenv tonon_prodafter grants exist could un-gate the direct-reveal path without anyone noticing.kindimmutability + the PolicyEngine PROD invariant are belt-and-braces for the same threat.
Operators who need to change kind must DELETE + recreate. The FK
cascade through access_requests / secret_wraps / project_secrets
ensures no orphaned bindings survive.
Operational checklist for new environments¶
- Create the env via
POST /environments: - Bind the secrets the env should expose via
POST /projects/:project_id/secretswith the newenvironment_idfield set (Slice L3). The legacysecrets.labels.Envlabel is no longer the authorization key —project_secrets.environment_idis. - Add a
policy_rulesrow scoped to this env that picks the right workflow. See Policy templates for the canonical shapes. - (Optional) Grant the
secret.reveal.directpermission to thedeveloperrole (it's seeded by default; revoke if you want a stricter baseline). See Authentication — direct reveal permission.
Related¶
- Policy templates — non_prod / prod policy rule examples.
- Authentication —
secret.reveal.directpermission + the L4 dev endpoints. - API reference:
GET /users/me/projectsreturns each project with its environments inline (Slice L4).