> 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/access/role-engineering.md).

# Role engineering and maintenance

Build and maintain a role catalog using the tools OpenIAM actually ships — role compare, entitlement CSV import, bulk operations, membership tags, risk scoring and business rules.

## What this is, and when you need it

Role engineering advice is usually generic: name roles for responsibilities, avoid role explosion, certify regularly. All true, and none of it tells you which screen to open.

This page is the same methodology anchored to specific OpenIAM capabilities. Every practice below names the route, report, drop-down or property that executes it — and where OpenIAM has no mechanism, it says so and states the manual alternative rather than implying a feature exists. Read it when you are building a catalog from scratch, or when an existing one has grown past the point where anyone can review it.

Work through it and you will have a catalog you can load from a file rather than type, measured against four reports rather than an opinion, with the duplicates collapsed, an owner and a risk rating on every role, and each role's population owned by exactly one mechanism. It is a programme of work, not a single sitting; each section below is separately useful.

This page is the method and the measurements, not one procedure. It hands each phase off to the page that performs it: [Import roles, groups, organizations and entitlement memberships](/administration-guide/lifecycle/synchronize-and-import-data/import-roles-groups-and-memberships.md) loads the catalog, [Manage roles and groups](/administration-guide/access/manage-roles-and-groups.md) does the compares and the bulk edits, [Build a business rule](/administration-guide/access/business-rules.md) moves the grants off human hands, [Risk factor reference](/administration-guide/risk/factors.md) turns the catalog into a number, and [Plan and scope a campaign](/administration-guide/governance/certification/plan-and-scope-a-campaign.md) puts it in front of a reviewer. The one procedure that lives here is collapsing a duplicate pair, because it is the step those pages assume you have already done.

## The five things OpenIAM gives you

| Capability                  | Where                                                        | What it is for                                                                                   |
| --------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ |
| Role compare                | `/webconsole/compare-roles?id=<ROLE_ID>`                     | Finding and collapsing near-duplicate roles                                                      |
| Entitlement CSV import      | **Provisioning → Synchronization**, CSV format `ent_csv_ex2` | Loading a designed catalog with owner, admin, approvers, attributes and max duration in one pass |
| Entitlement bulk operations | `/webconsole/entitlements-bulk-operations`                   | Retrofitting owner, risk, duration or status across many roles                                   |
| Membership tags             | On every membership                                          | Measuring how access is *actually* granted, not how you intended                                 |
| Risk scoring                | `/webconsole/risk-factors`                                   | Making "which roles matter" a computed number instead of an opinion                              |

Plus the reports: `ROLE_AUDIT_REPORT`, `GROUP_REPORT`, `GROUP_AUDIT_REPORT`, `ENTITLEMENTS_REPORT`, `ENTITLEMENTS_MEMBERSHIP_REPORT`, `RESOURCE_USAGE_REPORT`, `USER_ACCESS_REPORT` and `ORPHANED_USER_ACCOUNTS` — see [Shipped reports](/administration-guide/reference/shipped-reports.md).

## Design: two layers, expressed in things OpenIAM stores

The two-layer catalog — outer roles named for responsibilities, inner roles and groups named for target permissions — is a convention, not a product feature. OpenIAM has no "business role" object, and the role **Type** field (`ACCESS_ROLE` / `PROVISION_ROLE`) does not implement it. See [The access model](/administration-guide/access/access-model.md#business-roles-and-technical-roles-are-a-design-convention).

So express the layer in the fields that *do* persist and *are* searchable:

| Design intent                                    | Store it in                            | Why that field                                                                                                      |
| ------------------------------------------------ | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| "This is a target permission"                    | **Role Managed System**                | Populated means one target owns it; searchable and filterable in the role grid                                      |
| "This is an outer/composition role"              | Managed System **empty**               | Cross-system composition has no single owner                                                                        |
| Consistent classification for reporting          | Role **Type**                          | The only `ROLE_TYPE` field there is; used for the search grid's type column and the SCIM `type` canonical values    |
| Who is accountable                               | **Role Owner**                         | Populated by the owner picker; feeds request approval and certification reviewer resolution                         |
| Who maintains it                                 | **Role Admin**                         | Delegated operational authority                                                                                     |
| Sensitivity                                      | **Role Risk**                          | Consumed by `builtin.entitlement.sensitivity`, which maps `CRITICAL`→100, `HIGH`→75, `MEDIUM`→50, `LOW`→25, unset→0 |
| Catalog metadata that is not a first-class field | **Role attributes** (`ROLE_ATTRIBUTE`) | Target permission IDs, license codes, source system, catalog version                                                |

{% hint style="info" %}
Role attributes are excellent for correlation and reporting and useless for reviewers: the certification entitlement detail does not render arbitrary role attributes. Anything a reviewer must read to make a decision belongs in the role **name** and **description**. Confirm it in campaign preview before you rely on it.
{% endhint %}

## Build: load the catalog, do not hand-type it

A designed catalog arrives as a spreadsheet. OpenIAM ships a CSV synchronization format for exactly this, shipped since 4.2.1.2 as the synchronization configuration named **`ent_csv_ex2`** (and its group twin `grp_csv_ex1`). Find both at `/webconsole/synchronizations`; if they are missing, someone has deleted them, and the header below is what you must recreate:

{% code overflow="wrap" %}

```
MANAGED_SYSTEM_NAME,GROUP_TYPE,ROLE_TYPE,ENTITLEMENT_NAME,IS_ACTIVE,ENTITLEMENT_OWNER_TYPE,ENTITLEMENT_OWNER,ENTITLEMENT_ADMIN_TYPE,ENTITLEMENT_ADMIN,APPROVER1_TYPE,APPROVER1,APPROVER2_TYPE,APPROVER2,APPROVER3_TYPE,APPROVER3,AD_GROUPS,ATTRIBUTE_NAME1,ATTRIBUTE_VALUE1,ATTRIBUTE_NAME2,ATTRIBUTE_VALUE2,ATTRIBUTE_NAME3,ATTRIBUTE_VALUE3,MAX_MEMBERSHIP_DURATION
```

{% endcode %}

That header *is* your design matrix. It carries owner, admin, three approvers, three attributes, maximum membership duration, active flag and the role type — everything a governed role needs, on one line, versioned in your source control. The applications equivalent is `application_csv_ex3`, whose header includes `PARTICIPATE_IN_ACCESSCERTIFICATION`, `CATEGORY_NAME` and `PERMISSIONS_LIST`.

### What each column means

All twenty-three columns are described in [Role and group field reference](/administration-guide/access/manage-roles-and-groups/role-and-group-reference.md#the-entitlement-csv-columns), including the six that OpenIAM's own documentation of the entitlement import does not describe.

Four of them decide whether the load is worth doing at all. `ENTITLEMENT_NAME` is what every other row and every later membership feed resolves by, so it must be unique and stable. `ENTITLEMENT_OWNER` is what certification reviewer resolution and request approval depend on, and it is the field most catalogs are missing. `MANAGED_SYSTEM_NAME` populated means "this is a target permission" and empty means "this is a composition role". `MAX_MEMBERSHIP_DURATION` is the default expiry on new memberships — recorded, and acted on only once `AUTO_REVOCATION` is enabled.

The header above is the contract, and a file built from the older *Importing roles* page will not load through this configuration — that page describes the **applications** format. The reference page states the difference, and what to do if you need a column the header does not have.

Use it and you get a reviewable artifact and a repeatable load. Hand-type the catalog in the Webconsole and you get neither. See [Import roles, groups, organizations and entitlement memberships](/administration-guide/lifecycle/synchronize-and-import-data/import-roles-groups-and-memberships.md); role imports record audit action `IMPORT_ROLE`, group imports `IMPORT_GROUP`.

## Measure: what the catalog actually looks like

Do this before you redesign anything. Four measurements, each with a real source:

**How many roles have no members?** `ENTITLEMENTS_MEMBERSHIP_REPORT` and `RESOURCE_USAGE_REPORT`. An unmembered role is either a design placeholder or dead weight, and you cannot tell which without its owner.

**How is access really being granted?** Read the membership tags. `ADMIN_ASSIGNMENT` on a large fraction of memberships means your governed paths are being bypassed, whatever the process document says. There are exactly five tags — `ACCESS_REQUEST_ASSIGNMENT`, `ADMIN_ASSIGNMENT`, `BUSINESS_RULE_ASSIGNMENT`, `SYSTEM_ASSIGNMENT` and `PENDING_MANUAL_ASSIGNMENT` — and you cannot add a sixth, so every membership in the system falls into one of them.

You can turn that into a score rather than an impression: `builtin.membership.origin` assigns `ADMIN_ASSIGNMENT` a bootstrap of `75.0` and `BUSINESS_RULE_ASSIGNMENT` a bootstrap of `10.0`, so a population granted by hand scores measurably riskier than the same population granted by rule. See [Risk factor reference](/administration-guide/risk/factors.md#factor-membership-origin-parameters).

**Which roles are near-duplicates?** This is the one thing role engineering usually does by eyeballing spreadsheets, and OpenIAM has a screen for it. **Compare with...** on a role's entitlements screen opens **Role Comparison** at `/webconsole/compare-roles?id=<ROLE_ID>`, which lists every child role, group and resource of both roles side by side — a name in only one of the two columns exists in only that role. Two roles whose diff is one resource are one role plus an exception.

**Which roles are stale?** `builtin.membership.age` (Stale Membership) and `builtin.membership.expired` (Expired Membership) are time-varying factors recomputed by the nightly risk sweep, so staleness surfaces without an event. `ROLE_AUDIT_REPORT` gives you the change history per role.

### What OpenIAM does not do

**There is no role mining or clustering.** Nothing in the product proposes candidate roles from observed entitlement patterns. If you want bottom-up role discovery, export `ENTITLEMENTS_MEMBERSHIP_REPORT` and cluster it externally, then load the result back through `ent_csv_ex2`. Do not wait for a screen that does it.

**There is no catalog-wide duplicate detector.** Role Comparison is pairwise: you pick the second role by hand, one pair at a time, and nothing sweeps a catalog for you. That is why the measurement above matters — narrow the candidate pairs first by name similarity and managed system, then compare each pair.

**There is no role versioning or approval-of-design workflow.** The role row is mutable and the audit log is the only history. Your version control for role *design* is the CSV file you imported from.

## Converge: collapse the duplicates

{% stepper %}
{% step %}

### Clone the survivor first

Use the role clone action (audit `CLONE_ROLE`) to make a disposable copy of the role you intend to keep. Convergence changes access for everyone in both roles; you want somewhere to test.
{% endstep %}

{% step %}

### Diff the pair

Open the survivor's entitlements screen, press **Compare with...**, and pick the duplicate in **Select an object to compare with**. Read the result before touching anything. Remember it covers child roles, groups and resources only, and excludes resources of type `MENU_ITEM` and `WORKFLOW` — so navigation and workflow differences will not appear and must be checked by hand.
{% endstep %}

{% step %}

### Decide row by row, then apply

Mark only the rows you intend to change — the add icon on a row marks it to be added and turns the name green, the delete icon marks it for removal and strikes the name through in red. Leave every other row alone, then press **Save**. The write lands on the **primary** role, meaning the one whose entitlements screen you pressed **Compare with...** on, not the one you selected. **Cancel** discards everything. Audit records `COMPARE_ROLE_ACCESS`, and Save returns you to the primary role's entitlements screen.
{% endstep %}

{% step %}

### Move the members, then retire the duplicate

Move members with `ADD_ENTITLEMENT` / `DELETE_ENTITLEMENT` from `/webconsole/entitlements-bulk-operations` (audit `ENTITLEMENT_BULK_OPERATION`). Then set the duplicate's status to inactive — do not delete it yet. A role referenced by a business-rule target, a live campaign, a request catalog entry or an SoD rule set must have those references cleared first.
{% endstep %}

{% step %}

### Confirm nobody lost anything

For a sample of members of both original roles, compare effective access before and after. Membership rows moving is not the same as effective access being preserved, particularly where the two roles had different parents.
{% endstep %}
{% endstepper %}

## Retrofit: fix the whole catalog at once

Most catalogs fail governance on four things: no owner, no risk rating, no membership duration, and no certification scope. All four are bulk-fixable.

`/webconsole/entitlements-bulk-operations` exposes `BulkOperationEnum`; the catalog-relevant operations are:

| Operation                                                    | Retrofits                                                          |
| ------------------------------------------------------------ | ------------------------------------------------------------------ |
| `CHANGE_OWNER`                                               | Accountability, which certification reviewer resolution depends on |
| `CHANGE_ADMIN`                                               | Delegated maintenance                                              |
| `CHANGE_RISK`                                                | The input to `builtin.entitlement.sensitivity`                     |
| `CHANGE_MEMBERSHIP_DURATION`                                 | Default expiry on new memberships                                  |
| `CHANGE_MAX_USERS`                                           | Capacity and licence limits                                        |
| `CHANGE_STATUS`                                              | Retiring a cohort of roles together                                |
| `CHANGE_VISIBLE`                                             | Removing dead roles from the request catalog without deleting them |
| `CHANGE_DESCRIPTION`                                         | The text reviewers actually read                                   |
| `CHANGE_ASSIGN_TO_ALL`                                       | — see the warning below                                            |
| `ADD_ENTITLEMENT_ATTRIBUTE` / `DELETE_ENTITLEMENT_ATTRIBUTE` | Catalog metadata such as source and version                        |

The full 25-value list is in [Enumerated values](/administration-guide/reference/enums.md#bulk-operations).

{% hint style="danger" %}
`CHANGE_ASSIGN_TO_ALL` sets "assign to all users" across every role in the selection. On a role carrying a managed-system entitlement, that is a mass provisioning event. Never include it in a retrofit batch; run it alone, on one role, with a measured population.
{% endhint %}

Do the retrofit as several narrow batches, not one wide one. Bulk operations are recorded as a single `ENTITLEMENT_BULK_OPERATION` audit event, so a batch you cannot describe is a batch you cannot unpick.

## Automate: move grants off human hands

Once the catalog has owners and risk ratings, decide per role which mechanism owns its population, and pick exactly one:

| Population is decided by     | Mechanism                                                 | Page                                                                                       |
| ---------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| Governed identity attributes | A business rule with paired on-match and no-match targets | [Build a business rule](/administration-guide/access/business-rules.md)                    |
| A human decision             | A request with an approval flow                           | [Configure requests and approvals](/administration-guide/requests.md)                      |
| An authoritative HR feed     | Synchronization                                           | [Set up a synchronization](/administration-guide/lifecycle/synchronize-and-import-data.md) |
| Nothing repeatable           | A direct grant with an end date and a documented reason   | [Manage roles and groups](/administration-guide/access/manage-roles-and-groups.md)         |

Two facts change how you plan this.

**Business rules are not recalculated on a schedule.** `PERFORM_BR_REPROVISION` ships disabled, seeded at cron `0 0 0 * * ?`. Once you attach a rule to a role, that rule owns the population from the next midnight onward, and manual grants on that role will be reverted. That is the desired behaviour — just do not be surprised by it, and do not leave a manual-grant process running alongside.

**End dates do not enforce themselves.** `AUTO_REVOCATION` ships **disabled**. Setting `CHANGE_MEMBERSHIP_DURATION` across the catalog records durations that nothing acts on until you enable that task. Enable it, or revoke through a rule or a campaign, and be honest about which.

## Govern: make the catalog reviewable and safe

**Certification.** Campaign scope is where role review actually happens. The `ROLE_PARTICIPATE_CERTIFICATION` template field ships, drives no control on the role form and is read by nothing, so do not plan around a per-role opt-in flag; the flag certification actually reads is on the resource. Scope campaigns from [Plan and scope a campaign](/administration-guide/governance/certification/plan-and-scope-a-campaign.md), and check in preview that the reviewer can see enough to decide.

**Segregation of duties.** Build SoD rule sets over the converged catalog, not the raw one — every duplicate role is a rule you have to write twice. Activating a rule set auto-creates resources of type `SOD_ENTITLEMENT`, overridable with the property `org.openiam.sod.ruleset.resource.type`, which ships `SOD_ENTITLEMENT`. See [SoD policies and rule sets](/administration-guide/governance/sod/sod-policies-and-rule-sets.md).

**Risk.** Set every role's risk rating, because an unset rating scores `0` — "unknown sensitivity is not itself a risk signal". A catalog of unrated roles produces a risk report that says nothing.

## Keep it shallow

Prefer containment to inheritance. A three-level parent chain grants everything at every level, and a reviewer presented with the child role cannot see what they are approving. OpenIAM gives you no depth limit and only a direct-self-reference cycle check, `CANT_ADD_YOURSELF_AS_CHILD`, so depth discipline is entirely yours to enforce. Nothing on any screen reports the depth of your hierarchy; if you need that number you must walk the tree, role by role, from the roots.

Depth costs you at the target too. What a connector writes into an account's group attribute comes from the user's **direct** memberships only, so a group two levels down a role hierarchy grants inside OpenIAM and changes nothing in the directory — see [What nesting does not reach](/administration-guide/access/manage-roles-and-groups.md#what-nesting-does-not-reach).

If you find yourself creating a role per person, the answer is not a deeper hierarchy. It is either an attribute-driven business rule over one shared role, or a requestable exception role with its own owner and duration.

## Verification

An engineering change is done when you can show all five:

| Claim                                     | Evidence                                                                                            |
| ----------------------------------------- | --------------------------------------------------------------------------------------------------- |
| The catalog was loaded as designed        | The CSV file, plus `IMPORT_ROLE` / `IMPORT_GROUP` audit events                                      |
| The duplicates were collapsed             | `COMPARE_ROLE_ACCESS` audit events, plus a fresh diff showing no differences                        |
| Every role has an owner and a risk rating | `ENTITLEMENTS_REPORT`; `ENTITLEMENT_BULK_OPERATION` audit events for the retrofit                   |
| Grants moved to a governed path           | The share of memberships tagged `ADMIN_ASSIGNMENT` fell; `builtin.membership.origin` scores dropped |
| No member lost access in the process      | Effective access before and after for a sample of each affected role                                |

## When it goes wrong

| Symptom                                                                       | Cause                                                                                               | Fix                                                                                                                                                                                                                                                                                                                                                                                                              |
| ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Compare says two roles are identical but users behave differently             | Compare covers child roles, groups and resources, and excludes `MENU_ITEM` and `WORKFLOW` resources | Diff users, attributes, parents and scalar fields by hand                                                                                                                                                                                                                                                                                                                                                        |
| A retrofit changed more roles than intended                                   | Bulk operations act on the whole selection and record one audit event                               | Narrow batches; verify the selection count before submitting                                                                                                                                                                                                                                                                                                                                                     |
| Roles you retired keep reappearing on requests                                | The role is inactive but still visible, or still in a request catalog entry                         | `CHANGE_VISIBLE` to false and clear the catalog entry before deleting                                                                                                                                                                                                                                                                                                                                            |
| A role cannot be deleted                                                      | Something still references it — child resource, business-rule target, campaign, SoD rule set        | Clear the references first; OpenIAM refuses the delete while a membership still points at the role, and does not name the business-rule, campaign or SoD references at all                                                                                                                                                                                                                                       |
| Every role scores the same risk                                               | Risk ratings are unset, which scores `0`                                                            | Set risk with `CHANGE_RISK`; see [Risk factor reference](/administration-guide/risk/factors.md)                                                                                                                                                                                                                                                                                                                  |
| A role's nested groups grant inside OpenIAM but never appear in the directory | Target membership attributes are built from direct memberships only                                 | Grant the groups directly as well — [What nesting does not reach](/administration-guide/access/manage-roles-and-groups.md#what-nesting-does-not-reach)                                                                                                                                                                                                                                                           |
| A role looks fully populated but grants nothing                               | The role's exclude-from-authorization flag is set, or the authorization graph is stale              | No screen shows the flag — read `ROLE.EXCLUDE_FROM_AUTH` as described in [the role field on neither the form nor the template](/administration-guide/access/manage-roles-and-groups/role-and-group-reference.md#the-role-field-on-neither-the-form-nor-the-template). If it is `'N'`, the graph is stale: [Rebuild the authorization graph](/administration-guide/operations/rebuild-the-authorization-graph.md) |
| Reviewers cannot tell two roles apart in a campaign                           | Role attributes are not rendered in certification entitlement detail                                | Put the distinguishing information in the role name and description                                                                                                                                                                                                                                                                                                                                              |

## Related

* [The access model](/administration-guide/access/access-model.md) — what the objects mean before you engineer them
* [Role and group field reference](/administration-guide/access/manage-roles-and-groups/role-and-group-reference.md) — the 23 entitlement CSV columns, and the shipped role and group form fields
* [Manage roles and groups](/administration-guide/access/manage-roles-and-groups.md) — compare, bulk operations, dependency-aware removal
* [Build a business rule](/administration-guide/access/business-rules.md) — moving grants off human hands
* [Risk factor reference](/administration-guide/risk/factors.md) — turning catalog quality into a number
* [Shipped reports](/administration-guide/reference/shipped-reports.md) — the 25 reports, including the entitlement ones

*Applies to OpenIAM 2026.8.1. Last reviewed 3 September 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/access/role-engineering.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.
