> For the complete documentation index, see [llms.txt](https://docs.openiam.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.openiam.com/administration-guide/lifecycle/simulation-mode.md).

# Simulation mode

Hold connector requests for human review before they reach a target, then apply or discard them.

## What this is, and when you need it

**Simulation** makes the provisioning dispatcher persist a generated connector request to the database instead of sending it. An administrator reads the attribute set, then applies it or deletes it. It is the only place in OpenIAM where you can see exactly what a target would receive, for a real user, before it receives it. Turn it on while you prove a new policy map; turn it off once the mapping is proven.

It is not a dry-run environment and it is not an approval workflow. It intercepts one point — the moment a built connector request would be dispatched — and only for the operations you list. Three real provisioning paths bypass it, including orphan deletion, which the list itself appears to offer: [What simulation does not protect against](#what-simulation-does-not-protect-against).

The specific risk is the queue it builds: nothing expires a captured request, each one holds the full outbound attribute set including personal data, and turning simulation off leaves those rows behind while real operations for the same identity are sent past them. Resolve the queue first.

## At a glance

|                          |                                                                                                                                                                                                                                                                                               |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **You will end up with** | One managed system whose **Simulation Requests** list holds the operations you chose, and a captured request you have read and then applied or discarded — a `SIMULATE_PROVISIONING` event with no `provisionconnectorrequest` document for that identity, and a row on the simulation screen |
| **Where**                | **Provisioning → Managed System** for **Simulation Requests**, then its **Simulation** screen — `/webconsole/simulation?id=<MANAGED_SYSTEM_ID>`                                                                                                                                               |
| **You need first**       | A managed system with a policy map and a proven heartbeat, a `<TEST_USER>`, reviewers you are content to let see personal data, and the menu grant that carries the simulation screen — anyone who can reach it can apply anything on it                                                      |
| **Takes**                | 10–15 minutes to capture and read one request; the queue then takes as long as your reviewers do                                                                                                                                                                                              |
| **Reversible**           | Yes — removing the operations from **Simulation Requests** takes effect on the next dispatch, and a discard preserves the masked attribute set in `DELETE_SIMULATION_REQ`; what cannot be undone is a request you applied, which is a real dispatch                                           |

## Before you begin

* A managed system with a policy map and a proven heartbeat. Simulation captures the request *after* the policy map has been evaluated, so there is nothing to review until the map produces attributes.
* `<TEST_USER>` and, for the first pass, confirmation that the target account does **not** exist.
* Reviewers with least-privilege access. A captured request contains the full outbound attribute set, so it contains personal data, and — subject to the masking described below — credentials.

## Capture a request, review it, apply it

{% stepper %}
{% step %}

### Select the operations to capture

On the managed system, add the operations to **Simulation Requests** and save. For a first pass select `Add` and `Save`: that covers account creation from any trigger without blocking the disable and delete paths you have not built yet. The full selectable list is [Which operations can be captured](#which-operations-can-be-captured). Select the smallest set that covers what you are proving, on one managed system.

{% hint style="warning" %}
Lockout-sensitive in one direction: every operation on this list silently stops reaching the target, so a disable or a delete you believe has happened has not. Do not list an operation you are relying on for revocation.
{% endhint %}

List the operation OpenIAM **starts with**, not the one the connector would be sent — if a `FORCE_*` login status is rewriting operations, list the incoming one.
{% endstep %}

{% step %}

### Trigger the operation

Provision `<TEST_USER>` exactly as a real trigger would. Nothing about the request differs between the captured path and the sent path — that is the point. If nothing is captured **and** nothing reached the target, the operation had nothing to send; that is a policy-map result, not a simulation fault.
{% endstep %}

{% step %}

### Read the captured request

Open the managed system's **Simulation** screen from its own edit menu, and open the request. Each attribute is rendered as `<value>:<operation>` pairs, and the per-value operation is the most useful thing on the screen: it is how you catch a row sending `REPLACE` where you meant `ADD`. Password-typed values are masked, but the masking rule has a gap — see [What the view shows, and what it masks](#what-the-view-shows-and-what-it-masks).
{% endstep %}

{% step %}

### Apply, or discard

Use **Fetch pending requests** first: it shows everything queued before the one you selected for the same identity. If more than one has accumulated, apply **in sequence** — an `Update` applied on its own against a target where the account does not yet exist is the failure that option exists to prevent. See [Apply in sequence](#apply-in-sequence).

Discarding is the safe default: it preserves the masked attribute set in the audit record, so a discarded request stays reviewable.
{% endstep %}

{% step %}

### Take the system out of simulation

Resolve every remaining row first, then remove the operations from **Simulation Requests** and save. In that order: turning simulation off does not resolve requests already captured, and the next real operation for the same identity is sent while those rows still sit there.
{% endstep %}
{% endstepper %}

## Verification

* **The capture itself.** `SIMULATE_PROVISIONING` — set on the target-system request audit log, with the managed system attached via `addManagedSystem(id, name)`. If you expected a capture and there is no `SIMULATE_PROVISIONING` event, the operation is not in `SIMULATION_ACTIONS`, or `doProvision` was false.
* **Nothing reached the target.** No `provisionconnectorrequest` document and no `connectorreply` document for that managed system and identity in the window. The absence is the proof.
* **The apply.** `APPLY_SIMULATION_REQ` (parent, on the UI request) with one `DISPATCH_SIMULATION_REQ` child per request. Each child carries the masked display attributes as its request body, a description `<operation> operation called for entity for managed system <managed system name>`, and the target user's display name or the target group's name.
* **The discard.** `DELETE_SIMULATION_REQ_OPERATION` (parent) with one `DELETE_SIMULATION_REQ` child per request, description `Simulation request deleted for <operation>`, again carrying the masked attributes — so a discarded request is still auditable after the row is gone. This is the only record of what you chose not to send.
* **The row.** After a successful apply or a discard, the `SIMULATION_PROV_REQUEST` row is gone:

{% code overflow="wrap" %}

```sql
SELECT SIMULATION_ID, ENTITY_NAME, ENTITY_TYPE, PROV_OPERATION, USER_ID, GROUP_ID, CREATED_AT
FROM   SIMULATION_PROV_REQUEST
WHERE  MANAGED_SYS_ID = '<MANAGED_SYSTEM_ID>'
ORDER  BY CREATED_AT;
```

{% endcode %}

* **The outcome, after apply.** An applied request is a real dispatch: follow `provisionconnectorrequest` → `connectorreply` → `LOGIN.PROV_STATUS` → the target object, exactly as in [Configure provisioning](/administration-guide/lifecycle/configure-provisioning.md).

All four audit retentions ship `-1` — `org.openiam.audit.log.prune.days.SIMULATE_PROVISIONING` among them. See [Audit actions and retention](/administration-guide/reference/audit-actions-and-retention.md).

## When it goes wrong

| Symptom                                                                        | Cause                                                                                                                                                                                     | Fix                                                                                                                                   |
| ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Nothing captured, nothing sent                                                 | There was nothing to send — an `Add`, `Save` or `Update` whose policy map produced only `NO_CHANGE` values, or produced nothing because a Groovy row threw and was skipped at debug level | [Build a policy map](/administration-guide/lifecycle/policy-map.md). This is not a simulation fault.                                  |
| Nothing captured, the target changed                                           | The operation is not in `SIMULATION_ACTIONS`, or the operation OpenIAM started with differs from the one the connector was sent — the gate matches the former                             | Add the incoming operation. If a `FORCE_*` login status is rewriting operations, list the incoming one, not the rewritten one.        |
| Request captured under an operation you did not select                         | `PROV_OPERATION` stores the **rewritten** operation, while the gate matched the one OpenIAM started with                                                                                  | Expected. Read the pairing rather than the stored value alone.                                                                        |
| Capture fails, `Error while encrypting value for password attribute`           | Encryption failed for a password attribute, or for the managed system's own stored credential                                                                                             | The request is neither stored nor sent. The audit entry for the target-system request has failed; check that the login service is up. |
| Apply reports success, target unchanged                                        | Apply dispatches asynchronously; the connector reply lands later                                                                                                                          | Follow `connectorreply`, not the apply response.                                                                                      |
| Apply fails, the row is still there                                            | The row is deleted only on success, and there is no status column to mark the attempt                                                                                                     | Read the `DISPATCH_SIMULATION_REQ` child's failure reason, fix the cause, apply again.                                                |
| Apply-in-sequence stops part way                                               | It stops at the first failure by design, leaving the remainder pending                                                                                                                    | Fix that request's cause and re-run the sequence; the already-applied ones are gone from the table.                                   |
| An `Update` applied alone fails because the account does not exist             | An earlier `Add` for the same identity was still pending                                                                                                                                  | Use **Fetch pending requests**, then apply with `applySequence=true`.                                                                 |
| A credential is visible in the review screen                                   | The masking test is object type `password` **or** attribute name containing `password`. An attribute like `secretKey` on a `USER` row matches neither                                     | Put credential-bearing rows on a `PASSWORD` object type, or name them so they contain `password`. Restrict who can reach the screen.  |
| Table growing without bound                                                    | Nothing prunes `SIMULATION_PROV_REQUEST`                                                                                                                                                  | Review and clear it. Take the managed system out of simulation once the mapping is proven.                                            |
| Orphan deletion happened despite `DirectDeleteConnectorRequest` being selected | That operation uses a dispatcher overload with no simulation check                                                                                                                        | Control it through orphan review and reconciliation responses instead.                                                                |

## Rollback

Turning simulation **off** — removing the operations from **Simulation Requests** and saving the managed system — takes effect on the next dispatch, because the list is read per operation from the cached managed system. Resolve the queue first: `DELETE_SIMULATION_REQ` preserves the masked attribute set in the audit record, so discarding leaves a reviewable record and loses nothing you cannot re-derive by re-triggering the change.

## Advanced behaviour and implementation notes

Everything below explains what the steps above depend on: where the interception happens, which operations can be listed, what the stored row holds, what the review screen masks, and the three paths simulation does not cover.

## How it works

OpenIAM builds the connector request from the policy map exactly as it would for a real send, and then, immediately before dispatching it, checks whether the operation is in the managed system's **Simulation Requests**. If it is, the request is stored and audited as `SIMULATE_PROVISIONING` instead of being sent. If it is not, it goes to the connector. Nothing about the request differs between the two paths — that is the point.

Three things follow, and all three matter:

1. **The match is on the operation OpenIAM started with**, not on whatever the request was rewritten to. A `FORCE_DELETED` identity turns a `Save` into a connector `Delete`; if `Save` is in your simulation list, that `Delete` is captured — and stored with `provOperation = "Delete"`, which is how you notice the rewrite. If only `Delete` is in your list, that same `Delete` is **sent**.
2. **An operation with nothing to send captures nothing either.** An `Add`, `Save` or `Update` where the policy map produced no changed values neither captures nor sends — see [the operation set](/administration-guide/lifecycle/provisioning-processors/provisioning-processor-reference.md#the-operation-set).
3. **Password-typed attributes are encrypted before storage**, both the policy map's password attributes and the managed system's own stored credential. If that encryption fails, the whole capture fails with `Error while encrypting value for password attribute` and the audit entry fails with it — the request is neither stored nor sent. A missing capture with that message on the audit entry is an encryption problem, not a mapping one.

The list is keyed on the managed system, so two managed systems in the same operation can behave differently. Group provisioning behaves identically.

## Which operations can be captured

The **Simulation Requests** multi-select offers exactly thirteen operations, and this is all of them:

| Stored value                   | Label                              | Reaches the dispatcher?                                   |
| ------------------------------ | ---------------------------------- | --------------------------------------------------------- |
| `Add`                          | Add                                | yes — as a save, and only when there is something to send |
| `Save`                         | Save                               | yes — same branch                                         |
| `Update`                       | Update                             | yes — same branch                                         |
| `Enable`                       | Enable                             | yes — → resume                                            |
| `Disable`                      | Disable                            | yes — → suspend                                           |
| `Lock`                         | Lock                               | yes — → suspend                                           |
| `Delete`                       | Delete                             | yes — → delete                                            |
| `SetPassword`                  | Set Password                       | yes — → reset password                                    |
| `ResetPassword`                | Reset Password                     | yes — → reset password                                    |
| `SyncPasswordFromSrc`          | Sync Password From Source          | yes — → reset password                                    |
| `Login`                        | Login                              | yes — → login                                             |
| `SendSingleProvisionOperation` | Send Single Provisioning Operation | yes — → send single provision event                       |
| `DirectDeleteConnectorRequest` | Direct Delete Connector Request    | **no** — see below                                        |

Anything not in that list cannot be simulated, and an operation that reaches the dispatcher without a matching case simply does nothing — silently.

{% hint style="info" %}
A managed system on the Ticket Connector captures ticket requests instead of account operations: its operations are **Create**, **Update** and **Close**, and the captured rows can be edited and rebuilt before you apply them. See [Review tickets before they are sent](/administration-guide/ticketing/review-before-sending.md).
{% endhint %}

## The request record

Table `SIMULATION_PROV_REQUEST`, which ships as of 4.2.1.3 and was amended in 4.2.1.4:

| Column           | Type                      | Contents                                                                                                                    |
| ---------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `SIMULATION_ID`  | `VARCHAR(32)`             | Primary key                                                                                                                 |
| `ENTITY_NAME`    | `VARCHAR(32)`             | The identity the request is for                                                                                             |
| `MANAGED_SYS_ID` | `VARCHAR(32)`, `NOT NULL` | FK to `MANAGED_SYS`                                                                                                         |
| `PROV_OPERATION` | `VARCHAR(32)`, `NOT NULL` | The **rewritten** operation — what the connector would have been asked to do, not the operation you selected for simulation |
| `ENTITY_TYPE`    | `VARCHAR(20)`, `NOT NULL` | `USER` or `GROUP`                                                                                                           |
| `USER_ID`        | `VARCHAR(32)`             | FK to `USERS`, for a user request                                                                                           |
| `GROUP_ID`       | `VARCHAR(32)`             | FK to `GRP`, for a group request                                                                                            |
| `CREATED_AT`     | `DATETIME`, `NOT NULL`    | Capture time. This is the sort key and the sequencing key.                                                                  |
| `REQUEST`        | `BLOB`, `NOT NULL`        | The whole connector request as JSON                                                                                         |

{% hint style="warning" %}
**There is no status column, and no status enum.** A simulation request has exactly two states, and both are expressed by existence: the row is there (pending) or it is not (applied successfully, or discarded). The row is deleted **only when the apply succeeded**, so a request that fails to apply stays in the table and can be applied again. There is nothing in the schema that records that it was tried.

Practical consequence: a stale row is ambiguous — it is either untouched or a failed apply. Reconcile against the `DISPATCH_SIMULATION_REQ` audit entries, which do record the attempt and its outcome, to tell them apart.
{% endhint %}

### The screen and its controls

The screen is `/webconsole/simulation?id=<MANAGED_SYSTEM_ID>`, and it reads the managed-system ID from the menu context, which is why you reach it from the managed system's own edit menu rather than by typing the route.

| Control                                                      | What it does                                                                                 | Audit action                                                                        |
| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| The grid, and its per-column search boxes                    | Lists and filters this managed system's queued requests                                      | —                                                                                   |
| The view icon on a row                                       | Opens that one request's attributes                                                          | —                                                                                   |
| **Review**                                                   | Shows everything queued ahead of the request you ticked, so you can see what will land first | —                                                                                   |
| **Apply Selected**                                           | Applies the ticked requests                                                                  | `APPLY_SIMULATION_REQ` parent, `DISPATCH_SIMULATION_REQ` child per request          |
| **Apply in Sequence**, from **Review and apply in sequence** | The same, in queue order                                                                     | The same                                                                            |
| **Delete Selected**, **Delete All**                          | Removes requests without applying them                                                       | `DELETE_SIMULATION_REQ_OPERATION` parent, `DELETE_SIMULATION_REQ` child per request |

**Select All** / **Unselect All** below the grid tick or untick every row on the current page. Apply and delete both act only on the ticked rows.

The list columns are `ENTITY_NAME` (Entity Name), `ENTITY_TYPE` (Entity Type), `PROV_OPERATION` (Operation) and `CREATED_AT` (Date), each searchable; the created-date filter is parsed as an ISO-8601 offset timestamp and widened to the whole local day. The confirmation text before applying is: *"You are about to apply the selected provisioning requests to the target system. This will affect objects in that system. Do you want to proceed?"*

### What the view shows, and what it masks

Opening a request loads the managed system's `USER_OBJECT` (or `GROUP_OBJECT`) policy map and builds the list of **sensitive attribute names** from it: every row whose object type is `password` (case-insensitive) **or** whose attribute name contains the substring `password` (lower-cased). For those, the displayed value is replaced with the hidden-password constant. Every other attribute is rendered as `<value>:<operation>,<value>:<operation>,…` — one value and its operation per pair, comma-separated.

That per-value operation is the most useful thing on the screen. It is how you see that a `memberOf` attribute is adding two groups and deleting a third in one request, and it is how you catch a row that is sending `REPLACE` where you meant `ADD`.

Note the masking rule's shape: an attribute called `unicodePwd` does **not** contain the substring `password`, so it is masked only because its policy-map row's object type is `PASSWORD`. An attribute called `secretKey` on a `USER` row is masked by neither test and its value is displayed in clear. Check your policy map's object types before you let a wide audience review requests.

### Apply in sequence

Ordinary apply processes the IDs you selected, in whatever order the repository returns them, and continues past failures — each request gets its own `DISPATCH_SIMULATION_REQ` child which succeeds or fails independently.

`applySequence=true` is different, and it is the option to use whenever more than one request has accumulated for the same identity. OpenIAM takes the request you selected and finds **every** pending request for the same managed system and the same user or group with `CREATED_AT` at or before the selected one, oldest first. It then applies them in that order and **stops at the first failure** — the failing request's child audit entry records the reason, that request and every later one stay pending, and the ones already applied are gone from the table. So a partly applied sequence is recoverable: fix the cause and run the sequence again from the earliest remaining request.

So a user with a captured `Add` and then a captured `Update` is applied create-first.

## What simulation does not protect against

{% hint style="danger" %}
**`DirectDeleteConnectorRequest` is offered in the simulation list and is never simulated.** The Webconsole shows *Direct Delete Connector Request* as a selectable simulation action. But that operation takes a different route to the connector — one that builds the request against an **orphan** object and dispatches it straight to the connector, with **no simulation check at all**.

That route is what orphan removal and reconciliation's unmatched-orphan branch use. So selecting `DirectDeleteConnectorRequest` in the simulation list gives you no protection at all against `REMOVE_ORPHAN` deleting a target account, or against reconciliation's `DELETE_FROM_RES` deleting every account it failed to match. Those are protected by the reconciliation situation responses and by orphan review, not by simulation.
{% endhint %}

Three more boundaries, in decreasing order of how often they catch people:

**Reconciliation and synchronization writes do not all pass the simulation gate.** A reconciliation response that saves the user *is* simulated. But `DELETE_FROM_RES` on an unmatched orphan calls the connector's delete directly and is **not**, and the OpenIAM-side responses `REMOVE_FROM_IDM`, `ADD_TO_IDM` and `DISABLE_IN_IDM` never touch a connector at all, so simulation has nothing to say about them. Reconciliation's own safety mechanism is the situation response set — every situation on `NOTHING` for the first pass. See [Configure reconciliation](/administration-guide/lifecycle/configure-reconciliation.md).

**Simulation is not an approval workflow.** There is no requester, approver, delegation, expiry, reminder or reason field, and anyone who can reach the screen can apply anything on it. A target change that needs a governed human decision needs an [approval flow](/administration-guide/requests/approval-flows.md); simulation is at best the technical review beside it.

**A captured request proves the policy map, not the target.** It shows what OpenIAM will send. It does not show whether the target's schema accepts it, whether the service account has the right to write it, or whether a later step in the same business action (password, enable, membership) will succeed. Applying is still the first moment anything is proven.

And one operational hazard that is not a design boundary: **captured requests accumulate.** Nothing expires them and no batch task prunes them, so a managed system left in simulation with `Save` selected collects one row per user save indefinitely, each holding a full JSON request in a `BLOB`.

## Related

* [Configure a managed system](/administration-guide/lifecycle/configure-managed-system.md#simulation-requests-the-real-option-list) — where the operations are selected.
* [Configure provisioning](/administration-guide/lifecycle/configure-provisioning.md) — the dispatcher, the `doProvisionSave` gate, and what happens after apply.
* [Build a policy map](/administration-guide/lifecycle/policy-map.md) — what the captured attribute set is made of, and why a row can be missing.
* [Configure reconciliation](/administration-guide/lifecycle/configure-reconciliation.md) — the safety mechanism for the writes simulation does not cover.
* [Audit actions and retention](/administration-guide/reference/audit-actions-and-retention.md) — `SIMULATE_PROVISIONING`, `APPLY_SIMULATION_REQ`, `DISPATCH_SIMULATION_REQ`, `DELETE_SIMULATION_REQ`, `DELETE_SIMULATION_REQ_OPERATION`.

*Applies to OpenIAM 2026.8.1. Last reviewed 7 August 2026.*


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.openiam.com/administration-guide/lifecycle/simulation-mode.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
