Skip to content

Pending Export Lifecycle

Last updated: 2026-09-29, JIM v0.16.0

This diagram shows the full lifecycle of a Pending Export from creation during synchronisation, through export execution, to confirmation during a confirming import. Pending Exports are the mechanism by which JIM propagates changes from the metaverse to target Connected Systems.

State Diagram

stateDiagram-v2
    [*] --> Pending: Created during Sync<br/>(export evaluation)

    Pending --> [*]: Never-exported provisioning<br/>cancelled with its CSO,<br/>or every queued change withdrawn<br/>(target already holds the value,<br/>or nothing authorises it any more)

    Pending --> Executing: Export run starts<br/>batch marked executing

    Executing --> Exported: Connector reports<br/>success, or worker start<br/>recovery when a change<br/>was already sent

    Executing --> Pending: Connector reports failure<br/>(retry after NextRetryAt backoff),<br/>or written in part while<br/>references are still owed,<br/>or worker start recovery<br/>when nothing was sent
    Executing --> ExportNotConfirmed: File-based export<br/>throws (retryable)
    Executing --> Failed: ErrorCount >= MaxRetries

    Executing --> [*]: Delete succeeds for a CSO whose<br/>provisioning was never confirmed,<br/>or an auto-confirming connector<br/>(PE deleted)

    Exported --> [*]: Confirming import confirms<br/>all attribute values match<br/>(PE deleted)

    Exported --> ExportNotConfirmed: Confirming import finds<br/>attribute values don't match,<br/>or a Full Import never returned<br/>an exported Create

    Exported --> Pending: Confirmed, but changes appended<br/>while a Create awaited confirmation<br/>remain (now an Update)

    ExportNotConfirmed --> Executing: Next export run<br/>(after NextRetryAt backoff)

    ExportNotConfirmed --> Pending: Sync re-evaluates<br/>and reasserts changes

    ExportNotConfirmed --> Failed: ErrorCount >= MaxRetries<br/>(permanent failure)

    ExportNotConfirmed --> [*]: Every queued change withdrawn<br/>(as from Pending)

    Failed --> [*]: Confirming import finds every<br/>change now on the CSO,<br/>or PE deleted manually

    note right of Pending
        Initial state.
        Created by EvaluateExportRules
        during Full/Delta Sync.
        Also the retry state after a
        connector-reported failure.
    end note
    note right of Exported
        Awaiting confirmation.
        Confirming import checks if
        CSO attributes match expected values.
        An exported Create is never
        re-sent while it waits.
    end note
    note left of ExportNotConfirmed
        Retryable failure.
        Will be re-exported after
        exponential backoff delay.
    end note
    note left of Failed
        Permanent failure.
        Requires manual intervention.
        RPEI: ExportConfirmationFailed
    end note

A change type withheld by a Run Profile export limit (#1629) never leaves Pending: the export run does not mark it, attempt it, or record anything against it.

Full Lifecycle Across Operations

A Pending Export's journey typically spans three separate Run Profile executions:

flowchart LR
    subgraph "1. Sync (Full or Delta)"
        SyncStart[MVO attribute changes<br/>during inbound flow<br/>incl. attribute recall +<br/>#91 next-contributor re-election] --> CheckDelete{MVO queued for<br/>immediate deletion?}
        CheckDelete -->|Yes| SkipDelete[Skip export evaluation<br/>MVO about to be deleted;<br/>work would be discarded #390<br/>No PE created]
        CheckDelete -->|No| EvalExport[EvaluateExportRules:<br/>Find export Synchronisation Rules<br/>for MVO type]
        EvalExport --> InScope{MVO in scope<br/>for export rule?}
        InScope -->|No| EvalDeprov[Evaluate deprovisioning:<br/>Create Delete PE if CSO exists<br/>Cancel never-exported provisioning<br/>instead, #1681]
        InScope -->|Yes| MapAttrs[Map MVO attributes<br/>to CSO attributes<br/>via export Synchronisation Rule mappings]
        MapAttrs --> NetChange{No-net-change<br/>detection}
        NetChange -->|CSO already current| Skip[Skip - no PE created<br/>Target already has correct values<br/>Withdraw any change still queued<br/>for it; empty Update PE deleted<br/>Withdrawal recorded on the item]
        NetChange -->|Changes needed| CheckExisting{Existing CSO<br/>in target system?}
        CheckExisting -->|Yes, of another<br/>Object Type| TypeConflict[No PE: the MVO's one CSO<br/>in this system is of another<br/>Object Type, #1344<br/>RPEI: CouldNotExportDueTo<br/>ExistingConnectedSystemObject]
        CheckExisting -->|Yes| CreateUpdatePE[Create PE:<br/>ChangeType = Update<br/>Status = Pending]
        CheckExisting -->|Yes, PendingProvisioning,<br/>Create not yet sent| RestageCreate[Restage the Create<br/>from the latest MVO state]
        CheckExisting -->|Yes, PendingProvisioning,<br/>Create already sent| AppendChanges[Append the changes to the<br/>exported Create as Pending, #1687<br/>they travel as one Update<br/>once the Create is confirmed]
        CheckExisting -->|No| CreateCreatePE[Create PE:<br/>ChangeType = Create<br/>Status = Pending<br/>Provision new CSO]
        CreateUpdatePE --> FlushReconcile
        CreateCreatePE --> FlushReconcile
        RestageCreate --> FlushReconcile
        AppendChanges --> FlushReconcile
        EvalDeprov --> FlushReconcile
        FlushReconcile[Flush-time reconciliation:<br/>CREATE+DELETE for same CSO<br/>cancels both no net change<br/>UPDATE+DELETE cancels UPDATE<br/>keeps DELETE]
        FlushReconcile --> PersistPE[Persist remaining PEs]
    end

    subgraph "2. Export"
        Withdraw[Withdraw queued changes nothing<br/>authorises any more: rule deleted<br/>or disabled, no enabled mapping,<br/>or CSO no longer joined<br/>Empty Update PE deleted<br/>not in preview] --> GetExecutable
        GetExecutable[Get executable PEs:<br/>Status = Pending or<br/>ExportNotConfirmed<br/>NextRetryAt <= now<br/>exported Creates skipped<br/>types over a Run Profile limit withheld] --> MarkExec[Mark batch:<br/>Status = Executing]
        MarkExec --> ConnExport[Connector executes<br/>export operations]
        ConnExport --> Success{Success?}
        Success -->|Yes, Create| ProvResult[Status = Exported<br/>Capture new external ID<br/>RPEI: Exported]
        Success -->|Yes, Update| ExpResult[Status = Exported<br/>RPEI: Exported]
        Success -->|Yes, in part| PartResult[References still owed, #1398<br/>Status = Pending<br/>Create becomes Update<br/>RPEI: Exported]
        Success -->|Yes, Delete| DeprovResult[Status = Exported<br/>RPEI: Deprovisioned<br/>Never-confirmed provisioning,<br/>or auto-confirmed (#1936):<br/>delete PE + CSO now, #1685]
        Success -->|No| FailResult[Increment ErrorCount<br/>Set NextRetryAt<br/>Status = Pending,<br/>or Failed at MaxRetries]
        ProvResult --> OptimisticApply
        ExpResult --> OptimisticApply[Optimistic apply #1079:<br/>Project exported attribute<br/>changes onto the CSO's<br/>in-memory + persisted values<br/>every export path, #1936;<br/>never stamps LastUpdated/Status]
        PartResult --> OptimisticApply
    end

    subgraph "3. Confirming Import"
        ImportData[Import fresh data<br/>from target system] --> Reconcile[Reconcile each PE for a CSO<br/>the import returned: compare<br/>its attribute changes against<br/>imported CSO values<br/>Executing PEs skipped; a Failed PE<br/>is deleted only when every change<br/>is now on the CSO, else untouched]
        Reconcile --> AllMatch{Any changes<br/>remain?}
        AllMatch -->|No| DeletePE[Delete PE<br/>Export confirmed<br/>PE lifecycle complete]
        AllMatch -->|Yes| Remaining[Remove confirmed changes<br/>A Create becomes an Update:<br/>the object is proven to exist, #1695<br/>Status from what remains:<br/>unconfirmed = ExportNotConfirmed<br/>appended only = Pending<br/>all failed = Failed]
        ImportData --> Unseen{Full Import did not return<br/>an exported Create at all?}
        Unseen -->|Yes| RetryCreate[Mark the Create for retry, #1695<br/>Status = ExportNotConfirmed,<br/>or Failed at MaxRetries<br/>RPEI: ExportNotConfirmed<br/>Delta Imports cannot prove absence]
    end

    PersistPE --> Withdraw
    OptimisticApply --> ImportData
    FailResult -.->|Next export run<br/>after backoff| Withdraw
    Remaining -.->|Next export run| Withdraw
    RetryCreate -.->|Next export run| Withdraw

Confirmation Happens on Import Only

Pending Exports are confirmed only by the confirming import path shown in "3. Confirming Import" above (ISyncEngine.ReconcileCsoAgainstPendingExport, driven by SyncImportTaskProcessor.ReconcilePendingExportsAsync). Synchronisation does not re-check them: every change to a CSO's values arrives through an import, so the import has always seen it first.

  • Failed Pending Exports need manual intervention, and reconciliation leaves them alone (no status, attribute, ErrorCount or attempt change) unless every change they assert is now visible on the CSO, for example because an administrator fixed the target by hand. They are then deleted, like a fully confirmed Exported one.
  • Parked and Executing Pending Exports are never touched by reconciliation. (Parked belongs to Unique Value Generation.)
  • Executing Pending Exports left behind by a worker crash or restart are recovered when the worker starts: to Exported if any change was already sent, so the next confirming import reconciles it, otherwise to Pending, so the next export retries it.

Attribute-Level Status Tracking

Each attribute change within a Pending Export has its own status, enabling partial confirmation:

stateDiagram-v2
    [*] --> Pending: Attribute change created

    Pending --> [*]: Withdrawn before export<br/>(target already holds it,<br/>or nothing authorises it)

    Pending --> ExportedPendingConfirmation: Export run executes<br/>successfully

    ExportedPendingConfirmation --> [*]: Confirming import<br/>confirms value matches<br/>(attribute change removed from PE)

    ExportedPendingConfirmation --> ExportedNotConfirmed: Confirming import<br/>finds value mismatch

    ExportedNotConfirmed --> Pending: Reasserted on<br/>next export run

    ExportedNotConfirmed --> Failed: Max retries exceeded

    ExportedNotConfirmed --> [*]: Withdrawn before re-export<br/>(as from Pending)

    Failed --> [*]: Manual intervention

Change Types

Type When Created What Happens
Create No CSO exists in target system for this MVO Provisions new object in target system. Connector creates object + sets attributes. PE captures DN template + all attributes. Once exported it is never re-sent while awaiting confirmation; changes arriving meanwhile are appended to it, and it becomes an Update once an import confirms the object exists.
Update CSO exists but attributes differ from MVO values Updates existing object attributes. Only changed attributes are included. No-net-change detection avoids unnecessary exports.
Delete MVO deletion rule triggered, or MVO falls out of export scope Removes object from target system. Created by EvaluateMvoDeletionAsync or EvaluateOutOfScopeExportsAsync, and reported as a DeprovisionQueued outcome on the execution item of the object that was deleted or left scope, which is also recorded as the Delete's queueing item (#1223, #1925). On success the PE goes to Exported and the confirming import removes the PE and CSO; for a CSO whose provisioning was never confirmed, the PE and CSO are removed at once. Never staged for provisioning that was never exported: that is cancelled instead.

Drift Detection Creates Corrective Exports

During sync, drift detection can also create Pending Exports:

flowchart TD
    DriftCheck[EvaluateDriftAndEnforceState<br/>during sync CSO processing] --> CompareCSO[Compare CSO attribute values<br/>against expected MVO values<br/>using EnforceState export rules]
    CompareCSO --> Drifted{CSO value<br/>differs from<br/>expected?}
    Drifted -->|No| NoDrift([No action])
    Drifted -->|Yes| CheckContributor{Is this system<br/>a legitimate contributor:<br/>its winning import flow<br/>reads the diverged<br/>attribute? #1864}
    CheckContributor -->|Yes| LegitChange([Skip - legitimate import<br/>from authoritative source])
    CheckContributor -->|No| CreateCorrective[Create corrective PE:<br/>ChangeType = Update<br/>Status = Pending<br/>RPEI: DriftCorrection]

Drift Correction and Export Evaluation Merge

When both drift corrections and export evaluation produce changes for the same Pending Export, they are merged at the value level using composite keys:

flowchart TD
    DriftChanges[Drift corrections<br/>e.g., 117 member removals] --> MergeKey[Merge key =<br/>AttributeId + value identity<br/>e.g., member:user1, member:user2]
    ExportChanges[Export evaluation changes<br/>e.g., 1 member addition] --> MergeKey
    MergeKey --> Deduplicate[Union with value-level dedup<br/>All contributions preserved]
    Deduplicate --> MergedPE[Merged PE contains<br/>all 117 removals + 1 addition]

This prevents silent loss of drift corrections when merging with export evaluation changes. Previously, merging by AttributeId alone would keep only one side's changes for multi-valued attributes.

Key Design Decisions

  • Three-operation lifecycle
    A Pending Export typically spans Sync (creation), Export (execution), and Confirming Import (confirmation). This design ensures changes are verified end-to-end.

  • Partial confirmation
    Individual attribute changes can be confirmed independently. If 3 out of 5 attributes match the target system, only the 2 unconfirmed attributes remain on the Pending Export for retry.

  • Create-to-Update demotion (#1695)
    Reconciliation only runs for a CSO an import actually returned, so a Create PE reaching it proves the object exists. Whatever remains on it once confirmed changes are removed (unconfirmed attributes, or changes appended while it awaited confirmation) therefore travels as an Update, whichever attributes confirmed. This replaced two narrower triggers (a confirmed secondary external ID, or every original change confirmed with more queued), under which a single unconfirmed attribute left the PE shaped as a Create and the retry sent a second Create for an existing object. A Create written in part for want of a reference (#1398) is demoted at export time instead, since the row now exists.

  • An exported Create waits; later changes are appended (#1687)
    A CSO stays PendingProvisioning until an import confirms its Create. A Metaverse Object change arriving in that window used to delete the exported Create and stage a fresh one carrying every change, so a second Create reached the connector. Staging now distinguishes a Create never sent (IsProvisioningNeverExported: restage it in place) from one already sent: the change is appended to the exported Create as a Pending attribute change, the export run never re-sends a Create at Exported, and the confirming import turns it into one Update carrying the change. An auto-confirming file export, which deletes the Create on success, stages the Update directly.

  • An exported Create a Full Import never saw is retried (#1695)
    Import deletion detection deliberately excludes PendingProvisioning CSOs, so a Create that was exported but never reported back by any import used to sit at Exported for ever. A Full Import now finds each such Create for the Object Types it read, marks its exported changes for retry (ExportNotConfirmed, or Failed once past the retry limit) and reports each on the import Activity. A Delta Import leaves them alone, since it cannot prove an object is absent.

  • One CSO per Metaverse Object per Connected System (#1344)
    Export evaluation resolves the target CSO by Metaverse Object and Connected System, so an export Synchronisation Rule targeting a second Object Type in the same system would resolve to the object already holding that slot. DetectObjectTypeConflict catches this where the decision is made: nothing is staged, and a CouldNotExportDueToExistingConnectedSystemObject RPEI names the rule, both Object Types and the object holding the slot, rather than the page failing on the one-PE-per-CSO unique index.

  • The cross-page reference pass leaves Pending Exports in place (#1741)
    A Full Synchronisation re-evaluates objects whose references span pages once every page is done. That pass used to batch-delete the targets' Pending Exports first, which (since #1687) made a group whose Create was never sent read as already sent, so only its members were staged as an Update for a group that did not exist. It now lets per-object staging find each existing PE and decide: an unsent Create is rebuilt with the resolved references, an exported one has the changes appended, a pending Update is merged.

  • No-net-change detection
    Before creating a PE during sync, the system checks if the target CSO already has the expected values (using pre-cached data in ExportEvaluationCache). This avoids unnecessary export operations and reduces connector load. An attribute found already current also withdraws any change still queued for it (or, for a multi-valued attribute, for that value) on the CSO's existing PE, in the run's in-memory batch or persisted by an earlier run: the Metaverse and the target already agree, so the queued change is stale (a value changed and changed back before an export, or a clear queued before another source supplied the value the target holds). It is the #1199 supersede rule applied when the answer is "no change" (SyncEngine.WithdrawChangesAlreadyCurrent); a PE left empty is deleted, a change already sent and awaiting confirmation is kept, and a PE a connector is executing is never touched. The page's ExportEvaluationCache.CsoIdsWithPersistedPendingExports limits the persisted lookup to objects that have one. Each withdrawal is reported (#2001): export evaluation returns it in ExportEvaluationResult.Withdrawals (a change replaced by a newly staged one is not counted), and the worker records a PendingExportChangesWithdrawn outcome per target CSO on the object's execution item, nested where its staged exports go and carrying a snapshot of the withdrawn values. It queues nothing, so it is not counted as a Pending Export. Sync Preview and the Full Synchronisation preview read the queued PE read-only and propose the same outcome. The recall executors (Synchronisation Rule deletion, Synchronised Deprovisioning and the stranded-value sweep) withdraw the same way and record the same outcome at root level on the recalled object's item (#2011), and the Connected System deletion preview proposes it.

  • Drift correction
    When EnforceState is enabled on an export Synchronisation Rule and the CSO has values that don't match the MVO, a corrective PE is created to reassert the correct values. This detects and corrects unauthorised changes made directly in target systems.

  • Never-exported provisioning is cancelled, not deprovisioned
    When a Metaverse Object is deleted or leaves an export Synchronisation Rule's scope while its target CSO is still PendingProvisioning with an unsent Create (Status = Pending, never attempted), the object does not exist in the target system. SyncEngine.IsProvisioningNeverExported is the verdict; ExportEvaluationServer removes the Create PE and the CSO together and stages nothing, whatever the rule's OutboundDeprovisionAction. A Delete PE here would carry no identifier (the File connector rejects it with "Delete export has no External ID value"), and the CSO, holding no external ID value, is excluded from import deletion detection, so nothing could ever confirm it away. Once a Create has been handed to a Connector (any other status, or a Pending one with an attempt recorded) the ordinary deprovisioning decision applies. The cancellation is reported as a ProvisioningCancelled sync outcome (nested under the MvoDeleted outcome for a deletion, or as a root outcome on the execution item of an object leaving scope), so it is visible on the Activity, in the causality tree and Table view, and does not count towards Pending Export totals. Sync Preview mirrors the verdict and reports it as its own ProvisioningCancelled node in the outcome tree.

  • Attribute recall and re-election
    When a Connected System Object disconnects (obsoleted, or fallen out of scope), JIM recalls the values that system contributed. If a lower-priority Synchronisation Rule still contributes, the next contributor is re-elected in the same sync run (Attribute Priority, #91) and export evaluation stages a change-of-value on the target; if no contributor survives, the attribute is cleared and export evaluation stages a null-clear. Export evaluation is no longer skipped for recall. Expression-based mappings (e.g. DN templates) are protected from evaluating against post-recall nulls by re-election supplying a replacement value, by grace-period freezing of identity-critical single-source attributes, and by the #390 skip of recall when the MVO is about to be deleted.

  • Value-level drift merge
    When merging drift corrections with export evaluation changes, the merge key is a composite of AttributeId + value identity (not just AttributeId). This prevents silent loss of multi-valued attribute drift corrections; e.g., 117 member removals would be dropped if merged by AttributeId alone.

  • Flush-time CREATE→DELETE reconciliation (#218)
    At flush time (during sync), deferred CREATE/UPDATE PEs still held in memory are checked against DELETE PEs already persisted for the same CSO earlier in the page: CREATE+DELETE pairs cancel both (no net change), UPDATE+DELETE cancels the UPDATE (deletion still needed). The pairing decision is ISyncEngine.ReconcileDeferredExportsAgainstPersistedDeletes; the worker applies it. There is deliberately no export-time counterpart. One existed, scanning every executable PE for such pairs before each export, but IX_PendingExports_ConnectedSystemObjectId_Unique allows one PE per CSO, so two persisted PEs for the same CSO can never exist and it could never fire. Contradictions across sync runs are resolved where the second PE is staged instead: the one-PE-per-CSO collision policy replaces or reuses, and never-exported provisioning is cancelled outright (next note).

  • Queued changes are withdrawn once nothing authorises them
    Before each export (not a preview), a queued change on an Update PE is withdrawn when the export rule that queued it is deleted or disabled, when that rule no longer has an enabled mapping for the attribute, or when the CSO is no longer joined (scope exit with Disconnect). The same check runs as each export-relevant configuration change is saved (rule or mapping disabled, removed or deleted, and a schema refresh disabling either), so the queue is accurate at once; the export-time run is the backstop, and the only one that sees a join broken by scope exit. ISyncEngine.SelectQueuedChangesWithoutAuthority makes the decision; an Update PE left empty is deleted. Changes already sent (ExportedPendingConfirmation), unattributed changes, Creates, Deletes and Executing PEs are left alone.

  • A Disconnect withdraws a Delete queued under an earlier Delete action (#1970)
    The export-time withdrawal above leaves Deletes alone, so a Delete queued while an export rule said Delete would otherwise still be exported after the rule is switched to Disconnect, removing an account the run reported keeping. Instead, when an object leaves scope under Disconnect, ISyncEngine.DecideOutOfScopeDeprovisioning reads the CSO's existing PE: an unsent Delete (Pending, ExportNotConfirmed, Failed or Parked) is deleted as the join is broken; a Delete already sent (Executing or Exported) is left to finish and the object is not disconnected (DeleteAlreadySent), since the confirming import completes the deletion. A Delete staged earlier in the same run by another out-of-scope rule stands (Delete beats Disconnect, as for #655). Changing an export rule's Deprovisioning Action flags the rule's Metaverse Object type for export scope review (SyncRuleScopeState.DeprovisioningActionChangedBy), which is what brings objects already out of scope back through this decision. The Deprovisioning Action change preview asks the engine the same question per object.

  • Exponential backoff
    Failed exports use increasing retry delays (NextRetryAt) to avoid hammering a target system that's experiencing issues.

  • Optimistic export apply (#1079)
    On export success, ExportExecutionServer.ProcessBatchSuccessAsync projects the exported attribute changes onto the CSO's attribute values (add-if-absent, replace-on-Update, delete-if-present, delete-all) at export time rather than waiting for the confirming import to re-materialise them. This means the confirming import's "Compare each import attribute against existing CSO values" step (see Full Import Flow) usually finds nothing to do, avoiding millions of redundant attribute value inserts at scale. Two rules keep this safe:

  • The parent CSO row (Status, LastUpdated, and every other field) is never touched. LastUpdated staying untouched is what lets a no-op confirming import skip the object entirely under the Full Synchronisation unchanged-object watermark; a status transition (e.g. PendingProvisioning → Normal) is still a genuine change and is stamped by the confirming import as before.
  • An Update with no value (clearing a single-valued attribute) removes the recorded values, as a RemoveAll does. Reconciliation confirms an empty Update when the attribute holds nothing, so this agrees with what the confirming import will find.

Every export path applies, the file path included (#1936). On the file path it is not only an optimisation: an auto-confirming export (the File Connector auto-confirms in every mode) deletes its Pending Export on success, and a target in Export Only mode is never imported from, so recording what was written is the only way JIM learns what the target holds. Before #1936 the file path was excluded, the CSO held only its External ID for good, and a value later cleared in the Metaverse was never cleared in the file. The file path's loader already includes each CSO's attribute values, because the File Connector writes full rows from them.

A failure during optimistic apply is caught, logged as a Warning, and never fails the batch or the Activity. Where a confirming import follows, it self-heals by re-materialising the values. Where the export auto-confirms, nothing would, so those Pending Exports are not deleted: they are set to ExportNotConfirmed (their changes to ExportedNotConfirmed), the next export run sends them again and retries, and the Activity carries a warning (ExportExecutionResult.UnrecordedExportCount).

  • An auto-confirmed Delete removes its CSO (#1936)
    A Delete that auto-confirms removes the CSO and its Pending Export at once, whatever the CSO's status, through the same step as a never-confirmed provisioning's Delete (#1685): the export is the only confirmation the object will get. Before #1936 only a PendingProvisioning CSO was removed, so one an import had once confirmed (a system run Bidirectional before Export Only) outlived its row in the file.

Reference-typed attribute changes (for example a group's member list) resolve their Connected System Object Id purely from PendingExportAttributeValueChange.ResolvedReferenceCsoId, a persisted column stamped once at the point of resolution (ExportExecutionServer.TryResolveReferencesFromLookup, SPEC-1079B). An earlier version of optimistic apply kept this as an in-memory-only hint and fell back to a database lookup whenever a change crossed a persistence boundary without it (cross-run retry, or the parallel batch path's per-batch fresh-context reload); that fallback queried the whole Connected System once per export run, and an even earlier per-batch version of the same fallback cost 10-15 seconds per call over an unindexed scan at Scale500k25kGroups (2026-07-21). Persisting the column removes the loss the fallback was compensating for, so no database lookup is needed here at all: a Pending Export whose Reference change has never been resolved simply applies unresolved (matched on UnresolvedReferenceValue, self-healing on the next import) rather than triggering a lookup. No foreign key or index backs the column - see its XML doc for the trade-off.

Optimistic apply means the confirming import's update path still hydrates every matched CSO's full attribute-value graph to run the diff, even though the diff usually confirms there's nothing to change. At Scale500k25kGroups this held ~9.8M attribute value objects in memory simultaneously across a 524,997-CSO run, tripling the confirming import's live heap versus the pre-#1079 baseline and causing an OOM kill. SyncImportTaskProcessor.PerformImportAsync now releases each update-path CSO's hydrated attribute values once its batch has been persisted (ReleaseHydratedAttributeValues), well after the diff, the change-history write, and the CSO cache refresh have all read what they needed - reconciliation, which runs afterwards, is unaffected because it reloads each CSO's attribute values fresh from the database per page rather than reading the released in-memory copies.

OptimisticExportApplyCalculator.ApplyAdd/ApplyRemove originally called SyncEngine.ValueExistsOnCso/a linear scan once per attribute change, while every accepted Add appended to that same growing per-attribute list - O(M^2) for a Pending Export with M Add changes. The live database has groups with up to 495,008 members, and a full-scale run measured 255 slow OptimisticApply instances totalling 77.5 minutes, all in the group-batch wave. The calculator now builds a per-attribute index once per Pending Export (a Dictionary of value to matching row instances, keyed exactly like ValueExistsOnCso's per-type comparisons - DateTime on Ticks, Reference on the CSO row's UnresolvedReferenceValue, Binary left as a linear SequenceEqual scan since byte arrays do not hash usefully and are never large multi-valued sets) and maintains it incrementally as changes are applied, turning the existence/match check into an O(1) average-case lookup.