> 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/advanced-lifecycle-patterns.md).

# Advanced lifecycle patterns

Future starts, contractors, leave, transfers, protected populations, termination, rehire and two HR sources — each connected to the transform return code, batch task or business-rule action that imple

## What this is, and when you need it

Real workforce lifecycles are not join, move, leave. People have future start dates, delayed terminations, extended leave, concurrent assignments, contract end dates, rehires, and privileged access that must not change without review. More than one HR or directory system may describe the same person.

OpenIAM has no "JML" switch. Every pattern below is assembled from four building blocks, and the point of this page is to name which block does what:

| Building block                   | What it decides                               | Where                                                                                                                                                                        |
| -------------------------------- | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Transform script return code** | Which lifecycle event this source record *is* | `TransformScript` constants `-1`…`11` — [Synchronization script contracts](/administration-guide/reference/synchronization-script-contracts.md#transformscript-return-codes) |
| **Business rule action**         | What access follows from the identity's state | The **Action** a business rule performs — [Grant birthright access](/administration-guide/lifecycle/birthright-access.md#the-four-mechanisms-and-choosing-between-them)      |
| **Effective-dated membership**   | When a grant starts and stops                 | `startDate` / `endDate` on the membership xref                                                                                                                               |
| **Batch task**                   | The clock that realises a future or past date | `BATCH_CONFIG` — see the table below                                                                                                                                         |

The specific risk on this page: four of the six relevant batch tasks ship **disabled**, and two ship **enabled**. A design that assumes the wrong one is a design that silently does nothing, or silently does something at 17:00 every day.

## The effective-dating batch tasks

Cron expressions are Quartz, six fields, evaluated in the batch service's own time zone. Confirm any row's live state on **Administration → Batch Tasks**, which is also where the bean and method are shown.

| `TASK_ID`                | `TASK_NAME`                                       | Cron                             | Ships        | Bean / method                                                               | `PARAM1`                                                |
| ------------------------ | ------------------------------------------------- | -------------------------------- | ------------ | --------------------------------------------------------------------------- | ------------------------------------------------------- |
| `PROV_BY_START_DATE_FUT` | Activate by Start Date                            | `0 0 0 1/1 * ?` (midnight daily) | **enabled**  | `provisionAfterStartDateInitializer.initializeProvisionAfterStartDate`      | days of look-ahead; `null` → `1`                        |
| `LEAVER_PROCESS`         | Disable by last date                              | `0 0 17 * * ?` (**17:00 daily**) | **enabled**  | `leaverProcessInitializer.initializeLeaverProcess`                          | days of look-back; `null` → `0`                         |
| `PERFORM_BR_REPROVISION` | Perform Business Rules recalculation              | `0 0 0 * * ?` (midnight daily)   | **disabled** | `businessRuleRecalculationInitializer.perform`                              | Seeded enabled in 4.2.1.0, disabled again as of 4.2.1.5 |
| `AUTO_REVOCATION`        | Notification of expiring/revoke of expired access | `0 0 20 * * ?` (20:00 daily)     | disabled     | `revocationOfEndDatedAccess.initializeRevocationOfEndDatedAccess`           | —                                                       |
| `FUTURE_USER_PROV`       | Provision Deprovision on date                     | `0 0 0 1/1 * ?`                  | disabled     | `provisionDeprovisionOnDateInitializer.initializeProvisionDeprovisonOnDate` | **minutes** of window; seeded `60`                      |
| `DELETE_PENDING`         | Delete/Deactivate by last Data                    | `0 0/30 * * * ?` (every 30 min)  | disabled     | script `batch/deleteOnExpiration.groovy`                                    | —                                                       |

`AUTO_REVOCATION` was seeded in 4.1.3.0 as *Revoke expired access* and renamed in 4.2.1.1; a runbook using the old name predates that release. `FUTURE_USER_PROV` had a different bean and name for part of 4.2.1.6 and was corrected within the same release, so a document naming `futureProvisioningInitializer` is out of date — no bean of that name exists. Both are listed with their current values in [Shipped batch tasks](/administration-guide/reference/shipped-batch-tasks.md#the-fifteen-named-tasks).

Two tasks ship enabled and are worth understanding before you design anything around them:

**`PROV_BY_START_DATE_FUT`** selects every identity whose **Start date** falls between today 00:00:00.000 and today + `PARAM1` days 23:59:59.999 — **with no status filter** — and hands each one to the script named by `org.openiam.provision.after.start.date.initializer` (shipped `batch/ActivateProcess.groovy`). Filtering by status is the script's job, not the task's: the shipped script acts only on an identity whose status is `PENDING_START_DATE` and does nothing otherwise. Parent audit action `ACTIVATE_USERS_BY_START_DATE`, child `ACTIVATE_USER` per user.

**`LEAVER_PROCESS`** at 17:00 delegates entirely to the script named by `org.openiam.idm.leaver.script` (shipped `batch/LeaverProcess.groovy`): it wires the script's inherited service fields, asks it for a list of users, then calls it once per user. Parent audit action `TERMINATE_USERS_BY_LAST_DATE`, child `AUTOMATIC_USER_TERMINATION` per user. The shipped script selects identities whose **End date** falls between midnight `PARAM1` days ago and midnight this morning, and disables each one.

So on a stock install, users are activated by start date at midnight and disabled by last date at 17:00, and both behaviours are entirely determined by two Groovy scripts you can replace. The task and its selected script version are **one control** and must be promoted and tested together.

### The base classes your script extends

Each of the two scripts extends a fixed base class with a fixed set of methods to implement. An activation script extends `AbstractActivateProcess` and implements `activate(User user, String auditLogId)`. A leaver script extends `AbstractLeaverProcess` and implements `getUsers(int daysBeforeLeaver)`, which returns the population, and `perform(User user)`, which acts on one of them; it may also override `init()` for its own setup, in which case it must call the inherited one first.

Note the split: the leaver base class owns both the *population query* and the *action*, which is why a leaver policy change is usually a change to one Groovy file rather than to any configuration.

Both base classes also give your script a set of OpenIAM service fields it can call without declaring them — but **the two tasks populate them differently, and only the leaver task populates all of them.** Which fields are live on which path is stated once, in [which inherited fields each task populates](/administration-guide/reference/shipped-batch-tasks.md#which-inherited-service-fields-your-script-can-use). Read it before you write either script; it is also where the shipped activation script's own failure is recorded.

***

## Pattern 1: future-dated joiner

**Problem.** Accounts and access must be ready at the approved moment, without giving a future employee early access.

**Configuration.**

1. Import the person with an immutable workforce identifier and map the approved start date. Set the user's status to a pre-start state — the shipped activation script tests for `PENDING_START_DATE` exactly.
2. Return `NO_DELETE` (`0`) from the transformation for pre-start records, or `INITIATE_ADD_USER_AND_INVITE_WORKFLOW` (`9`) if the person should be invited to set their own credentials at activation.
3. Create only the pre-start objects your policy allows. Separate identity staging from target-account enablement.
4. Where the entitlement itself should start later, use effective-dated memberships — a `startDate` on the role, group, resource or organization xref — and enable `FUTURE_USER_PROV` to realise them.
5. Set `PARAM1` on `PROV_BY_START_DATE_FUT` to a look-ahead in days that is at least as wide as one run interval, so a missed run recovers.
6. Test: a start in the future, a start inside the current window, a missed prior window, a changed start date, a cancelled hire, and a daylight-saving transition.

**What actually activates the person.** The shipped activation script does three things: it moves the identity to status `PENDING_INITIAL_LOGIN` and saves it as a real provisioning save, so the account is created at that moment; it re-reads the identity's logins and takes the one on the default managed system; and it generates a password-reset token and sends the `NEW_USER_ACTIVATION_NOTIFICATION` mail template with `BASE_URL`, `USER_ID` and `TOKEN` parameters.

{% hint style="danger" %}
**On this build the third step cannot run.** The shipped script reaches for two services that the activation task does not supply, so the identity is saved with its new status and then the run fails on a null reference before the token is generated or the mail is sent — leaving an activated identity with no activation mail, and the rest of that window's identities unprocessed. This is the single most important thing to know before designing around `PROV_BY_START_DATE_FUT`, which ships **enabled**: [which inherited fields each task populates](/administration-guide/reference/shipped-batch-tasks.md#which-inherited-service-fields-your-script-can-use).
{% endhint %}

{% hint style="warning" %}
The shipped script also hard-codes `https://example.openiam.com/idp/activate` as the `BASE_URL` notification parameter. Left as shipped, every activation email that does get sent points at `example.openiam.com`. Replace it — the correct value is normally `org.openiam.webconsole.defaultBaseDomain` plus the activation path, and that property ships **empty**.
{% endhint %}

## Pattern 2: contractor with approved engagement dates

**Problem.** A contractor should hold only approved access for the approved period, even when the source forgets to send a termination.

**Configuration.**

1. Import worker type, sponsor/manager, start date and contract end date. `addSupervisorToUser(pUser, searchFiled, searchValue, supervisorType, managedSysId)` on `AbstractUserTransformScript` is how you attach the sponsor.
2. Reject or quarantine a contractor with no stable identifier, no sponsor, or an uninterpretable end date. Return `SKIP` (`-1`) so it counts as an error, not `SKIP_OK`. Never manufacture a long default silently.
3. Assign the contractor base role through a business rule conditioned on worker type, status **and** dates — not through `ASSIGN_TO_ALL`.
4. Route specialist access through requests with a maximum duration no longer than the engagement end date.
5. Set membership `endDate` values so the revocation process can find them. `addUserRoleByName(pUser, roleName, managedSysId, rights, startDate, endDate, description)` takes both dates directly.
6. Enable `AUTO_REVOCATION` only after reading its properties. It reads `org.openiam.workflow.access.auto.expire.days.reminder` (shipped **`7`**) and `org.openiam.workflow.access.auto.expire.reminder.base.url` (shipped **`http://localhost:8080`**), audits `EXPIRING_ACCESS_PROCEDURE` with `SEND_REMINDER_ABOUT_EXPIRING_ACCESS` children, and passes `DAYS_BEFORE_EXPIRATION` into the reminder notification. There is also a master switch, `org.openiam.workflow.access.auto.expire`, labelled *Use Batch Task for Handling Expiring Access*, which ships **`false`**.
7. Notify the sponsor before expiry, then verify the generated revocation and the target deprovisioning.

Test: an extension, an early termination, a missing sponsor, an expired contract arriving late, and repeat processing.

{% hint style="danger" %}
**Do not use `PROLONG_ALL_ACCESS` to extend one contract.** It sets the end date of *every* membership not already marked for deletion to today plus the number of days configured on the rule. Every role, group, resource and organization membership the person holds is moved to the same new date — including access that was deliberately expiring sooner and access granted by an unrelated approval. Extend the one membership.
{% endhint %}

## Pattern 3: leave of absence or temporary suspension

**Problem.** The person remains employed, some or all access must be suspended, and it must be restorable without losing ownership, history, or unrelated future end dates.

**Configuration.**

1. Define a distinct leave state and effective dates. Do not overload termination. The user statuses `LEAVE`, `LEAVE_WITH_PAY` and `LEAVE_OF_ABSENCE` are all treated as "terminated now", together with `DEACTIVATED`, `TERMINATED` and `DECEASED`. **Moving a user into any leave status triggers the revoke-access path**, exactly as a termination does, and only once — a user already in one of those statuses does not trigger a second trigger while they stay in that state.
2. Classify applications: retain, disable the account, end-date the access, revoke the access, or require a human decision. Different applications will fall in different buckets and that is the whole design.
3. Return `INITIATE_REVOKE_ACCESS_WORKFLOW` (`7`) from the transformation when the source signals leave and the revocation should be reviewed. OpenIAM then raises a revoke-access request whose roles and groups come from the transformed user's roles and groups, with the comment *"Please confirm revocation of following access items."* — so the reviewer sees a list and confirms it.
4. For a broad suspension, inventory every current entitlement and record its original source and end date **before** changing anything. You will need that snapshot to restore.
5. For a selective suspension, target named roles, groups and resources. Do not use `END_DATE_ALL_ACCESS`.
6. Route privileged and regulated systems through approval or manual fulfilment when automatic restoration is not permitted.
7. On return, re-evaluate current qualification. Do not replay the pre-leave snapshot if the job or the policy changed.

{% hint style="danger" %}
**`RESTORE_ALL_ACCESS` clears the end date on every current membership.** Every membership not already marked for deletion comes back with **no** end date at all. Anything that had a legitimate, independent expiry — a contract, a project, an approved temporary grant — becomes permanent. Use it only when every membership the person holds is governed by the same leave policy and you have the pre-state proven. Otherwise restore the specific memberships.
{% endhint %}

## Pattern 4: transfer, department or job change

**Problem.** Old-job access must go while common and new-job access remain, without waiting for a periodic review.

**Configuration.**

1. Make the HR job and organization attributes authoritative, and identify the effective time. The condition attributes are IDs: `JOB_CODE_ID`, `USER_TYPE_IND`, `EMPLOYEE_TYPE_ID`, `USER_SUB_TYPE_ID`, `LOCATION_CD`, `COST_CENTER`.
2. Build **separate** business roles for old and new responsibilities. Never edit one role to mean two jobs.
3. Configure deterministic rules with paired `ADD_*` / `REMOVE_*` actions — but only where the rule is the authoritative source of that access. A `REMOVE_ROLE` no-match action will remove a role the person also holds through an approved request.
4. Keep requested exceptions in a separate role or a separate grant path so a rule removal cannot erase independently approved access.
5. Return `INITIATE_CHANGE_POSITION_WORKFLOW` (`8`) from the transformation when the position changed and the resulting access needs review. OpenIAM raises a request of type `REVIEW_ASSESS_ON_POSITION_CHANGE`, described as `Position was changed for user <displayName>`, which is the text to search for in the request list.
6. **Preview before you recalculate.** Run the business-rule review and read the `user-recalculation` index; the nightly `PERFORM_BR_REPROVISION` is disabled by default, so nothing will apply the change for you.
7. Run SoD evaluation for the overlap window, and decide explicitly whether old access ends before, at, or after new access begins.

Test: same-day transfer, future-dated transfer, changed back before the effective date, concurrent jobs, missing job code, and a user with the same access granted twice through different paths.

A note on governance: the installed campaign model provides user and application/entitlement certification only, either of which can be narrowed by risk. There is no "title change" campaign type. If a transfer needs human validation, trigger a scoped campaign of one of those two types — see [Plan and scope a campaign](/administration-guide/governance/certification/plan-and-scope-a-campaign.md).

## Pattern 5: protected or privileged population with an automation hold

**Problem.** Executive, break-glass or highly privileged identities may need IAM-team intervention before automatic lifecycle changes execute.

There is **no product feature for this.** It is a pattern you build, and the reason it is worth documenting is that building it partially is worse than not building it.

**Configuration pattern.**

1. Define an authoritative protected-population marker and its owner. A governed user attribute, or membership of a specific group — not a title string.
2. Express it as a user attribute you can test from both a business-rule condition (`USER_ATTRIBUTES` is one of the attributes a condition may name) and a transformation script (`getUser().getUserAttributes()`), carrying reason, approver, start and expiry.
3. Make **every** relevant path check it before generating a change. That means: the transformation script (return `SKIP_OK`, not `SKIP`, and raise a task), every business rule's condition, the request approval flow, and any custom batch script.
4. On hold, create an administrative or manual task carrying the proposed change and the source event. Do not discard it — a discarded change is an audit gap.
5. Preserve emergency security actions. A hold must not prevent an approved immediate lock or disable needed for containment.
6. On release, re-read current source state and recalculate. Do not replay a stale queued snapshot.

**Inventory the paths you have to cover**, because there are more than people expect: synchronization transformations, business rules (including the nightly recalculation), request approvals, direct bulk operations, `PROV_BY_START_DATE_FUT`, `LEAVER_PROCESS`, `AUTO_REVOCATION`, `FUTURE_USER_PROV`, reconciliation situation responses, and `ASSIGN_TO_ALL`. Test each one for a single protected identity. A hold that one path ignores is not a hold.

## Pattern 6: termination and delayed or off-cycle leaver

**Problem.** Termination must stop access reliably even when the source event is late, a schedule misses its window, or one target is unavailable.

**Configuration.**

1. Map the authoritative last date and status, and distinguish immediate involuntary termination from scheduled end of engagement.
2. Decide account disable versus deletion per system, and express it as each managed system's `ON_DELETE` resource property. Absent means `DELETE`. Prefer `DISABLE` where audit or correlation needs the account record.
3. Return `INITIATE_TERMINATE_USER_WORKFLOW` (`10`) from the transformation for source-driven terminations. Set `pUser.accessRightEndDate` in the transformation: it is the **only** source of the termination request's end date, and a request raised without it carries a null end date.
4. Or use `LEAVER_PROCESS`, which is **enabled** at 17:00 daily and driven by `org.openiam.idm.leaver.script`. Document the script version. Set `PARAM1` wide enough to recover a missed run and make `perform(user)` idempotent — the shipped implementation calls `disableUser`, which is naturally idempotent; a custom one that deletes is not.
5. Remove or end-date roles, groups, resources and organizations per policy. Verify inherited and alternate paths — the revoke-access path fires on the transition *into* a terminal status, not on each subsequent save of a user already in one.
6. Track every target result. A completed parent leaver event is not proof that every connector succeeded; read the per-identity `PROVISIONING_DISABLE_IDENTITY` / `PROVISIONING_DELETE_IDENTITY` children and the connector replies.
7. Quarantine failures, retry through the supported path, and escalate high-risk systems by hand.

{% hint style="danger" %}
**`DROP_ALL_ENTITLEMENTS` marks every membership for deletion** — resources, roles, groups **and organization affiliations**, without exception. It is intentionally total and it is not a substitute for a reviewed leaver policy — it removes the affiliations that drive organization-based access as well as the entitlements, so a mistaken application cannot be undone by re-adding a role.
{% endhint %}

## Pattern 7: rehire

**Problem.** A returning worker is the same person and must not automatically regain stale access.

**Configuration.**

1. Correlate on the immutable workforce or person identifier — never an old email or login. `MATCH_FIELD_NAME` of `EMPLOYEE_ID` or `USERID`, or a custom match rule.
2. The transformation will see `isNewUser == false`, so the valid `INITIATE_*` codes are the existing-user ones. A rehire is `INITIATE_EDIT_USER_WORKFLOW` (`6`) if the returning access needs review, or `NO_DELETE` (`0`) if the business rules are trusted to recompute it.
3. Recalculate qualification from the **new** job, organization, worker type and dates. Let the business rules grant; do not restore by hand.
4. **Do not clear old membership end dates.** `RESTORE_ALL_ACCESS` is precisely the wrong action here — it reinstates every expired grant including the privileged ones. Grant what the new engagement approves.
5. Route old privileged and requested exceptions through new approval.
6. Verify: historical identity continuity (same `USER_ID`, same audit history), the new current access, the target account state — re-enabled, renamed and correlated, or replaced — and the certification scope.

Return `INITIATE_ADD_USER_AND_INVITE_WORKFLOW` (`9`) only for a genuinely **new** person. It is accepted only for a record that does not yet correspond to an OpenIAM identity, so a rehire fails it with a failed audit entry reading `Operation 9 not supported for existing user`.

## Complex case: two HR sources during a migration

Use the field-authority matrix in [Import from two authoritative sources without overwriting](/administration-guide/lifecycle/synchronize-and-import-data/import-from-two-authoritative-sources.md#approve-the-field-authority-matrix) for attributes, then add **state** authority on top — because two sources disagreeing about a person's *status* is worse than disagreeing about their phone number.

| Data or state             | Legacy HR                               | New HR                                | Conflict behaviour                                        |
| ------------------------- | --------------------------------------- | ------------------------------------- | --------------------------------------------------------- |
| Person key                | Read the common immutable ID            | Read the common immutable ID          | Reject zero and multiple matches                          |
| Name, manager             | Authority until the cutover date        | Shadow, then authority                | The lower-priority source makes no assertion              |
| Start / last date         | Authority for the legacy population     | Authority for the migrated population | A population marker decides. **Never newest-write-wins.** |
| Termination               | Accepted only for a legacy-owned worker | Accepted only for a new-owned worker  | Ambiguous ownership → security queue, not a guess         |
| Job and access attributes | Explicit per-field ownership            | Explicit per-field ownership          | Reject and alert; preserve the prior governed value       |

Run each synchronization independently with `IS_PROVISION = 'N'` first. Compare results. Then enable only the owned changes. Test: a source outage, a late event, a duplicate worker, a migrated worker still present in both systems, and a rollback to the pre-cutover authority matrix.

The mechanism that makes the population marker work is the transformation's own branch on it. A single config cannot be "authoritative for half the population" declaratively — the transformation reads the marker and returns `SKIP_OK` (`11`) for records the other source owns. `SKIP_OK`, not `SKIP`, so the run's failure count stays meaningful.

## Scheduling and recovery

* Record, for every task: the service time zone, the cron expression, the next three expected fire times, the window/look-back parameter, the worst-case duration, and the overlap behaviour.
* **`LEAVER_PROCESS` runs at 17:00, not midnight.** A design that assumes an overnight leaver sweep will disable people during the working day.
* Use feature-owned scheduling. A synchronization schedule (`Synchronization=<name>`), a reconciliation schedule (`Reconcile=<managed system name>`) and a hand-created batch task are not interchangeable, and the reconciliation one keys its name on the managed system, so two configs on one system collide.
* A scheduled certification generates a **preview** for administrator review; it does not launch a campaign.
* Run each date-driven task once against one disposable future-dated or past-dated identity before enabling recurrence.
* After an outage, calculate the missed window and widen `PARAM1` for one deliberate run. Do not move system clocks and do not run duplicate jobs to catch up.

See [Create a batch task](/administration-guide/automation/batch-tasks.md) for creating and scheduling them.

## End-to-end test matrix

For every lifecycle policy, test all ten:

normal event · missing required attribute · conflicting sources · repeat of the same event · reversal of the event · late event · downstream target failure · scheduler outage · protected identity · rollback

After each, verify seven things: the person record; dates and status; direct **and inherited** access; the target account; any request or manual task raised; the audit tree; and the batch-task history.

## Verification

The audit actions that prove each mechanism ran:

| Action                                                                                                          | Written by                                                                           |
| --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `ACTIVATE_USERS_BY_START_DATE` → `ACTIVATE_USER`                                                                | `PROV_BY_START_DATE_FUT`                                                             |
| `TERMINATE_USERS_BY_LAST_DATE` → `AUTOMATIC_USER_TERMINATION`                                                   | `LEAVER_PROCESS`                                                                     |
| `BUSINESS_RULE_RECALCULATE`                                                                                     | `PERFORM_BR_REPROVISION`                                                             |
| `EXPIRING_ACCESS_PROCEDURE` → `SEND_REMINDER_ABOUT_EXPIRING_ACCESS`                                             | `AUTO_REVOCATION`                                                                    |
| `PROVISIONING_DEPROVISION_ON_DATE` → `PROVISION_MEMBERSHIP_ON_START_DATE`, `DEPROVISION_MEMBERSHIP_ON_END_DATE` | `FUTURE_USER_PROV`                                                                   |
| `SYNCHRONIZATION` → `SYNCHRONIZATION_RECORD` → the `PROVISIONING_*` children                                    | Any synchronization; the record event is set as the parent of the provisioning event |
| `BATCH_TASK_EXECUTE`                                                                                            | Every batch-task run                                                                 |

Each of those actions but one has an `org.openiam.audit.log.prune.days.<ACTION>` property shipping `-1`, so the history is complete until someone enables pruning; the one exception is named below. See [Audit actions and retention](/administration-guide/reference/audit-actions-and-retention.md).

Two gaps worth knowing about rather than hunting for. `USER_LEAVER` is a declared audit action with a retention property of its own, but **nothing ever writes it** — an audit search on it returns nothing on any deployment, so do not build a report on it. Use `TERMINATE_USERS_BY_LAST_DATE` and `AUTOMATIC_USER_TERMINATION` instead. And the retention property for the effective-dating task is spelled `org.openiam.audit.log.prune.days.PROVISIONING_DEPROVISING_ON_DATE` while the action constant is `PROVISIONING_DEPROVISION_ON_DATE` — the two do not match, so that action's retention is not actually configurable by that key. Both ship `-1`, so nothing is lost today.

`FUTURE_USER_PROV` has two halves: one for memberships whose **start** date falls in the window, one for memberships whose **end** date does. Each half looks at role, group, organization and resource memberships, collects the affected users, marks the matching memberships `ADD` or `REPLACE`, and performs a real provisioning save per affected user, once per window. So its effects are visible exactly where any other provisioning save is — the `PROVISIONING_*` audit entries and the identity's provisioning status — and not in any report of its own.

## When it goes wrong

| Symptom                                                         | Cause                                                                                                                                                         | Fix                                                                                                                                   |
| --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Future-dated hires are never activated                          | The user's status is not `PENDING_START_DATE`, so the shipped `ActivateUser` script does nothing. The task itself has **no** status filter — the script does. | Set the status in the transformation, or replace the script.                                                                          |
| Activation emails point at `example.openiam.com`                | `batch/ActivateProcess.groovy` hard-codes that `BASE_URL`                                                                                                     | Replace the script; `org.openiam.webconsole.defaultBaseDomain` ships empty and must be set.                                           |
| Leavers are disabled in the middle of the afternoon             | `LEAVER_PROCESS` cron is `0 0 17 * * ?` and ships **enabled**                                                                                                 | Change the cron, or accept it. Do not assume midnight.                                                                                |
| Effective-dated memberships never take effect                   | `FUTURE_USER_PROV` ships **disabled**; and `SUPPORTS_DATE_FOR_AFFILIATIONS` on the managed system suppresses immediate provisioning in favour of it           | Enable the task with a `PARAM1` minute window at least as wide as its interval, or untick the managed-system flag.                    |
| Expired access is never revoked                                 | `AUTO_REVOCATION` ships **disabled**, and `org.openiam.workflow.access.auto.expire` ships `false`                                                             | Enable both, after testing the reminder properties.                                                                                   |
| Access-renewal reminder links point at localhost                | `org.openiam.workflow.access.auto.expire.reminder.base.url` ships `http://localhost:8080`                                                                     | Set it.                                                                                                                               |
| A rule change applied itself overnight                          | `PERFORM_BR_REPROVISION` has been enabled on this system — it ships **disabled**                                                                              | Confirm the task's state before assuming either behaviour, and read the `user-recalculation` index.                                   |
| One contract extension moved every membership's end date        | `PROLONG_ALL_ACCESS`                                                                                                                                          | Extend the single membership.                                                                                                         |
| Restoring from leave made temporary grants permanent            | `RESTORE_ALL_ACCESS` nulls every end date                                                                                                                     | Restore named memberships from the pre-leave snapshot.                                                                                |
| Moving a user to a leave status revoked access as if terminated | `LEAVE`, `LEAVE_WITH_PAY` and `LEAVE_OF_ABSENCE` are treated identically to `TERMINATED`                                                                      | Expected. Design leave as a revocation with a documented restoration, or use a non-terminal status and drive the suspension yourself. |
| `INITIATE_TERMINATE_USER_WORKFLOW` starts with a null end date  | The request's end date comes only from `pUser.accessRightEndDate` on the transformed user                                                                     | Set it in the transformation.                                                                                                         |
| A rehire's `INITIATE_ADD_USER_AND_INVITE_WORKFLOW` fails        | Code `9` is only valid for a user who does not exist yet; the synchronization service log says `Operation 9 not supported for existing user`                  | Branch on `isNewUser`; use `6` for an existing person.                                                                                |
| A protected identity was changed anyway                         | One path did not check the marker                                                                                                                             | Re-inventory the ten paths listed in Pattern 5.                                                                                       |
| A missed batch window silently skipped people                   | The look-back or look-ahead `PARAM1` is narrower than the gap                                                                                                 | Widen it for one deliberate run; make the action idempotent first.                                                                    |
| An incremental sync advanced past records it did not process    | `LAST_REC_PROCESSED` is set from the newest record seen, not the newest successfully processed                                                                | Overlap the query window and make processing idempotent.                                                                              |

## Rollback

Stop only the affected schedule or definition — not every task — and preserve the run and target evidence before changing anything. Restore the prior authority matrix and the prior script versions, which is why customer-owned scripts must be in a versioned path rather than edited in place. Then replay one bounded identity through the authoritative source.

Reversing an OpenIAM status without reversing the target operations and the access memberships is not a rollback. The three things that have to move back together are: the person's status and dates, their direct and inherited memberships, and the accounts in every target the change reached.

## Related

* [Set up a synchronization](/administration-guide/lifecycle/synchronize-and-import-data.md) — the feed that carries the lifecycle event.
* [Grant birthright access](/administration-guide/lifecycle/birthright-access.md) — the business-rule actions and the nightly recalculation.
* [Configure provisioning](/administration-guide/lifecycle/configure-provisioning.md) — what a saved lifecycle change reaches, and the fail-open paths.
* [Create a batch task](/administration-guide/automation/batch-tasks.md) — creating, parameterising and scheduling the tasks above.
* [Shipped batch tasks](/administration-guide/reference/shipped-batch-tasks.md) — all fifteen named tasks, with cron and enabled state.
* [System properties](/administration-guide/reference/system-properties.md) — the leaver, activation and auto-expire keys.
* [Audit actions and retention](/administration-guide/reference/audit-actions-and-retention.md) — every action named above.

*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/advanced-lifecycle-patterns.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.
