Approvals and human oversight
This page describes the approval workflow of the Navin Platform (human in the loop): who requests, who approves, how quorum and expiry apply, how an agent run waits for a decision, and every four eyes rule found in the code. Code: services/control-plane/src/eai/domains/approvals, migration sql/postgres/V041.
Version française : ../approvals.md
Lifecycle of an approval
| Status | Meaning |
|---|---|
pending | Waiting for decisions |
approved | The number of approvals reaches the quorum |
rejected | An approver rejected |
expired | expires_at passed without a final decision |
cancelled | Cancelled with POST /v1/approvals/{approval_id}/cancel |
- Request:
POST /v1/approvals(rolestenant_admin,developer,agent,security_officer,finops) with the action, the resource, a reason, aquorumfrom 1 to 10,approver_rolesand a durationexpires_in_seconds. Default duration:EAI_APPROVAL_TTL_SECONDS(86400 s). An idempotency key avoids duplicates. - Read:
GET /v1/approvals,GET /v1/approvals/{approval_id}(tenant members). - Decision:
POST /v1/approvals/{approval_id}/decidewithapproveorrejectand a comment. Admitted roles: theapprover_rolesof the request, elsetenant_admin,security_officer,developer,business_owner. - Expiry: the
approvalsworker job (EAI_WORKER_APPROVAL_EXPIRY_INTERVAL_SECONDS) moves overdue requests toexpired; a request read after its deadline is expired too.
Separation of duties
The eai.domains.approvals.service service refuses that the same person requests and approves:
- the requester, the user the agent acts for (
on_behalf_of) and everyexcluded_approversof the request never decide: 403separation_of_duties, denial audited (approval.decidedeny); - an approver decides only once: 409
already_decided; the quorum only counts distinct people; - a closed request answers 409
approval_closed; - an action that needs an approval checks its state (
require_approved): 403approval_requireduntil it isapproved.
Approval of an agent run
- The agent manifest lists its sensitive actions in
autonomy.human_approval_for(patternstool:<name>,agent:<name>,model:<name>or a bare name). - A matching
tool_call,subagent,model_calloractstep withoutapproval_idis recordedpendingand arun.<kind>approval is opened; an agent may also request anapprovalstep with its own quorum (run.approval). - The run moves to
waiting_approval. - On the decision, the run resumes (
running, reasonapproval_granted) or fails (failed, reasonapproval_rejectedorapproval_expired). - A step replayed with an
approval_idis accepted only when the approval isapprovedfor that run.
The OPA policy may also require an approval through an approval obligation (authentication-authorization.md), and the SOC may apply the REQUIRE_APPROVAL reaction.
Controlled promotions
Moving a resource to approved or active opens a promotion approval (eai.domains.approvals.promotion). Once approved, the platform checks the risk classification (a high risk system needs an approved risk assessment), runs the blocking evaluation suites of the target and applies the status only when everything passes. Retirements (deprecated, retired, blocked, suspended, restricted) stay immediate and audited. A model version goes through POST /v1/models/{model_id}/versions/{version}/approval.
Change path
Every change of a prompt, skill, connector, rule, model, policy, tool, agent or pack follows the stages draft, test, security_review, business_approval, staging, production, monitoring (sql/postgres/V192, GET /v1/marketplace/changes, POST /v1/marketplace/changes/{change_id}/advance).
| Transition | Condition |
|---|---|
security_review to business_approval | Approval change.security_review by a security officer |
business_approval to staging | Approval change.business_approval by a tenant administrator |
staging to production | Staging smoke tests passed, then approval change.production by two people |
The author never approves; every transition is kept in history and audited.
Four eyes rules
| Workflow | Rule | Code or route |
|---|---|---|
| Break glass | Two distinct approvers (security_officer or tenant_admin), never the requester nor the beneficiary | POST /v1/break-glass |
| Scheduled agent mandate | Approved by someone other than the requester | POST /v1/mandates |
| Recovery after a kill switch | Two distinct SOC leads, other than the requester, then reauthorization | POST /v1/soc/recoveries/{recovery_id}/approve |
| Containment order | EMERGENCY_LOCKDOWN and SUSPEND_TENANT always, manual order above a blast radius of 25: second person | POST /v1/soc/orders/{order_id}/approve |
| Release of an audit legal hold | Two distinct approvers, each with a reason (409 audit_hold_same_approver) | POST /v1/evidence/holds/{hold_id}/release |
| Risk exception | The named approver is not the author (409 separation_of_duties) | POST /v1/risk/exceptions |
| Backup restore | Two approvers other than the requester (Navin Manager rule) | POST /v1/platform-manager/backup/restores/{plan_id}/approve |
| DR exercise | Approval in the Navin Manager, self approval refused | POST /v1/platform-manager/dr/exercises/{exercise_id}/approve |
| Production release of a change | Approval change.production by two people | POST /v1/marketplace/changes/{change_id}/advance |
Evidence
- Every creation, decision, denial, cancellation and expiry is an audit event (
approval.*actions, forwarded to the SIEM by default withEAI_SIEM_FORWARD_ACTIONS). - Individual decisions are in
eai.approval_decisions(approver, decision, comment). - Audit events written under a verified identity context carry
approvals.approval_ids(identity-context-delegation.md). - The
EAI-HUM-01control of the risk catalogue ("Human oversight and approvals for sensitive actions") hasapprovalsas its evidence source (risk-compliance.md).