---
id: WF-PFM-SEC-010
title: Reconcile derived role assignments
trigger: Portal sign-in, explicit eligibility reconciliation or the nightly schedule asks Security to refresh
  derived access.
product_area: platform
module: security
revision: 1
status: draft
design_intent: proposed
implementation_evidence: partially-observed
inference: []
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
implements:
- CAP-PFM-SEC-014
moves:
- STATE-PFM-SEC-006
crosses:
- SVC-PFM-SEC-security
idempotency: Source synchronization uses binding/contact or appointment/rule derivation keys and unique live
  rows. An unchanged desired set converges. Nightly submission uses workspace and UTC day to deduplicate repeated
  scheduling; redelivered queue work reconciles again.
concurrency: Derivation-key locks serialize synchronization for the same subject. Eligibility evaluation runs
  outside the write transaction; the desired set commits together afterward. Nightly scans cover contacts already
  holding derived rows, rather than discovering every newly eligible contact.
hops:
- n: 1
  actor_or_service: Trigger (sign-in, reconcile route, nightly scheduler)
  action: On sign-in refresh the linked contact; explicit reconcile queues subjects; nightly scheduling creates
    workspace jobs for existing derived subjects.
  anchor: services-platform:modules/security/services/derived-assignments/submit-nightly-reconcile.ts
  seam: schedule
  on_failure: Log per-workspace scheduling failure and continue; sign-in waits for the attempted refresh but
    does not fail solely because it failed.
  left_inconsistent: Eligibility-derived access can remain unchanged until renewal or expiry.
  observer: Sign-in/reconciliation logs and job state expose missed refresh.
  provenance: observed
  source: services-platform:modules/security/services/derived-assignments/submit-nightly-reconcile.ts (header)
- n: 2
  actor_or_service: Reconcile dispatcher (on nudge and every minute)
  action: Claim subject rows, publish without a transaction and remove only queue-accepted work.
  anchor: services-platform:modules/security/services/dispatch/dispatch-derived-assignment-reconciles.ts
  seam: schedule
  on_failure: Allow refused claims to expire for later dispatch.
  left_inconsistent: The subject remains awaiting reconciliation.
  observer: Pending rows and dispatcher logs show outstanding work.
  provenance: observed
  source: services-platform:modules/security/services/dispatch/dispatch-derived-assignment-reconciles.ts (header)
- n: 3
  actor_or_service: Reconcile consumer, or the nightly job's task
  action: Evaluate rule-bound roles through bounded eligibility batches for the subject.
  anchor: services-platform:modules/security/services/derived-assignments/reconcile.ts
  seam: queue
  on_failure: Return failed queue items for retry; exhausted receives can dead-letter.
  left_inconsistent: The subject retains earlier stored assignments.
  observer: Consumer logs and dead-letter monitoring expose failures.
  provenance: observed
  source: services-platform:modules/security/services/derived-assignments/reconcile.ts (header)
- n: 4
  actor_or_service: Shared derivation function (one transaction)
  action: 'Synchronize each decided binding: renew eligible rows to the role interval, revoke ineligible rows
    and preserve indeterminate rows with a warning.'
  anchor: services-platform:modules/security/services/persistence/assignments.ts:syncDerivedAssignments
  seam: sync
  on_failure: Roll back partial synchronization and retry the item.
  left_inconsistent: No partial desired-set transaction commits.
  observer: Synchronization logs and security history show decisions.
  provenance: observed
  source: services-platform:modules/security/services/derived-assignments/reconcile.ts (header)
- n: 5
  actor_or_service: Authorizer (next fresh evaluation)
  action: Include the linked contact’s usable derived assignments in a portal member’s evaluated slice.
  anchor: services-platform:modules/security/services/persistence/assignments.ts:resolveGrantSlice
  seam: sync
  on_failure: Ordinary evaluator refusal applies if no permission is available.
  left_inconsistent: Earlier cached slices can retain the prior set until expiry.
  observer: The access document and fresh context show the evaluated result.
  provenance: observed
  source: services-platform:modules/security/db/security-assignments.table.ts (header)
branches:
- Appointment-owning services call the same synchronization capability in their own transactions using their
  source key.
- Newly eligible contacts enter through sign-in or explicit reconciliation; nightly refresh starts from existing
  derived rows.
waits:
- 'Eligibility-bound renewals use the configured role interval: one day by default, within five minutes to
  thirty days. Other derivation sources supply their own validity.'
evidence:
- 'services-platform at ad3f6a65: modules/security/services/derived-assignments/*; services/dispatch/*reconcile*;
  services/persistence/assignments.ts; aws/logic/security-derived-assignments.lambda.ts, security-derived-assignment-reconcile.step-function.ts;
  aws/resources/security-derived-assignment-reconciles.sqs.ts; docs/eligibility.md (Derived assignments); docs/queued-work.md.'
examined_at: '2026-10-23'
examiner: codex
controls: [TC-AVL-001, TC-AVL-004, TC-AVL-005, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-006, TC-CRY-001, TC-CRY-002, TC-CRY-003, TC-CRY-004, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-IAM-001, TC-IAM-002, TC-IAM-004, TC-IAM-005, TC-IAM-006, TC-LOG-001, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-MON-001, TC-MON-003, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-004, TC-PI-005, TC-PRV-001, TC-PRV-002, TC-SEC-001, TC-SEC-002, TC-SEC-003, TC-SEC-004, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-TEN-004, TC-VEN-001, TC-VEN-002, TC-VEN-003]
---

# Reconcile derived role assignments

## What It Does

Derived access belongs to its source. Eligibility reconciliation computes outside the transaction and then synchronizes the decided assignments under derivation-key locks.

## The Flow

```mermaid
flowchart TD
    H1["Select reconciliation subjects"]
    H2["Dispatch queue items"]
    H3["Evaluate bounded batches"]
    H4["Synchronize decided assignments"]
    H5["Evaluate linked-contact authority"]
    H1 -->|queued reconciliation| H2
    H2 --> H3
    H1 -->|direct refresh or nightly work| H3
    H3 --> H4
    H4 --> H5
```

The diagram separates the relevant success, alternative and failure paths. The numbered hop rows retain the detailed authority checks, delegated work and retry limits.

## When Something Fails

Expiry is the fail-safe for eligibility-bound renewals. The shared function accepts source-owned validity, so the design does not assert a universal lifetime for appointment-derived rows. Newly eligible subjects are not discovered by the existing-row nightly scan.

Source synchronization uses binding/contact or appointment/rule derivation keys and unique live rows. An unchanged desired set converges. Nightly submission uses workspace and UTC day to deduplicate repeated scheduling; redelivered queue work reconciles again.

Derivation-key locks serialize synchronization for the same subject. Eligibility evaluation runs outside the write transaction; the desired set commits together afterward. Nightly scans cover contacts already holding derived rows, rather than discovering every newly eligible contact.

## Open Design Questions

The recorded findings retain any stronger planning expectation, missing writer or unproved recovery path. Observed behavior does not approve those outstanding product choices.
