Account Rules
Classification and ownership rules, with priority-based multi-rule handling per type.
What are Account Rules?
Account Rules determine (a) which accounts belong to which type (classification) and (b) which identity they correlate to (ownership). A rule is a set of conditions — every account or identity that matches receives the rule's effect.
Classification rules
A classification rule has a list of conditions
built from fields on the account itself:
attributes.userType eq 'Member'— Entra stylenative_account_name starts_with 'a-'— user principal name (UPN) patternattributes.accountEnabled eq true
Conditions combine with AND or OR.
Need more complex logic? Use a Jinja escape:
{"account.native_account_name.startswith('adm-')"}.
Ownership rules
Ownership rules link an account to an identity. A rule has conditions that compare identity fields against account fields:
identity.email == account.userPrincipalName (lower)identity.employee_id == account.attributes.workdayId
For Entra-style environments, email == UPN works
out of the box in about 90% of cases.
Multiple rules — scope first, then priority
A system can have several rules. The engine evaluates them in order and the first match wins, so the ordering is the logic. Two things decide it, in this sequence:
- Scope. A rule written for one account type is considered before one written for a system, which is considered before a tenant-wide rule. A narrower rule always pre-empts a broader one, whatever its priority number.
- Priority within that scope — lowest number first.
- Rule 1 (priority 10): "Privileged admin pattern" —
UPN starts_with 'a-'→ type personal-privileged - Rule 2 (priority 100): "Default" — all other Members → type personal
💡 Tip
For legacy or migrated accounts that do not follow the current naming convention: add an extra rule with a lower priority and a tailored pattern. The engine matches that rule first, and the old account still receives the correct type and owner.
Correlation runs before classification
Within a single pass the engine correlates first, then classifies. That ordering is not incidental: rules can test the correlated identity — is this account linked to anybody at all, and if so is that a person or a machine — and neither question can be answered before the link is resolved.
It is what makes the tenant-wide defaults sensible rather than arbitrary. Out of the box, an account correlated to a service, system, application, AI-agent or device identity classifies as a service account, and an account correlated to anybody else classifies as personal. Only the non-human side enumerates identity types, so a new human type falls through to personal on its own; a new machine type is a deliberate addition.
An orphan is left unclassified, on purpose
An account with no correlated identity matches neither default and keeps no classification at all.
That is a decision, not a gap. The tempting alternative — stamp it personal, since most accounts are — produces a governance model in which an account nobody owns is indistinguishable from an account belonging to a named employee. An unclassified account is a visible, countable question; a wrongly-classified one is an answer, and the wrong one. Orphans are the orphan finding's business, and this engine deliberately declines to hide them.
The same reasoning applies to the tenant-wide defaults generally: they are labelled fallbacks and sit at high priority numbers so that any rule you write pre-empts them. They exist to stop accounts falling through entirely, not to express a view about your estate.
Read/write parity invariant
Account Rules (read) and the provisioning configuration (write) MUST
be aligned on the same identifier fields. If ownership says
email == UPN but the write-mapping fills UPN from
gen_username('first.last'), then the accounts the platform
creates become orphans.
Further reading: