Skip to Content
Numinance
Operations

Risk and approval

How risk classes route work to autonomy, human approval or denial, and how separation of duties keeps an agent from approving itself.

View as .mdUpdated
Under development
The risk and autonomy contracts are public-spec. The approval workflow and the decision point that enforces them live in the proprietary runtime and are designed, not shipped.

Approval is a signal the policy engine consumes, from a human who is not the author. It is never something an agent can grant itself.

Risk classes

Every action has a risk class, from lowest to highest: RISK_CLASS_READ, LOW, MEDIUM, HIGH and PRIVILEGED. A change's risk class is the maximum over its playbook steps. A capability declares a risk floor that no provider and no customer can lower, and lowering the risk of an existing capability in a later release is a contract-breaking change.

Step risk can only be raised by the playbook author. The resolved risk is the maximum of the step's declared risk and the risk of every capability its grant allows.

From risk to a decision

Before every privileged action the runtime evaluates policy outside the model. The result is one of three decisions:

DecisionMeaning
allowThe action proceeds, possibly with obligations such as extra evidence
denyThe action does not happen, and the denial is recorded
require approvalThe action waits for approvers who satisfy the rule

Inputs include the action and its risk floor, the acting identity, the change and environment, the autonomy level and floor, residency, remaining budget, the evidence present and required, and time windows. Floors are evaluated first and default to deny.

Autonomy policy explorer

Given defaults, rules and floors, what is the highest level this change may reach?

Illustrative · simulated
Scenario
Playbook
Risk
Environment
Rule level (what-if)
  1. OPERATE
  2. RELEASE
  3. STAGE
  4. MERGErequestedeffective
  5. PR
  6. IMPLEMENT
  7. SUGGEST
  8. OBSERVE
Effective level AUTONOMY_LEVEL_MERGE

The dependency-upgrade rule matches and requests MERGE. No floor matches, so nothing lowers it.

  1. 1 · Default
    defaults.level = PR (overridden by the matching rule)
  2. 2 · Rule: dependency-upgrade, LOW → MERGEmatched
    playbook (resolver context): dependency-upgrade ✓ · risk (exact): LOW ✓
  3. 3a · Floor: risk HIGH or higher → max SUGGESTnot matched
    risk (resolver context): LOW; a floor matches that class and every higher one.
  4. 3b · Floor: environment production → max MERGEnot matched
    environment (resolver context): a different environment.
  5. 4 · Result
    min(requested MERGE, lowest matching floor none) = MERGE
schemas/examples/AutonomyPolicy/org-ladder.yamlreal example
apiVersion: numinance.dev/v1
kind: AutonomyPolicy
metadata: {name: org-ladder, scope: SCOPE_ORG}
spec:
defaults: {level: AUTONOMY_LEVEL_PR}
rules:
- {match: {playbook: dependency-upgrade, risk: RISK_CLASS_LOW}, level: AUTONOMY_LEVEL_MERGE} (matched)
floors:
rules:
- {match: {risk: RISK_CLASS_HIGH}, max_level: AUTONOMY_LEVEL_SUGGEST}
- {match: {environment: production}, max_level: AUTONOMY_LEVEL_MERGE}
  • Kernel floors are always on. They sit under every policy and no rule or default removes them.
  • The highest configured level never bypasses a matching floor. Floors cap; they are not suggestions.
  • Promotion is proposed, then accepted. Evaluation may propose a higher level; a human accepts it. An agent never grants itself a rung.
✓ matched■ reachable rung✕ blocked by a floor▤ real org-ladder example
Text description

The ladder is the AutonomyLevel enum, lowest to highest: OBSERVE (reads only), SUGGEST (proposes, writes nothing), IMPLEMENT (writes in a sandbox), PR (opens pull requests), MERGE (merges when policy allows), STAGE (deploys to pre-production), RELEASE (deploys to production), OPERATE (operations against production).

Resolution follows AutonomyPolicySpec: the level is the lowest level among matching rules, else defaults.level; then no level above the lowest max_level among the matching floors. The example policy is org-ladder: default PR; one rule (playbook dependency-upgrade and risk LOW sets MERGE); two floors (risk HIGH or higher caps at SUGGEST; environment production caps at MERGE).

  • In a rule, risk matches exactly and a key the change has no value for does not match.
  • In a floor, risk matches that class and every higher one, and a key the change has no value for counts as a match, which is why the toggle none for environment still matches the production floor.
  • The playbook, risk and environment values are the resolver context of the change. They are not extra fields on a rule. The what-if rule level only changes the simulation; the YAML shown is the real example text.
  • Kernel floors are always on. Promotion to a higher level is proposed by evaluation and accepted by a human, and is never self-granted.
  • An org with no floors gets the shipped floor (SUGGEST for HIGH and PRIVILEGED, PR for the rest); control-pack floors can only add caps.

Everything here is simulated in the browser from the example policy. Nothing is connected to a running engine.

An autonomy policy in practice

This is the shipped example from the spec. It defaults to opening pull requests, lets a low-risk dependency playbook reach merge, and floors high-risk work at suggestion.

schemas/examples/AutonomyPolicy/org-ladder.yaml
apiVersion: numinance.dev/v1
kind: AutonomyPolicy
metadata: {name: org-ladder, scope: SCOPE_ORG}
spec:
  defaults: {level: AUTONOMY_LEVEL_PR}
  rules:
    - {match: {playbook: dependency-upgrade, risk: RISK_CLASS_LOW}, level: AUTONOMY_LEVEL_MERGE}
  floors:
    rules:
      - {match: {risk: RISK_CLASS_HIGH}, max_level: AUTONOMY_LEVEL_SUGGEST}
      - {match: {environment: production}, max_level: AUTONOMY_LEVEL_MERGE}

The open CLI can check that this file is well formed with numi validate --files. It cannot tell you what the runtime would decide, because the open build does not resolve profiles or evaluate floors. See Autonomy policy.

The approval workflow

The designed workflow has five parts.

  1. Request. Policy returns require_approval with the approver group, a required count and an exclusion of the author principal. The change pauses at a gate.
  2. Presentation. The approver sees the plan, the evidence so far, the verifier results and the policy reasons by rule id.
  3. Decision. An authenticated human approves or rejects. The control plane or the customer's identity provider supplies the principal. The runtime validates the approver against the author identity before it accepts the decision.
  4. Signal. The approval enters policy as input, not as an override. Policy still decides whether the action proceeds.
  5. Record. The approval, the principal, the reason and the policy decision id are written to the evidence chain by the runner identity.

An approval applies to a specific plan digest. If the plan changes, the approval does not carry over.

Separation of duties

Four identities act in a change, and the policy treats them as structurally different.

IdentityMayMay not
Author agentPropose and implement inside its sandboxApprove, edit protected paths, deploy
Reviewer agentReview and report findingsApprove as a human, deploy
Release pipelineDeploy, and is the only identity that doesAuthor code
Human approverApprove, reject, escalateBe the author of the change they approve

Policy denies an approver equal to the author, and an approver that shares the author's run. Protected paths such as policy files, CI definitions and permission definitions are out of reach for agents by mount and by source-control branch protection. The kernel that enforces all this sits outside every grant.

Grants expire and cannot widen

For each work unit the runtime derives a grant from the profile, playbook step, risk class and autonomy level. A grant names capabilities, resource scope, budgets, environment and an expiry. A grant is immutable. A holder can request a narrower grant for a sub-step and can never widen its own. Credentials are issued just in time for one step and revoked when it completes or when the breaker trips.

Failure modes and safeguards

  • Self-approval attempt. Denied structurally and recorded as a denial.
  • Instruction injected through a ticket or document. Retrieved text is treated as data. Only signed policy and the kernel can create enforceable authority.
  • Stale approval. An approval bound to an old plan digest is not valid for a new plan.
  • Approver fatigue. Mitigate by narrowing what routes to humans, not by adding more approvers to rubber-stamp.

Troubleshooting

  • A change is stuck at a gate. Check the approver group and count. If the only eligible approver is the author, nobody can approve by design.
  • An action was denied that you expected to run. Read the policy reasons by rule id in the decision record, then check floors before rules.
  • A promotion did not apply. Floors and narrower scopes can only tighten. See Evaluations.

On this page