Approval policy and change tiers

Who may release what — the write floor, risk tiers L0–L3, the roles that decide, change classes, windows, the automatic lanes and every guard they check.

Reference · Updated 2026-10-28 · Describes release 0.1.164

This page is the one place that states who approves what. The how-to chapters describe the clicks and link here for the rules; when a rule on this page and a sentence elsewhere disagree, this page is the one checked against the code.

Related references: Changes reference (the change ledger, last-known-good records, rollback, harvest) and Compliance reference (controls, findings, risk decisions).

The write floor

Every call an agent makes that could change something pauses for a person. The gate is an allow-list of reads on top of default-deny: a call is free only when the classifier says it is a read; everything else waits for an approval. An incomplete rule costs one extra approval prompt, never a silent write. The credential that performs approved fixes (a write-role connection) is never visible to an agent at all.

Call Runs without approval Pauses for approval
API connector call (<name>_request, aws_request, azure_request, gcp_request, sentinel_query) Operations the read classifier lists for that connector type Every other operation, including writes sent through a read path
CLI connector call (<name>_exec) Read verbs; a connection's readonly_verbs may widen the read set for its own credential, never past the floor Any other verb
Network device (net_exec) show / display reads Configuration pushes
MCP server tools (<server>_<tool>) Tools the server annotates readOnlyHint: true, unless the tool name looks mutating Everything else (deny by default)
http_get Never — it is the exfiltration channel Always
web_fetch Domains listed in settings['web_fetch_allowlist'] Any other domain (an estate x-ray scan denies it instead of pausing)
web_search Always (the query is PII-scrubbed before it leaves) Never
lake_query Always — read-only by construction (single SELECT, no DDL, no file access) Never
change_lookup, service_graph, read_attachment, read_evidence Always (read tools) Never

Two more rules sit above the floor:

Rule Where it is set Effect
Extra approval gates Agents page, per pillar (approval_required, a list of fnmatch patterns; default []) A matching tool call pauses even when the floor would let it through. Nothing can remove the floor.
Computer-assist mode Guardrails, execution_mode: computer_assist The platform makes no write at all: no remediation, no rollback, no PR or ticket route. Plans are prepared for an operator to run outside the platform and report back.

Diagnosis is the posture everywhere else: reads flow, so an analysis is never blocked on a human; an estate x-ray scan is read-only by construction and refuses a write instead of pausing (the refusal is counted on the scan as denied_writes).

The approval gate

When a gated call is reached the run stops at status paused_approval and an approval request is written with the tool name, the agent's one-line description, the parameters and the exact invocation shown to the approver. The run resumes when every pending request on it is decided.

Fact Value
Decisions approved, denied, edited (approved with a human-modified command; a redacted command cannot be executed, the approver supplies a complete one)
Permission to decide approvals.decide — POST /api/aiops/{workspace}/investigations/{id}/approvals
Tier on the request Stamped when the request is created (see Risk tiers); approvals_required is the quorum for that tier
Quorum approved_by counts distinct users: L3 needs two different people, a second click by the same person does not count. An earlier vote counts only while its member may still release the tier: a member removed, disabled or moved to a role without approvals.decide no longer counts, when the second approval lands or when the approved call is dispatched. A vote counts only for the command its member released: an edit is another command, so an edited decision with a different edited_command starts the count again on that command (it waits as the request's proposed edited_command), and a plain approval of the original after a proposed edit starts it again on the original. To complete an edit another approver proposed, send edited with the same edited_command
A denial Goes back to the model as feedback; the run continues without the action
Timeout aiops_approval_ttl_s = 86400 s (24 h). The scheduler denies an older pending request with the note "expired: no human decision within the approval TTL", audits approval.expired, marks the run failed ("approval expired") and hands the incident to a person (an incident already resolved is not reopened)
Audit actions approval.requested, approval.approved, approval.denied, approval.edited, approval.released (a vote that did not complete the quorum), approval.dispatching, approval.executed, approval.receipt, approval.expired, approval.edit_rejected, execution.blocked

A paused tool-call decision always needs approvals.decide. A prepared change has one narrow exception: an operator with incidents.act may release a non-emergency standard change made standard by a matched runbook at a one-approver tier (L1/L2). A P1 or critical incident and L3 still require their existing approvers and quorum. Setting a class by hand or choosing Standard by default does not grant this release permission.

Risk tiers

A tier decides who may release a proposal and how many of them. It never decides whether a human is involved — that is the floor above. Classification is pure: the same inputs always give the same tier, and no model takes part.

Tier On-screen definition Policy label Distinct approvers Permission
L0 Suggestion — no write path matched; a human applies it by hand Suggestion — no write path; a human applies it 1 approvals.decide
L1 Low risk — a reviewed pull request; the merge is the second pair of eyes Low risk — reviewed merge or narrow blast radius 1 approvals.decide
L2 Controlled change — touches live infrastructure or a production environment; needs an elevated approver Controlled change — an approver releases it 1 approvals.decide
L3 High risk — wide blast radius (3+ trusted dependents) or a device push; two distinct approvers High risk — two distinct approvers required 2 approvals.decide

How a tier is derived, in order; a later step may only raise the result:

Step Rule
1. Base by channel gitops_pr → L1 (the PR is inert until a reviewer merges it); api, mcp, cli → L2; network → L3; no matched write path → L0
2. Production label Any alert label env, environment, stage or tier with value prod, production, prd or live → at least L2. Other values are not assumed safe, they are simply not escalated by environment
3. Blast radius 3 or more impacted services reached through a trusted dependency edge (declared or traced) → L3. Observed and discovered edges are shown to the human but never raise the tier
4. Route override A route's tier_override may raise the tier, never lower it
5. Change class A change request's class raises the release tier to its floor: standard L1, normal L2, emergency L3 (see Change classes)
6. Several channels A plan that spans channels is released at the tier of its riskiest channel

Which channel a gated tool call is: net_exec → network, any other <name>_exec → cli, everything else → api (an unrecognised tool errs strict, as a controlled change). An unknown tier value is treated as L2.

Who decides: permissions and roles

Action Permission
Decide a paused agent action approvals.decide
Approve, execute or roll back a change request; close a post-implementation review approvals.decide
Run a legacy route from an incident (Remediate), record an executed fix incidents.act or approvals.decide
Request a change, reclassify a change request, declare a change, ask for verification incidents.act
Request a risk decision (fix or suppress) or a suppression on a control incidents.act or approvals.decide
Approve, reject or revoke a risk decision approvals.decide
Certify a runbook route (pre-approval) approvals.decide
Pull changes or findings now, manage routes and scan settings pillars.manage
Stop a run (kill switch per run), grant extra budget to an incident incidents.act

Builtin roles (synced from code at every start; custom roles may hold any subset):

Role Holds Does not hold
owner every permission —
operator everything except members.manage, pillars.manage, approvals.decide cannot approve, suppress or release on its own
approver every *.view + approvals.decide, feedback.submit, chat.ask does not run analyses
viewer every *.view —
executive every *.view + chat.ask, approvals.decide runs nothing, signs off

Two-person rules beyond L3: a risk decision is approved by someone other than its requester whenever another member holds approvals.decide (a sole approver may self-approve, audited self_approved; aiops_risk_no_self_approval: true forbids it outright); a route is certified by someone other than the person who last edited it whenever another approver exists; an L3 rollback needs a second distinct approver.

Change classes

Every escalated analysis mints one change request (status planned) carrying an ITIL class decided in code at wrap-up — never by the model.

Class Tier floor Release rules
standard L1 A matched-runbook standard prepared fix may be released by one operator (incidents.act) or approver at L1/L2 — a certified runbook route's even at P1, with no emergency rules. L3 still needs two approvers. Certified step runbooks that match the fix exactly may also release it on their own where the workspace allows automatic fixes — an incident's fix only, never a compliance control, scanner finding, vulnerability or security alert (Step runbooks).
normal L2 One approver; a closed change window holds it
emergency L3 Two distinct approvers; once both have approved it passes a closed change window (audited change.window_bypassed); a post-implementation review is due from submission (Schedule the post-implementation review when an emergency change is submitted, on by default since 0.1.133; off, it opens when the write lands), due in aiops_pir_due_h hours (72)

Approving and releasing are separate acts. The person who releases an approved change need not have approved it: an approver approves, and later anyone allowed to release that tier — the same approver, another approver or an owner — runs it. Running an approved change records no extra approval. At dispatch the platform checks again that the person releasing may still release the tier and that the approvals still valid meet the quorum; when either fails, nothing is executed and the audit log records change.release_refused with the reason. A quorum that is no longer met (an approval stopped counting after the release began, a scheduled or automatic release included) puts the change back to waiting for approval (change.pending), so the next approver's click runs it; any other refusal rejects the change. An approval whose approver may no longer release the tier (role changed, member removed, support access ended) stops counting before the click is taken: the person who presses Approve & run adds their own approval in its place, and the change runs once the quorum is met again.

Operator release is checked again at preview and dispatch: the operations must still be pre-approved and a route-based standard fix must still have the same enabled, certified runbook revision with its standard-class switch on. Revoking that basis removes operator release even though the recorded class remains historical evidence. The review digest binds the basis; a changed certification requires another review.

Under the derived policy (default): standard only when a certified runbook route (remediate-…) covers the fix — even at P1; emergency for any other P1 or critical incident; otherwise normal. A fetched skill alone never makes a change standard while Standard change for a runbook route's operations is on (the default); with it off, the older rule applies: standard when a runbook skill matched and every typed operation of the plan is on the workspace's pre-approved list (aiops_preapproved_operations) with no raw commands, file changes or non-restating manual commands beside them. Demoting, disabling or editing the runbook before the request is released makes it normal (emergency at P1). Under change_class_policy: standard_default ("Standard by default" on Guardrails) every request starts standard and people raise it. Under both, a class a person set on an earlier request with the same operations is inherited. Anyone who may request a change may only raise the class (incidents.act): normal to emergency, or a standard no runbook backs; a runbook's standard moves only with its runbook. A new class clears the votes. A class only ever raises the tier. The exact derivation, the runbook-route exception and the reclassify rules are in the Changes reference.

Change and freeze windows

Window Where Meaning
Change window params.change_window on a route; settings['change_windows'] first matching service glob, then settings['change_window'] for prepared plans When a human may release an L2/L3 change: {"days": ["mon", ...], "hours": "HH:MM-HH:MM"}, UTC. Days alone = those whole days; hours alone = every day in those hours; an overnight range wraps midnight; no window = always open; a malformed window fails open (a typo must never lock remediation out)
One-time exception meta.window_exception on one change, requested on the change page and approved by approvals.decide Opens only that digest-bound change during its approved UTC interval; it is a separate approval, never a release vote, and it does not change class, tier or quorum
Freeze window settings['change_freeze'] (same shape) When an external change with no in-flight platform change for its service is flagged authorized: false and audited change.unauthorized_detected

Who checks the change window: the human release of a legacy route at L2/L3 (403 outside it), every human release of a live-infrastructure change (api, cli, mcp, network at L2/L3 — checked at the request, at dispatch and at each step), and the direct API lane before it releases. A pull request never checks it (the auto path may open a PR at 3 am; it is inert until merged), and neither does the rollback sweep (restoring the last-known-good state is the remedy for a change that already went wrong). An approved one-time window is the separate exception for one change; a genuine emergency still uses its full quorum and existing bypass audit/PIR semantics.

Automatic lanes and their guards

Four things can happen without a per-incident click. Each lane re-checks every guard at fire time; the first failing guard is the answer, and a pre-approved route that matched but did not run leaves one route.match_skipped row on the incident with the reason ("Pre-approved route matched — not run" on the incident page).

Pre-approved GitOps runbooks

Fires where the human Remediate click would, after an analysis finished escalated with a matched route.

Guard Skip reason when it fails
Not an unauthorized change or a denied-access report (a person decides, whatever runbook is certified) unauthorized_change
Not a compliance control, scanner finding, vulnerability or security alert (a finding is fixed only after a person approves it, never by a route or a step runbook on its own) finding_lane
settings['aiops_auto_remediation'] on auto_fix_off
Not computer-assist mode execution_disabled
Kill switch off, daily and monthly limits not reached, licence allows runs budget
Never fired before on this incident (one shot) one_shot
The incident still escalated, not verify-muted, not a Quick-start test alert (silent)
A route selected for this incident, enabled and certified at its current revision uncertified_revision, not_preapproved, demoted, analysis_named_other, other_route_selected, typed_steps_won, route_not_offered, …
Tier at most L2 tier_l3
Mode gitops_pr; any other mode goes through the direct API lane below the direct lane's own code
Not unmapped, not flapping, the route's recorded fix on this key not "not a good state" audited incident.auto_remediate_blocked

The run is audited incident.auto_remediate with the route revision, who certified it and the precedent change. The change window is not consulted for a pull request.

Direct API fixes

Off by default. settings['aiops_auto_remediation_direct'] under the master switch above lets a certified API runbook make its one call without a click.

Guard Refusal code
Not an unauthorized change or a denied-access report unauthorized_change
The route may run on its own (API mode, both switches on) never_alone reasons
Certified at its current revision uncertified_revision
Certified by a named person (not a migrated pre-approval) certifier_unknown
That person still holds approvals.decide certifier_lapsed
A runbook route: enabled, named remediate-…, selector set, not an on_open / on_resolve ticket route not_a_runbook
params.operations lists exactly the typed operations, every one on aiops_preapproved_operations, none a revert operation, all for the connection's cloud (aws, azure, gcp) operations_not_preapproved
Exactly one operation and the configured call is that operation's compiled form call_not_bound
No ${…} placeholder anywhere in the route templated_action
params.snapshot declared (before-state) no_before_state
params.rollback declared no_rollback
The selector matches the incident's opening alert pinned_labels
No other API runbook matches that alert ambiguous
Not P1 and not critical (an emergency is never automatic) emergency_class
The latest analysis did not ask for a person analysis_asked_for_person
Not a reopened incident; this route has not already run on it reopened, ran_here
Inside the route's change window the window refusal
No in-flight change on the route route_busy
A verified precedent: the same fix, released by a person and validated, on the same alert no_precedent

After the write the post-execution validation runs; a failed validation follows the rollback rules below and, with aiops_demote_on_rollback on, the first rollback takes the route's pre-approval away.

Automatic rollback

The scheduler sweeps changes that landed failed in the last aiops_rollback_sweep_window_h hours (24), once per change:

Failed change Decision
Pull request whose post-fix verification failed, tier ≤ L2 Restore PR opened (inert until merged)
Pull request whose execution failed before a PR existed Skip — nothing went live
Any change the provider refused before acting Skip
A prepared plan that stopped at its backup (no write step ran, and no person reconciled the write as executed) Skip — nothing was applied
Verification uncertain on a live change Wait for a real verdict
Operator-declared rollback script, settings['aiops_auto_rollback'] on, tier ≤ L2 Executed
Compiled inverse of a posture step Escalate — unless aiops_auto_rollback_cloud (L1 or L2) covers the tier and the post-change metric window failed
Forward action was itself a recovery (start / restart / reboot / reset / redeploy) Escalate — its inverse re-creates the incident
L3, manual, external, computer-assist, already attempted Escalate / skip / wait
Any change of an unauthorized-change incident (a revert an approver released, a fix someone ran) Escalate — a person rolls it back

An agent-drafted rollback is never auto-executed. Full matrix: Changes reference → Rollback paths.

Risk decisions and compliance fixes

Never automatic. A risk decision lands as a request (requested) and takes effect only when an approver approves it; a compliance control's fix is a change request through the change hub — compliance holds no write path of its own. Suppressing an accepted finding at the scanner (Security Hub SUPPRESSED, an Azure Policy exemption) is the one compliance-side cloud write, opt-in per workspace (aiops_risk_suppress_at_source), metadata only, through the write-role connection of the same account. See the Compliance reference.

Kill switch, budgets and licence

Every point that would start new model spend asks the same guard; signals are never dropped — a blocked alert-driven run is marked failed and the incident is handed to a person.

Control Setting / action Effect
Pause all agents settings['agents_paused'] (Guardrails) No new run, no automatic fix, no automatic rollback; alerts and incidents keep recording
Daily limit settings['daily_cost_limit_usd'] (0 = none; platform default aiops_daily_cost_limit_usd_default) Trips at UTC midnight boundaries
Monthly limit settings['monthly_cost_limit_usd'] Trips from the 1st, UTC
Per-incident limit settings['incident_cost_limit_usd']; POST …/incidents/{id}/budget-grant (incidents.act) Over the cap the incident escalates; a person grants more
Per-run cap aiops_max_cost_usd_default / tenant cost cap Best-of-2 doubles it for the second pass
Stop one run POST …/investigations/{id}/cancel (incidents.act) A remote runner gets the stop at its next checkpoint
Licence expired past grace or invalid New runs and approved releases pause, like the kill switch
Computer-assist execution_mode: computer_assist No platform write of any kind

Benchmark and test-lab runs are excluded from every spend total (test spend is not operating cost).

The Needs you queue

GET /api/aiops/{workspace}/needs-you lists every decision waiting on a person, most urgent first; each source is shown only to members holding its permission.

Source What it is
incident_approval A paused agent action
incident_escalated An incident handed to a person
change_approval A change request awaiting its quorum
merge_proposal An advisory incident merge
risk_request A requested risk decision or control exception
pir An open post-implementation review (overdue past its due date)
unmapped_signal An alert that named no service
problem_proposal A recurring problem's automation candidate
route_demoted A runbook route that lost its pre-approval after a rollback, a failed validation or a fix that did not hold
route_certification A route revision waiting to be certified
route_skipped A pre-approved route that matched but did not run for a reason a route edit or a setting would fix

Where this is explained in context

Chapters that mention tiers, approvers or the gate today; each should link here rather than restate the rules.

Next steps