Skip to main content

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_approval is true
  • blocker_ids is 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_ui or api)
  • draft id
  • snapshot id
  • draft version
  • source_snapshot_hash
  • command_history_hash
  • blocker ids
  • advisory ids
  • optional rationale

The compiled artifact stores deterministic compiled output for review/runtime use, including:

  • compiled_hash
  • review_projection_json
  • runtime_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:

  1. Studio approval objects

    • GovernanceWorkspace
    • BusinessDraft
    • BusinessDraftSnapshot
    • GovernanceApproval
    • CompiledGovernanceArtifact
  2. Runtime plan objects

    • GovernancePlan
    • compiled_session
    • compiled_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 yet
  • APPROVED / ENFORCING: runtime may receive compiled_session + compiled_hash

If a plan is approved but missing its runtime artifact, the hosted plan endpoint returns:

  • APPROVED_ARTIFACT_INVALID with HTTP 422

That is a hard failure, not a soft warning.


Runtime effect by environment

When zero-config governance resolves successfully:

  • development maps to monitor-style runtime behavior
  • staging maps to protect-level runtime behavior with advisory compatibility handling
  • production maps to govern-level runtime behavior

When governance is unavailable:

  • development stays non-blocking
  • staging and production fail closed

What to check when approval looks wrong

If approval is not behaving the way you expect, check in this order:

  1. Is the Studio snapshot actually ready_for_approval?
  2. Are blocker_ids empty?
  3. Do workspace and draft still point at the same active snapshot?
  4. Did approval create both a GovernanceApproval and a CompiledGovernanceArtifact?
  5. Is the runtime still reading a separate or older GovernancePlan object?