Approval Model
This page explains what approval means in the current repo and what it changes.
Studio-side approval boundary
For the browser workflow, approval happens through the Studio workspace approval route:
POST /api/dashboard/studio/workspaces/:workspace_id/approve
That route is the Studio-side write boundary for approval. It:
- authenticates using middleware-injected session headers
- rejects cross-site browser writes
- applies per-tenant rate limiting and RBAC
- enforces strict request-body validation
- delegates the real approval work to
approveActiveDraft()
If you are reasoning about browser approval, start there.
Preconditions for approval
The Studio approval path fails closed. Approval only succeeds when all of these are true:
- the workspace exists for the tenant
- the workspace has an active draft and active snapshot
- workspace and draft agree on the active snapshot
- the snapshot exists and matches the draft version
- the snapshot status is
ready_for_approval - persisted
approval_state.ready_for_approvalistrue blocker_idsis empty
If any of those checks fails, approval is rejected instead of inferred.
What approval creates
A successful Studio approval creates:
- one immutable
GovernanceApproval - one linked
CompiledGovernanceArtifact - an updated
GovernanceWorkspace.active_approval_id
The approval proof includes explicit metadata such as:
- actor id
- channel (
studio_uiorapi) - draft id
- snapshot id
- draft version
source_snapshot_hashcommand_history_hash- blocker ids
- advisory ids
- optional rationale
The compiled artifact stores deterministic compiled output for review/runtime use, including:
compiled_hashreview_projection_jsonruntime_projection_json- optional compatibility projection
Idempotency and concurrency
The approval path is concurrency-aware.
Important behaviors:
- if the same snapshot was already approved, the API returns the existing approval/artifact
- approval uses a compare-and-set style lineage barrier on workspace pointers
- unique constraints on snapshot and approval version prevent duplicate writes
- same-snapshot races recover by reading the winning approval
- real lineage movement is treated as stale lineage, not silently merged
In practice, approval is idempotent for the same snapshot and fails closed when the draft moved underneath the approver.
Approval does not collapse every governance object into one row
The current repo has two durable governance strata:
-
Studio approval objects
GovernanceWorkspaceBusinessDraftBusinessDraftSnapshotGovernanceApprovalCompiledGovernanceArtifact
-
Runtime plan objects
GovernancePlancompiled_sessioncompiled_hash- plan status (
LEARNING,READY,APPROVED,ENFORCING)
That matters because the zero-config runtime path fetches GovernancePlan from:
GET /api/v1/governance/plan?agent=<agent>&environment=<env>
So in the current implementation:
- Studio approval creates Studio approval/artifact rows
- zero-config runtime attachment still reads
GovernancePlan
That split is real in the repo today and is worth knowing when debugging authority behavior.
Runtime effect by plan state
For the zero-config runtime path:
LEARNING/READY: review-first state, no approved runtime artifact yetAPPROVED/ENFORCING: runtime may receivecompiled_session+compiled_hash
If a plan is approved but missing its runtime artifact, the hosted plan endpoint returns:
APPROVED_ARTIFACT_INVALIDwith HTTP422
That is a hard failure, not a soft warning.
Runtime effect by environment
When zero-config governance resolves successfully:
developmentmaps to monitor-style runtime behaviorstagingmaps to protect-level runtime behavior with advisory compatibility handlingproductionmaps to govern-level runtime behavior
When governance is unavailable:
developmentstays non-blockingstagingandproductionfail closed
What to check when approval looks wrong
If approval is not behaving the way you expect, check in this order:
- Is the Studio snapshot actually
ready_for_approval? - Are
blocker_idsempty? - Do workspace and draft still point at the same active snapshot?
- Did approval create both a
GovernanceApprovaland aCompiledGovernanceArtifact? - Is the runtime still reading a separate or older
GovernancePlanobject?