Policy simulator
The Policy simulator screen lets you compare the decisions of a candidate policy with those of the active policies, on inputs you write and on decisions recorded by the platform, without changing anything, and read the revisions of a policy with the difference between two of them. Portal path: /security/policy-simulator, menu Security and governance > Policy simulator. It is used by security officers, tenant administrators and developers before they publish a policy change, and by auditors who read the history.
What the screen shows
The screen (PolicySimulator in portal/src/screens/security-kit.tsx) has two sections.
- Simulate a candidate policy ("Nothing is changed: decisions are compared on inputs and recorded decisions."): a form with Candidate (JSON, prefilled with a
builtinpolicy that deniesmodel.invoke), Policy (identifier of the policy the candidate replaces; empty: the candidate is added), Inputs (JSON list of Policy Contract requests, default[]) and Recorded decisions (number of the last recorded decisions to replay, default50). The result shows four badges, Total, Changed, Allow to deny, Deny to allow (amber when above zero), and a table with Current allows, Candidate allows, Changed and Input, one row per compared input. Without any row: "No input compared". - Revisions ("Every create, update and delete appends an immutable revision."): a picker Policy with Apply, then a table of the revisions, columns Revision, Change, Name, Version, Status, and next to it "Diff from revision {from} to {to} (click two rows)" with the JSON of the difference. Without revision: "No revision".
An example of Candidate and Inputs:
{
"candidate": {"name": "candidate", "engine": "builtin", "priority": 100,
"rules": [{"effect": "deny", "actions": ["model.invoke"], "resource_types": ["model"]}]},
"inputs": [{"action": "model.invoke", "resource": {"type": "model", "id": "claude-sonnet-eu"}}]
}
Actions
| Button or action | What it does | API call | Roles |
|---|---|---|---|
| Run (Simulate a candidate policy) | Evaluates every input and recorded decision with the active policies, then with the candidate in place, and returns the differences; audited as policy.simulate. Nothing is stored. | POST /v1/policies/simulate | tenant_admin, security_officer, developer (form hidden for others) |
| Apply (Revisions, picker Policy) | Loads the revisions of the policy and the difference of its last change. | GET /v1/policies/{policy_id}/revisions, GET /v1/policies/{policy_id}/revisions/diff | tenant_admin, security_officer, auditor, developer |
| Click on a first row, then a second row | Sets the range from then to and reads the difference between these two revisions; a third click starts a new range. | GET /v1/policies/{policy_id}/revisions/diff (query from_revision, to_revision) | Same |
| Retry (error state) | Reads the revisions again. | GET /v1/policies/{policy_id}/revisions | Same |
Roles
The portal shows the simulation form to ROLES.policySimulators (tenant_admin, security_officer, developer, portal/src/lib/kit.ts); other users see "Requires one of these roles". The Control Plane (services/control-plane/src/eai/domains/policies/__init__.py) requires the same roles for the simulation, and tenant_admin, security_officer, auditor or developer for the revisions and their difference. platform_admin passes every role check; its role is removed from the simulated subjects.
Typical tasks
Measure the effect of a new rule before publishing it
- Write the candidate. The candidate is a full policy, same format as on the Policies screen; only the
builtinengine can be simulated. Edit Candidate. Expected result: valid JSON in the field. If it fails: "Invalid JSON" under the field. - Choose what to compare. Recorded decisions show the real traffic, inputs show the cases you care about. Keep Recorded decisions
50and add your Inputs. Expected result: at least one source of cases. If it fails:validation_error(HTTP 422) "inputs or recorded is required" when both are empty or zero. - Run. Click Run. Expected result: the four badges and one row per case; Allow to deny counts what the candidate would block. If it fails:
simulation_engine_unsupported(HTTP 422) for anopacandidate;forbiddenfor a missing role. - Decide. No change was made. If the differences are the expected ones, create or edit the policy on the Policies screen. Expected result: the decisions of the platform follow the new rule.
Compare two revisions of a policy
- Load the history. Enter the identifier (UUID) of the policy in Policy and click Apply. Expected result: the revisions with their Change (
create,update,delete). If it fails:not_foundmeans an unknown policy or an identifier that is not a UUID. - Pick the range. Click the older revision, then the newer one. Expected result: "Diff from revision X to Y" and the JSON of the difference. If it fails:
not_foundwithpolicy_revisionmeans the range does not exist.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
validation_error (HTTP 422) "inputs or recorded is required" | No input and Recorded decisions at 0. | Add inputs or replay recorded decisions. |
validation_error (HTTP 422) on Candidate | The candidate does not follow the policy format (name, engine, rules), or more than 200 inputs, or Recorded decisions above 500. | Fix the JSON or the numbers. |
simulation_engine_unsupported (HTTP 422) | Only builtin rule candidates can be simulated. | Simulate a builtin candidate. |
not_found with policy | Policy is unknown or not a UUID. | Copy the identifier from the Policies screen. |
forbidden (HTTP 403) | The simulation needs tenant_admin, security_officer or developer; the revisions also accept auditor. | Ask for one of these roles. |
| "No input compared" | The simulation returned no case. | Add inputs, or make sure decisions were recorded. |