Access reviews
Access reviews (historically called "certification" or "attestation") are the
periodic and event-driven confirmation that access is still correct. A reviewer
confirms or revokes an assignment; compliance gets a record that the review
happened. There is a single configuration source (CertRule), so the same engine
drives every kind of review.
One engine, three trigger types
There is a single access-review engine. What used to be two parallel producers
is now one rule type with a trigger_type that determines how reviews are
spawned. Every resulting task lands in the same unified review inbox as a
DecisionTask.
trigger_type |
Trigger | Question it answers |
|---|---|---|
continuous |
Periodic (a rolling clock per subject) | "Is this still correct N days after the last approval?" |
event |
A bus event (mover, new assignment, content change, drift, …) | "Something structurally changed — review now" |
campaign |
An explicit campaign with a fixed window | "Review this whole population before the audit deadline" |
The reviewer works continuous and event reviews from one inbox; campaigns are tracked on their own tab. The mental model is that rule-based recertification is the spine and campaigns are the fallback — for audits that must happen on a fixed calendar, or for object sets you deliberately do not want on an automatic clock.
CertRule — the single configuration source
All reusable review configuration lives in CertRule. Its key fields:
| Field | Meaning |
|---|---|
target_kind |
What is being reviewed: grant (an assignment), role, policy, identity_validity, ownership, or account |
trigger_type |
continuous, event, or campaign |
reviewer_chain (inline) or reviewer_chain_id (named) |
Who reviews, in which order, and with what quorum — see Approver chains |
review_scope |
For assignment reviews only — how the items are grouped (see below) |
conditions |
A filter that selects the slice of objects this rule covers (its "bucket") |
auto_action_on_deadline |
What happens to an undecided item at the deadline — differs per target_kind |
Because a rule's conditions scope a slice of the object space, the platform can
expose the gap: every reviewable object should fall under some rule. The ones
that fall under none are never reviewed, and are reported as such — see
Coverage below.
A rule, and what it produces
Concretely. A rule that says every assignment on a critical-risk entitlement is reviewed every ninety days, by the entitlement's owner and then the security team:
target_kind: grant
trigger_type: continuous
conditions: entitlement.risk_level = "critical"
review_scope: account
interval: 90 days
reviewer_chain: step 1 — object owner (quorum: all)
step 2 — security team (quorum: any)
auto_action: revoke
The continuous sweep walks the assignments that match, skipping any whose clock has not yet run out. For one that has — say R. Okafor holds Production Database Admin, last confirmed ninety-one days ago — it spawns step one only: one task per resolved owner, sharing a fan-out group, carrying the quorum. Step two does not exist yet. It is created once step one's quorum is met, so a chain whose second step is never reached never generates work for the security team.
The reviewer sees the item in their inbox grouped under Okafor's account on that
database, alongside the other assignments held through the same account, because
review_scope is account. If they revoke it, the assignment is removed and the
decision is recorded with them as decider. If nobody decides before the deadline,
auto_action revokes it — and that is recorded too, as an automatic action
rather than as a human decision. A deadline is not an approval, and a record that
blurs the two is worth less than no record.
Target kinds group into three pillars
The target_kind values organise into three business questions:
| Pillar | Question | Target kinds |
|---|---|---|
| Assignment | Does this right still belong to this person? | grant |
| Validity | Should this object still exist? | identity_validity (external and non-human identities) |
| Composition | Should this object still contain these items? | role, policy |
Owner attestation (ownership) sits alongside these: each owner periodically
confirms they still own their objects. Administrators toggle which kinds are
reviewable in the review configuration; a rule can only target a kind that is
enabled.
💡 Tip
Human identities sourced from human resources (HR) are excluded from
identity_validityreviews — HR is authoritative for whether the person still exists. Their access is still reviewed, through the assignment (grant) path.identity_validityreviews apply to non-human and external identities only.
Continuous sweep — dispatched per target kind
The continuous sweep dispatches per target_kind:
| Target kind | What it iterates |
|---|---|
grant |
Each assignment |
role |
Each role |
policy |
Each access grant policy |
identity_validity |
Each identity, filtered by identity type or source system |
ownership |
Each owned object (owner attestation) |
account |
Each account, reviewed as an object |
Each sweep spawns the first step's tasks — one per approver, sharing a fan-out group and carrying quorum metadata. A later step is created lazily once the previous step's quorum is reached.
Event-triggered reviews
Event reviews bind to the assignment-side event catalogue — for example an identity transfer (a mover), a new assignment being granted, drift detected at reconciliation, an emergency-access use, or a role or policy content change. Two default rules are seeded and on by default:
- A mover rule — after a transfer, the person's current assignments are reviewed.
- A new-assignment rule — a fresh assignment on a high-risk entitlement is reviewed just in time.
An event is a just-in-time review that resets the periodic clock: a second identical event does not spawn a duplicate while a review is already open for that item, and completing the review updates the "last reviewed" timestamp the continuous sweep reads. This is the only producer of mover and new-assignment reviews — there is no separate parallel mechanism.
Oversight and deciding are different jobs
The access-reviews workbench has five tabs, and none of them is a place to decide anything.
| Tab | Answers |
|---|---|
| Insight | How are the running reviews going — progress, who is behind, what auto-closes soon |
| Campaigns | The fixed-window reviews and their state |
| Rules | The CertRule definitions |
| Coverage | What is reviewed, and what is not |
| Config | Which kinds are reviewable at all |
What the workbench can do is nudge: remind a reviewer, or reassign an item to someone else. Both are recorded. Deciding happens in one place — the inbox — and a deep link to the retired "my reviews" tab now lands there rather than reopening a second decision surface.
The split is between two different people on two different days. A compliance lead watching a campaign burn down needs progress, laggards and deadlines across thousands of items; a reviewer needs the five items that are theirs, with enough context to judge them. Serving both from one screen produces a screen that is a weak version of each. More importantly, a second place to decide is a second implementation of what deciding means — the self-review refusal, the quorum arithmetic, the lazy creation of the next step, what gets written to the audit trail. Two implementations do not stay identical; they diverge, and the divergence shows up as an item that was decidable from one screen and not the other, or a decision that recorded less than the other would have. One decision surface is not a user-interface preference. It is how the guarantees stay true.
The tabs are also persona-gated: a reviewer with no configuration rights sees Insight and nothing else, while an auditor sees the observation surfaces without the ability to edit rules.
Coverage is not a two-way answer
The obvious way to report review coverage is as a fraction — this many objects covered, that many not. It is also wrong, because it collapses several very different failures into one number and hides the worse ones behind a green tile.
Every reviewable object on each of the six axes resolves to one of these states:
| State | Meaning | Counts as |
|---|---|---|
| Covered | A rule with a clock matches this object, and the chain reaches the reviewer it names | coverage |
| Covered via fallback | Same, but the named reviewer did not resolve, so the chain fell back to a substitute who did — and the tile says so | coverage |
| Event-only | A rule matches, but every rule that matches is event-triggered. There is no periodic clock | a gap |
| No rule | Nothing matches it. It is never reviewed, and nobody has claimed otherwise | a gap |
| No owner | Only on owner attestation: the object genuinely has nobody to attest it | a gap |
The middle two are the ones that matter, because they are where a green tile can lie.
Event-only is the sharper of the pair. An object matched only by an event rule looks governed from every angle except the one that counts: there is a rule, it matches, it appears in the configuration as a control a compliance lead can point at in an audit — and it starts a review when a transfer or a new grant happens, and never otherwise. There is no periodic clock: next review reads on event rather than a date, and last certified stays empty until the first review actually fires. That is a routing statement dressed up as an assurance statement, so it is counted with the gaps. The detail pane still shows who would review it when an event fires, because that review is real; the tile simply does not count a review that nothing schedules.
Covered via fallback is the opposite move: it is real coverage, said out loud. A review step can no longer end at nobody. The platform resolves the chain the whole way — through the full ownership cascade, not merely a check for an explicit owner row — and where the named reviewer does not resolve, it takes the tenant's configured fallback for that step's slot, and failing that the IAM terminal, which exists by construction. So an object reached by a substitute is genuinely reviewed and counts as covered, but it is reported as via fallback rather than silently folded into the green: the person who wrote the rule should be able to see that their first choice was not the one who got the task. No owner survives as a state only on owner attestation, which has its own spawner and where "nobody owns this" is the honest answer rather than a routing failure.
A manager step is reported as a distribution over the holders, not as a single verdict: an entitlement held by sixteen people is covered for the holders whose manager resolves and covered via fallback for the rest — routed to that step's configured substitute, and the IAM terminal beyond that — never a gap, since the same "a review step can no longer end at nobody" guarantee applies per holder. One holder with a manager does not make the tile report all sixteen as reaching their manager directly; it reports the real split.
The same resolution drives the tile, the list and the per-object detail, which is why they cannot disagree. A list saying covered next to a detail pane saying nothing covers this object was the earlier version of this page's problem, and one shared answer is what fixed it.
"Our resolver is broken" and "this object has nobody" are different statements, and the code keeps them apart as far as it can: a defect in the resolution propagates rather than being absorbed, so it surfaces as a failure instead of quietly becoming another object in the gap count. A genuinely operational failure — a timeout reaching something it needs — is absorbed so that one bad row cannot take down the whole coverage view, but never silently: it is logged as a failed step. If a coverage figure moves and no configuration changed, the logs are the place that says why.
Review scope — account-first for assignment reviews
Assignments are rooted on an account (an entitlement is held through an account,
not directly by an identity). So an assignment review is presented account-first
by default. review_scope controls the presentation:
review_scope |
Presentation |
|---|---|
account (default) |
Assignments grouped per account — the honest shape, since account-level risk (shared, privileged, orphan) lives at the account layer |
account_with_access |
Adds an "account invalid → revoke all" lead control that revokes the whole account group through the normal per-assignment decisions |
grant_flat |
A deliberate opt-out that hides the account grouping and lists assignments flat |
review_scope is only consulted for target_kind=grant; role, policy and
validity reviews have no account layer. It is purely presentation — the decision
mechanics are unchanged.
Self-review guard
For grant and identity_validity reviews, the reviewer may not be the subject.
The decide action is refused on the server, and the item must be reassigned first.
Ownership attestation is exempt — an owner confirming their own ownership is the
entire point.
Non-human identity ownership review
On startup, a default rule is seeded that enforces a yearly ownership review for
non-human identities — service accounts, artificial-intelligence agents,
applications, system accounts and internet-of-things devices. It targets
identity_validity, routes to the identity's manager as a proxy for the owner,
and flags rather than auto-revokes on the deadline. Artificial-intelligence agents
carry a shorter ladder on top of that floor, based on how autonomously they act —
see AI agent governance.
Known edges, published rather than discovered
- Coverage counts objects, not risk. An axis at 100% means every object on it resolves to a real reviewer, not that the reviews are good ones or that the intervals are appropriate.
- A rule that matches nothing is not a gap. It reports as a rule with no items, which is indistinguishable from a rule whose population is currently empty. Both are legitimate; neither is flagged.
- The workbench cannot decide, including for someone who has left. An item belonging to a departed reviewer is reassigned, not decided on their behalf. This is a deliberate refusal, and it means a stalled review needs an act by a person rather than an administrative override.
Further reading: