Automatic reassignment - contract guarantor

Last modified by Jan Duda on 2026/09/25 12:22

@since 15.18.0

Information

This is one of the automatic guarantor reassignment features. The parent page
describes what this family of features does, the resolution chain they share and how this one differs from
automatic role guarantor reassignment and automatic approval task approver reassignment

Certain identities are required to always have guarantors on each of their contracts. This feature ensures that this is maintained. When the last active guarantor of a contract is deactivated or deleted, a new guarantor (or several guarantors) resolved by a configurable script is assigned to the contract. The deactivated guarantor is then removed as a guarantor of that contract, i.e. it WILL NOT automatically be a guarantor of that contract again if reactivated. If the contract has other inactive guarantors at that moment, they will stay untouched.

Error

This feature only works in the time of the event of the guarantor's deactivation or deletion. It will not reassign guarantors to contracts if their past guarantors were deactivated before the feature was deployed. To assign guarantors to those contracts, a schedulable task is provided.

Error

If you're only using direct managers, the processors implemented in this feature are enough to keep the guarded identities guarded. If you're using managers over organization structure, it is necessary to schedule the backfill task to run regularly since the guarantorship can then be lost by a contract expiration that doesn't deactivate the guarantor. The task is not scheduled by default.

Success

The reassignment runs asynchronously - the result is only visible once the event is processed. When deactivating a user with many subordinates, this may take some time.

Which contracts does this apply to

There is a configuration item idm.sec.core.contract-guarantee.guardedFormProjections that contains a comma-separated list of form projection codes (or identifiers) that determine which identities need their contracts to be guaranteed by someone. The feature then guards all contracts that belong to those identities except in the following cases:

  • the feature is switched off (idm.sec.core.contract-guarantee.enabled=false, the default),
  • the contract has already ended (validTill in the past). Nobody is responsible for a contract that is over, and an installation with a few years of history is full of them. Contract state does not disqualify it: an excluded or blocked contract still gets a guarantor, as long as it doesn't have an expired validTill.
  • the contract is controlled by contract slices. Assigning a guarantee that is not in the slice's guarantee set would be deleted on the next recalculation, so the contract is reported as blocked instead and the guarantor has to be added to the slice by hand.
  • the contract or its identity is being force deleted (flagged with the DELETED entity state).
Information

Not to be confused with the per-projection Set contract guarantee flag
(IdentityFormProjectionRoute#PARAMETER_SET_CONTRACT_GUARANTEE, read by IdentityContractAddGuaranteeByProjectionProcessor). That flag makes the logged in user the guarantor of a contract they create in the user interface - one moment, one actor, never during synchronization. This configuration says something else: that an identity of that kind must never be left without an active guarantor, for the whole lifetime of its contracts and on every path. Both combinations are legitimate - contracts arriving from an HR feed are guarded without ever being seeded, and a seeded guarantorship may well be meant to stay with its creator - so the flag is deliberately not read as an implicit member of this list. Reading it would also start reassigning guarantors on upgrade in every installation that has ticked it since 11.0.0 for its own sake.

How does the feature find managers and subordinates

The feature uses the configured managersFor filter set in the configuration item idm.sec.core.filter.IdmIdentity.managersFor.impl. For the correct functioning of this feature, as well as the correct functioning of IdM in general, it is necessary to make sure that the subordinatesFor (idm.sec.core.filter.IdmIdentityContract.subordinatesFor.impl) filter is the appropriate counterpart, i.e. if the managers filter ignores tree structures, the subordinates filter should ignore them as well and vice versa.

Error

These two configuration items must be configured as a matching pair. "Who manages this contract?" and "which contracts does this identity manage?" are the same question asked from both ends, but they are two independent configuration items, and this feature asks both: the first decides whether a contract still has an active guarantor, the second which contracts to look at when an identity leaves.

A mismatched pair makes the feature reach contracts the departing identity never guaranteed. For example with managers resolved from contract guarantees only (guaranteeManagersFilter) and subordinates left on the default, deactivating a manager collects every guarded contract under their tree node, finds no guarantee on any of them, and assigns fallback guarantors all over the department.

When does the reassignment happen

Reassignment is triggered when the guarantor identity is deleted or deactivated, either manually or due to contract expiration/deactivation/deletion. It does NOT happen when the IdmContractGuarantee object itself is deleted or changed - that can only happen during manual removal of the guarantor done deliberately by an administrator.

Information

During contract synchronization from a system, it can happen that all of an identity's contracts are deactivated but the identity itself stays active until the HR processes run. This is done to prevent high resource utilization during synchronization and to instead offload it to the time of the HR processes' scheduled run at night.

Asynchronous processing

The triggering processors are synchronous: they run when the guarantor's deletion or deactivation is about to be committed, and all they do is take a snapshot of everything the reassignment will need and publish a ContractGuaranteeFallbackEvent. The reassignment itself is done by IdentityGuaranteeFallbackResolveProcessor, which processes that event asynchronously - a guarantor of many guarded contracts would otherwise make the administrator wait for the whole reassignment. Because everything needed is carried by the event, it does not matter that the guarantor and its contracts are already gone by the time it runs.

Information

With asynchronous event processing turned off (idm.sec.core.event.asynchronous.enabled=false) the event manager executes the event synchronously instead, still before the triggering transaction is committed.

How are the new guarantors resolved

To resolve new guarantors, there is a system script under the configuration item idm.sec.core.contract-guarantee.fallbackScript. This script takes the following input arguments:

NameTypeMeaning
identityIdmIdentityDtothe guarded user whose contract lost its guarantor, always filled
guaranteeIdmIdentityDtothe departing guarantor, null when the script is run from the backfill task
contractIdmIdentityContractDtothe contract that is now orphaned, always filled
guaranteeContractsList<IdmIdentityContractDto>all contracts of the departing guarantor, never null, an empty list if run from the backfill task
guaranteeManagersList<IdmIdentityDto>all managers of the departing guarantor, never null, an empty list if run from the backfill task

Resolving guaranteeContracts and guaranteeManagers outside the script is necessary, since the script's execution is asynchronous - on guarantor deletion, it could run after the guarantor's identity no longer exists and its past managers can't be resolved. The field guaranteeManagers is only passed to the script on guarantor deletion, on guarantor deactivation the script will see them as empty lists and can resolve them itself - this allows a script that doesn't need those parameters to avoid the expensive querying. The field guaranteeContracts is much cheaper so it is filled always.

If the script returns an empty list - or no script is configured at all, which is the default - the guarantors are resolved further down a chain that always ends with somebody:

  1. the active holders of the role in idm.sec.core.contract-guarantee.fallbackGuarantorRole - and when that item is empty (the default) or names a role that does not exist, the admin role (idm.sec.core.role.admin, superAdminRole by default) is used in its place,
  2. if that role has no usable holder: the single admin identity.
Error

The shipped script returns nothing when there is no departing guarantor, and on the backfill path there never is one. So the whole backfill depends on fallbackGuarantorRole, which is not set by default. Without it every contract gets assigned to the admin role holders.

Product provided scripts

CodeFunctionParameters (in the script body)
contractGuaranteeFallbackTargetsFinds managers of the departing guarantor. Filters out those mentioned on the blacklist by their node or by their username

BLACKLIST_USERNAMES - list of usernames of the users that will never be assigned as guarantors

BLACKLIST_NODES - list of codes of the tree nodes whose users will never be assigned as guarantors - NOT recursive

contractGuaranteeFallbackByUsername (demo)Finds guarantees by a list of usernames

USERNAMES - list of names of the users to be assigned as guarantors

BLACKLIST_NODES - list of codes of the tree nodes whose users will never be assigned as guarantors - NOT recursive

contractGuaranteeFallbackByRole (demo)Finds guarantees by a list of roles

ROLE_CODES - list of role codes the users of which will be assigned as guarantors

BLACKLIST_USERNAMES - list of usernames of the users that will never be assigned as guarantors

BLACKLIST_NODES - list of codes of the tree nodes whose users will never be assigned as guarantors - NOT recursive

The parameters live in the body of the script, not in the global configuration - so a copy of the script is configured entirely by editing its own body.

The shipped scripts are templates: they are shipped with their lists empty, and redeploying a script from disk - the Redeploy action, or a product upgrade - restores the body shipped with the product and throws away everything you edited in it, the BLACKLIST_USERNAMES and BLACKLIST_NODES parameters included. For real use, duplicate the script, save the copy under a different code, edit the copy, and point idm.sec.core.contract-guarantee.fallbackScript at it.

Backfill task

To assign guarantors to guarded contracts that have been left without a guarantor in the past or that have never been assigned a guarantor yet, there is a long running task "Assign fallback guarantor to guarded contracts" (core-contract-guarantee-fallback-long-running-task). This task checks all contracts of all guarded identities and uses the configured script to assign them guarantors. Note that during this operation, there is no departing guarantor, so the fields guaranteeContracts and guaranteeManagers will both be empty and the field guarantee will be null.

Trying it out first

The task supports the scheduler's standard dry run. It assigns nothing and lists exactly the contracts a real run would repair, each as a NOT_EXECUTED item - so the size of the problem is visible before anything is changed.

Configuration

The feature is off by default and nothing it needs has a usable default value, so it takes a few deliberate steps before it does anything. This is the whole procedure for the common case - the feature switched on, with the shipped manager script doing the resolving.

Step by step



      1. Mark the identities whose contracts are guarded.

Put the codes (or identifiers) of their form projections in idm.sec.core.contract-guarantee.guardedFormProjections, comma separated. No projection means nothing is guarded, so nothing happens.

2. Make your own copy of the script.

Go to Settings -> Scripts, open contractGuaranteeFallbackTargets, and save it under a code of your own (e.g. myContractGuaranteeFallbackTargets).

Do not skip this and configure the shipped script directly. The scripts shipped with the product are templates: redeploying a script from disk - the Redeploy action, or a product upgrade - restores the body shipped with the product and silently throws away everything you edited in it, your blacklists included.

3. Fill in the blacklists in your copy.

At the top of the body there are two lists:

List BLACKLIST_USERNAMES = [];
List BLACKLIST_NODES = [];

The script resolves the managers of the departing guarantor, and whoever it returns becomes a guarantor of the guarded contract. That is not always wanted: technical and service accounts, a shared "HR" identity, the managers of a department that must not take externists over. Name them in BLACKLIST_USERNAMES (usernames), or name whole organisation units in BLACKLIST_NODES (tree node codes) to exclude everybody holding a contract there.

The node blacklist is not recursive - the main usecase is preventing the guarantorship propagating too high through the company structure, so blacklisting a node only blacklists the node itself, not the nodes below it, so list every node that has to be blocked.

4. Point the feature at your copy.

idm.sec.core.contract-guarantee.fallbackScript=myContractGuaranteeFallbackTargets

5. Configure the fallback guarantor role.

idm.sec.core.contract-guarantee.fallbackGuarantorRole names the role whose active holders take the contract over when the script resolves nobody - everybody it found was inactive or blacklisted, or the departing guarantor had no manager at all. Optional, but see the warning in step 6: leave it empty and the admin role is used in its place, with the admin identity behind it if nobody holds that either.

6. Switch the feature on.

idm.sec.core.contract-guarantee.enabled=true

From this moment the guarded contracts are kept guarded - but only from this moment. Contracts that lost their guarantor earlier are repaired by the backfill task, Assign fallback guarantor to guarded contracts, which you run by hand from Settings -> Scheduled tasks. Use Dry run first: it changes nothing and lists exactly the contracts a real run would repair.

The shipped script resolves the managers of the departing guarantor - and on the backfill path there is no departing guarantor, so the script returns nothing for every contract. The whole repair therefore lands on the next level of the chain: the role from step 5, or - if you skipped it - the admin role, and if nobody holds that, the admin identity.

In other words, if you're using the shipped script, running the backfill task without first configuring fallbackGuarantorRole hands your superadmins the guarantorship of every orphaned guarded contract. Configure the role first, then dry run, then run.

7. Schedule the backfill task, if you need it as a safety net.

The product does not schedule it. Schedule it yourself at the end of your chain of HR processes, whose deactivations are what leaves a guarded contract unguarded.

This is necessary when your managers come from the organisation structure: the guarantorship can then be lost by a change of the manager's own contract - it ends, or moves to another node - the manager identity doesn't change so the processors of this feature don't notice anything.

Configuration values

#the projections that identify the identities that are to be guarded by this feature
idm.sec.core.contract-guarantee.guardedFormProjections=identity-guarded,identity-guarded2
#the code of the role by which the new guarantors are resolved if the script returns an empty list
idm.sec.core.contract-guarantee.fallbackGuarantorRole=
#the code of the script that resolves new guarantors
idm.sec.core.contract-guarantee.fallbackScript=
#on-off switch for this feature
idm.sec.core.contract-guarantee.enabled=false
#filter to find managers for a subordinate
idm.sec.core.filter.IdmIdentity.managersFor.impl=
#filter to find subordinates for a manager
idm.sec.core.filter.IdmIdentityContract.subordinatesFor.impl=

All four contract-guarantee properties are exposed on the contract-guarantee configuration agenda.

Notification

There is a notification sent to the users that have been assigned new guarantors to a guarded contract. The new
notification configuration created by the init processor is as follows:

ItemValue
Topiccore:contractGuaranteeFallbackAssigned (CoreModule#TOPIC_CONTRACT_GUARANTEE_FALLBACK_ASSIGNED)
TemplatecontractGuaranteeFallbackAssigned (IdmCoreContractGuaranteeFallbackAssigned.xml)
RecipientEach newly assigned guarantor (one per message)
Parametersidentity (the recipient), guardedIdentities, contracts, originalGuarantee, reason

The reason parameter is one of IDENTITY_DELETED, IDENTITY_DISABLED or BACKFILL.

The notification is sent as one notification per batch, i.e. if multiple guarantors are deactivated using a bulk action or an HR process and some of the reassigned contracts have the same fallback guarantor, the fallback guarantor will only receive one notification for that batch with a list of contracts assigned to them. Similarly if one guarantor of multiple contracts is deactivated, the fallback guarantor will only receive one notification listing all the assigned contracts.

Information

When one batch hands contracts of several different departing guarantors to the same recipient, the originalGuarantee and reason parameters are left empty and the template hides those parts - one message cannot name one predecessor.

Implementation notes

ClassUsage
ContractGuaranteeConfigurationResolves script, guarded projections and fallback role from configuration
DefaultContractGuaranteeConfigurationReads the properties, resolves projection and role codes or identifiers through LookupService
ContractGuaranteeTransferManagerContains the logic of resolving guarantors, subordinates, contracts...
ContractGuaranteeFallbackEventCarries the snapshot of script arguments, needed to preserve consistency when reassigning asynchronously on identity deletion
IdentityGuaranteeFallbackDeleteProcessor / IdentityGuaranteeFallbackDisableProcessorCatch identity delete and identity disable events, emit the event for IdentityGuaranteeFallbackResolveProcessor, synchronous
IdentityGuaranteeFallbackResolveProcessorAsynchronous processor, does the reassignment itself
ContractGuaranteeFallbackTaskExecutorThe backfill long running task (core-contract-guarantee-fallback-long-running-task)
IdmIdentityFilter#setManagersFor / IdmIdentityContractFilter#setSubordinatesForThe two configurable filters the feature asks from both ends - see How does the feature find managers and subordinates
ContractGuaranteeFallbackNotifyListenerSends the aggregated notification once the transaction of the ending long running task commits
IdmIdentityContractService#findByIdentityFormProjectionCandidate query of the backfill task
contractGuaranteeFallbackTargets / …ByUsername / …ByRoleShipped scripts
IdmCoreContractGuaranteeFallbackAssignedNotification template