Policy Model
Suvra uses a deterministic two-tier policy model: one required global policy plus an optional bound agent policy. Every active decision is a pure function of the loaded rules and the action's identity-aware context.
Two-tier model
Global policy: SuvraPolicy
The global policy (internal id suvra-policy, displayed as SuvraPolicy) applies to every agent. It carries the organization-wide rules every action is evaluated against.
Agent policy (optional)
A single optional agent policy can be bound to an agent via agents.policy_id. When both tiers match an action, the agent policy overrides the global policy.
Legacy scopes — tenant, business unit, domain, workspace, environment — remain in audit metadata and
params_hashnormalization for backward compatibility, but they are not active policy scopes in the runtime. Only global + agent policies are evaluated.
Deny-by-default
If no rule matches, the decision is always deny. There is no mode field, no allow_all, no flag that changes this. Deny-by-default is a hard guarantee — it is not configurable.
Decisions
| Decision | Behavior |
|---|---|
allow | Action is permitted. The executor runs immediately. |
deny | Action is blocked. No side effect. Denial is audited. |
needs_approval | Action is paused until a human approves via dashboard or API. |
Precedence
After rule matching, Suvra resolves the winning decision deterministically:
- Global policy rules are evaluated in listed order
- Agent policy rules are evaluated in listed order
- Within a policy, the last matching rule wins
- Agent-policy matches override global-policy matches
- If no rule matches, the final decision is
deny
Every policy-driven response — validate, execute, simulate — includes a structured decision_trace alongside compatibility fields (matched_rule_id, matched_policy_id, reasons, checks).
Rule schema
A policy is an ordered list of rule references:
policy_id: suvra-policy
rules:
- { rule_id: fs.write_reports_allow, enabled: true }
- { rule_id: http.external_request_needs_approval, enabled: true }
- { rule_id: fs.delete_production_deny, enabled: true }
Each library rule has:
id— stable identifier referenced from policiestype— the action type it applies to (fs.write_file,http.request,shell.exec,email.delete,secrets.read)effect—allow | deny | needs_approvalconstraints— optional constraint mapdescription— human-readable string
At evaluation time Suvra hydrates each reference from the Rules Library into a fully materialized rule.
Constraints
Filesystem
path_prefixmax_bytesworking_dir_prefix
HTTP
methodallow_domainstimeout_secondshost_in,host_prefix
Identity / request context
agent,user,role,workspace,environmentlabels— every listed label must be present (deterministic normalization: trim, dedupe, sort)tenant_id,business_unit,domain— retained for compatibility-sensitive matching
Command / provider
allow_commandsallow_providers(case-insensitive)allow_mailboxes(case-insensitive)allow_names(exact match)
Matching is exact-string for scalar constraints. A rule matches only when every configured constraint passes.
Identity-aware context
Policy rules can match on identity context normalized from the incoming action:
- Request-supplied:
agent,user,role,environment,workspace_dir,labels - Registry-derived (filled from the Agent Registry when
agent_idis present):agent_id,risk_tier,approval_profile,runtime_type,owner,purpose,integration(preferred; normalized to legacyframeworkstorage key)
Explicit request values always win field-by-field; the registry only fills missing fields.
Authoring rules
Most deployments start from the shipped Rules Library and only author custom rules when no library rule covers their need. A rule file looks like:
id: fs.write_reports_allow
type: fs.write_file
effect: allow
constraints:
path_prefix: workspace/reports/
max_bytes: 2097152
description: Allow report writes under workspace/reports up to 2 MiB.
Example: file writes
Allow writes under workspace/reports/ up to 2 MiB, require approval for writes outside that path:
rules:
- id: fs.write_reports_allow
type: fs.write_file
effect: allow
constraints:
path_prefix: workspace/reports/
max_bytes: 2097152
- id: fs.write_other_approve
type: fs.write_file
effect: needs_approval
Example: HTTP allow-list
rules:
- id: http.trusted_domains_allow
type: http.request
effect: allow
constraints:
method: GET
allow_domains:
- api.example.com
- data.example.com
timeout_seconds: 10
Example: environment-aware deny
rules:
- id: fs.delete_any_allow
type: fs.delete_file
effect: allow
constraints:
path_prefix: workspace/
- id: fs.delete_prod_deny
type: fs.delete_file
effect: deny
constraints:
environment: production
Example: identity-aware approvals
rules:
- id: secrets.read_critical_approve
type: secrets.read
effect: needs_approval
constraints:
allow_names: [stripe_api_key, github_token]
labels:
- risk:critical
Effective policy view
/dashboard/policy/effective shows the merged, resolved policy for a given agent — the global policy rules in order, then the bound agent policy rules, rendered in the exact precedence the engine uses. Submit an action payload and the page renders final_decision, matched_rules, matched_scopes, precedence_order, winning_rule, overridden_rules, and reason.
At runtime, the engine calls load_for_agent(agent_id) and returns:
policies—LoadedPolicyobjects with hydrated rulesstack— metadata entries (policy name, type, rule count) for displaywarnings— non-fatal normalization or shadowing warnings
Testing with /simulate
/simulate evaluates a policy against an action without executing side effects and without creating approvals:
curl -X POST https://suvra.example.com/simulate \
-H "Content-Type: application/json" \
-H "X-Suvra-Token: $SUVRA_AUTH_TOKEN" \
-d '{
"action": {
"type": "fs.write_file",
"params": { "path": "workspace/reports/q4.md", "content": "..." },
"agent_id": "report-bot"
},
"draft_policies": [ ... ]
}'
/simulate ignores SUVRA_MODE and always runs strict simulation. Responses include a warnings array for precedence/shadowing. Simulator safety: /simulate and the dashboard Effective View simulation workflow never execute side effects and never create approvals.
Explainability output
Every policy-driven response includes:
matched_rule_id— the winning rule (present even on constraint-failure fallback)matched_policy_idreasons— business-readable stringschecks— per-constraint{name, ok, detail}entries, includingtarget.*for policy target selectorsdecision_trace— structured trace:request_scope_summary,loaded_policy_layers,matched_rules,matched_scopes,precedence_order,winning_rule,overridden_rules,final_decision,reasonprecedence_trace— simulator/execution hierarchy explanation
Audit persistence stores this explainability inside result_summary JSON so any historical decision can be re-rendered.
Related
- Rules Library — the 64 curated rules you compose policies from
- Audit Explorer — review past decisions with their trace
- Deployment — node cache and offline behavior