---
id: WF-CRM-ENT-010
title: Disconnect and resolve shared contact details
trigger: >-
  Organization source removal disconnects shared contact details; staff
  subsequently choose to keep, relink or remove them.
product_area: crm
module: entities
revision: 1
status: draft
design_intent: confirmed
origin: >-
  D-25 (Andrew, 2026-10-19) and CRM-M1 plan A11, cited in
  services-crm:docs/disconnected-information.md
implementation_evidence: observed
inference: []
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
implements:
  - CAP-CRM-ENT-006
  - CAP-CRM-ENT-005
moves:
  - STATE-CRM-ENT-006
crosses:
  - SVC-CRM-ENT-entities
idempotency: >-
  Database disconnection belongs to the source-removal transaction. Dedicated
  resolution APIs compare the read version and require a still-disconnected row.
  The console rereads and patches the collection, relying instead on general
  save validation.
concurrency: >-
  Removing a source and disconnecting every affected contact commit together.
  Contact version advancement can invalidate open editors; resolution then
  compares the applicable saved version before changing a collection.
hops:
  - 'n': 1
    actor_or_service: Organization save
    action: >-
      Remove the organization address or phone source in a save, or absorb its
      organization through merge.
    anchor: >-
      services-crm:modules/entities/services/persistence/organizations.ts:updateOrganization
    seam: sync
    on_failure: Rollback leaves the source and contact links unchanged.
    left_inconsistent: No partial business write remains.
    observer: The caller receives the result.
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/organizations.ts:updateOrganization
  - 'n': 2
    actor_or_service: Database trigger
    action: >-
      Convert every dependent contact detail to a snapshot with source, time and
      reason, and advance affected contact versions.
    anchor: >-
      services-crm:db/migrations/20261019210829_disconnected_contact_information.sql
    seam: sync
    on_failure: Trigger failure aborts source removal and all disconnections.
    left_inconsistent: No partial business write remains.
    observer: Affected histories and contact reads show disconnection.
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contact-information-sources.integration.spec.js#turns
      the linked work address into a disconnected row holding its last value,
      not a copy (D-25)
  - 'n': 3
    actor_or_service: CRM change-event consumers
    action: >-
      React to changed organization addresses for contact regions and process
      the affected contacts’ search and region messages.
    anchor: 'services-crm:aws/crm.change-events.ts:crmChangeEventStreams'
    seam: queue
    on_failure: Retry failed SQS handlers and dead-letter after five receives.
    left_inconsistent: Regions and search can lag the committed disconnection.
    observer: Consumer logs expose failed projections.
    provenance: documented
    source: 'services-crm:docs/change-events.md#The handlers CRM registers'
  - 'n': 4
    actor_or_service: Contact 360
    action: >-
      Show disconnection provenance to readers and the three resolution choices
      to authorized editors.
    anchor: >-
      react-console:components/record-views/cards/contact/disconnected-row.tsx:DisconnectedRowNotice
    seam: sync
    on_failure: Rendering alone does not mutate or repair the detail.
    left_inconsistent: Disconnection remains until a user successfully resolves it.
    observer: Contact readers see verification status.
    provenance: observed
    source: >-
      react-console:components/record-views/cards/contact/disconnected-row.tsx:DisconnectedRowNotice
  - 'n': 5
    actor_or_service: 'Console, or an integration calling the dedicated actions'
    action: >-
      The console rereads and patches the containing collection; integrations
      can use a dedicated keep, relink or remove operation with the read
      version.
    anchor: >-
      services-crm:modules/entities/aws/api/disconnected-information.ts:mutateDisconnectedInformation
    seam: sync
    on_failure: >-
      Version conflicts require reload. Dedicated APIs additionally refuse a row
      already resolved.
    left_inconsistent: A failed save leaves the disconnected value available for another attempt.
    observer: The console presents a toast or field error.
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/contacts-disconnected.e2e.spec.js#is
      refused 409 on a row that is no longer disconnected
branches:
  - >-
    Calendar expiry makes a link unresolved; source removal creates
    disconnection provenance.
  - Archived contacts must be unarchived before ordinary resolution actions.
waits:
  - No dedicated reminder or timeout resolves an unattended disconnected detail.
evidence:
  - >-
    services-crm at 7ea48a41: docs/disconnected-information.md;
    db/migrations/20261019210829_disconnected_contact_information.sql;
    modules/entities/aws/api/disconnected-information.ts;
    services/persistence/contacts.ts (prepareContactInformationSources);
    aws/crm.change-events.ts (linked-contact-region-assignment).
  - >-
    react-console at da630758:
    components/record-views/cards/contact/disconnected-row.tsx;
    components/affiliations/disconnected-source.tsx.
controls:
  - TC-AZ-001
  - TC-AZ-002
  - TC-AZ-003
  - TC-AZ-004
  - TC-AZ-006
  - TC-DAT-001
  - TC-DAT-002
  - TC-DAT-003
  - TC-IAM-001
  - TC-IAM-005
  - TC-IAM-006
  - TC-LOG-004
  - TC-LOG-005
  - TC-PI-001
  - TC-PI-003
  - TC-PI-005
  - TC-PRV-001
  - TC-PRV-002
  - TC-TEN-001
  - TC-TEN-002
  - TC-TEN-003
narrative:
  status: verified
  review: NARRATIVE-REVIEW-2026-10-23
narrative_sources:
  - section: What It Does
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/organizations.ts:updateOrganization
  - section: What It Does
    provenance: observed
    source: >-
      services-crm:db/migrations/20261019210829_disconnected_contact_information.sql
  - section: What It Does
    provenance: documented
    source: 'services-crm:aws/crm.change-events.ts:crmChangeEventStreams'
  - section: What It Does
    provenance: observed
    source: >-
      react-console:components/record-views/cards/contact/disconnected-row.tsx:DisconnectedRowNotice
  - section: What It Does
    provenance: observed
    source: >-
      services-crm:modules/entities/aws/api/disconnected-information.ts:mutateDisconnectedInformation
  - section: What Starts It
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/organizations.ts:updateOrganization
  - section: What Starts It
    provenance: observed
    source: >-
      services-crm:db/migrations/20261019210829_disconnected_contact_information.sql
  - section: What Starts It
    provenance: documented
    source: 'services-crm:aws/crm.change-events.ts:crmChangeEventStreams'
  - section: What Starts It
    provenance: observed
    source: >-
      react-console:components/record-views/cards/contact/disconnected-row.tsx:DisconnectedRowNotice
  - section: What Starts It
    provenance: observed
    source: >-
      services-crm:modules/entities/aws/api/disconnected-information.ts:mutateDisconnectedInformation
  - section: The Flow
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/organizations.ts:updateOrganization
  - section: The Flow
    provenance: observed
    source: >-
      services-crm:db/migrations/20261019210829_disconnected_contact_information.sql
  - section: The Flow
    provenance: documented
    source: 'services-crm:aws/crm.change-events.ts:crmChangeEventStreams'
  - section: The Flow
    provenance: observed
    source: >-
      react-console:components/record-views/cards/contact/disconnected-row.tsx:DisconnectedRowNotice
  - section: The Flow
    provenance: observed
    source: >-
      services-crm:modules/entities/aws/api/disconnected-information.ts:mutateDisconnectedInformation
  - section: Hop by Hop
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/organizations.ts:updateOrganization
  - section: Hop by Hop
    provenance: observed
    source: >-
      services-crm:db/migrations/20261019210829_disconnected_contact_information.sql
  - section: Hop by Hop
    provenance: documented
    source: 'services-crm:aws/crm.change-events.ts:crmChangeEventStreams'
  - section: Hop by Hop
    provenance: observed
    source: >-
      react-console:components/record-views/cards/contact/disconnected-row.tsx:DisconnectedRowNotice
  - section: Hop by Hop
    provenance: observed
    source: >-
      services-crm:modules/entities/aws/api/disconnected-information.ts:mutateDisconnectedInformation
  - section: When Something Fails
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/organizations.ts:updateOrganization
  - section: When Something Fails
    provenance: observed
    source: >-
      services-crm:db/migrations/20261019210829_disconnected_contact_information.sql
  - section: When Something Fails
    provenance: documented
    source: 'services-crm:aws/crm.change-events.ts:crmChangeEventStreams'
  - section: When Something Fails
    provenance: observed
    source: >-
      react-console:components/record-views/cards/contact/disconnected-row.tsx:DisconnectedRowNotice
  - section: When Something Fails
    provenance: observed
    source: >-
      services-crm:modules/entities/aws/api/disconnected-information.ts:mutateDisconnectedInformation
  - section: Repeats and Races
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/organizations.ts:updateOrganization
  - section: Repeats and Races
    provenance: observed
    source: >-
      services-crm:db/migrations/20261019210829_disconnected_contact_information.sql
  - section: Repeats and Races
    provenance: documented
    source: 'services-crm:aws/crm.change-events.ts:crmChangeEventStreams'
  - section: Repeats and Races
    provenance: observed
    source: >-
      react-console:components/record-views/cards/contact/disconnected-row.tsx:DisconnectedRowNotice
  - section: Repeats and Races
    provenance: observed
    source: >-
      services-crm:modules/entities/aws/api/disconnected-information.ts:mutateDisconnectedInformation
  - section: What Is Observable
    provenance: observed
    source: >-
      services-crm:modules/entities/services/persistence/organizations.ts:updateOrganization
  - section: What Is Observable
    provenance: observed
    source: >-
      services-crm:db/migrations/20261019210829_disconnected_contact_information.sql
  - section: What Is Observable
    provenance: documented
    source: 'services-crm:aws/crm.change-events.ts:crmChangeEventStreams'
  - section: What Is Observable
    provenance: observed
    source: >-
      react-console:components/record-views/cards/contact/disconnected-row.tsx:DisconnectedRowNotice
  - section: What Is Observable
    provenance: observed
    source: >-
      services-crm:modules/entities/aws/api/disconnected-information.ts:mutateDisconnectedInformation
  - section: What It Does
    provenance: inferred
    source: >-
      services-crm:modules/entities/services/persistence/organizations.ts:updateOrganization;
      services-crm:db/migrations/20261019210829_disconnected_contact_information.sql;
      services-crm:aws/crm.change-events.ts:crmChangeEventStreams;
      react-console:components/record-views/cards/contact/disconnected-row.tsx:DisconnectedRowNotice;
      services-crm:modules/entities/aws/api/disconnected-information.ts:mutateDisconnectedInformation
    note: >-
      The explicitly labeled rationale interprets the existing transaction,
      transport and recovery boundaries; it is not a product decision.
  - section: Open Design Questions
    provenance: documented
    source: 'rhythm-v2-docs:specs/crm/entities/FINDINGS.md#Findings'
---

# Disconnect and resolve shared contact details

## What It Does

This flow preserves a person’s last known detail when its organization source disappears, then lets staff make an explicit recovery choice. If Harbor Engineering removes its main address, Ruth’s linked work address becomes disconnected information with an explanation, rather than a dangling reference or a silently confirmed independent value.

The recorded disconnection decision establishes that preservation behavior. Our reading is that showing source, cause and time helps staff distinguish this situation from a tie that simply ended. Recovery changes the contact under current-version checks; displaying the warning alone does not verify the address.

## What Starts It

An organization save removes a shared address or telephone source, or an organization merge removes its source identity. That transaction starts preservation of dependent contact values. Later a contact editor opens the retained disconnected detail and chooses an allowed resolution. Source loss and staff recovery are separate transactions; seeing a warning alone changes neither the retained value nor its verification state.

## The Flow

The diagram shows the sequence and service seams. Each synchronous stage waits for its result, while captured-event, queue and scheduled stages can run after the originating request has returned. The hop descriptions below identify the write and failure boundary, so the last arrow must not be mistaken for one atomic transaction across every participant.

```mermaid
sequenceDiagram
  participant O as Organization save
  participant D as CRM database
  participant U as Contact reviewer
  participant C as Contact save or dedicated action
  O->>D: remove shared source
  D->>D: preserve dependent snapshots and capture contacts
  D-->>U: disconnected values and origin
  U->>C: keep, relink or remove
  C->>D: compare version and commit resolution
```

## Hop by Hop

### 1. Organization save

An organization save removes an address or phone source, or a merge absorbs its organization. This is the transaction initiating source loss. If the save fails, the source and existing live references remain; staff cannot observe a committed removal without the corresponding transactional effects.

### 2. Database trigger

The database trigger snapshots every dependent contact detail and records the organization, time and reason for disconnection. It advances affected owner versions and captures their changes. Trigger failure rolls back source removal and all snapshots together, preserving the link’s referential boundary.

### 3. CRM change-event consumers

Region and search handlers receive the relevant changes after commit. They can process the organization and affected contacts independently. The disconnected contact value already exists while these projections refresh, so a delayed region update is not evidence that preservation failed.

### 4. Contact 360

Contact 360 presents the disconnection explanation to readers and supported recovery controls to editors. Showing this state is not a mutation. The detail remains marked for verification until an authorized version-tested save or dedicated action actually settles it.

### 5. Console, or an integration calling the dedicated actions

The console rereads and patches the containing collection; integrations can call dedicated keep, relink or remove actions. Dedicated routes additionally require the row still be disconnected. Keeping establishes owned information, relinking selects a valid effective source, and removal deletes the detail after applicable guards.

## When Something Fails

### Hop 1 — Organization save

The failure boundary is: Rollback leaves the source and contact links unchanged. The remaining work or data is: No partial business write remains. The caller receives the result.

### Hop 2 — Database trigger

The failure boundary is: Trigger failure aborts source removal and all disconnections. The remaining work or data is: No partial business write remains. Affected histories and contact reads show disconnection.

### Hop 3 — CRM change-event consumers

The failure boundary is: Retry failed SQS handlers and dead-letter after five receives. The remaining work or data is: Regions and search can lag the committed disconnection. Consumer logs expose failed projections. This failure is downstream of committed input; operators investigate its own delivery rather than treating the caller’s original save as failed.

### Hop 4 — Contact 360

The failure boundary is: Rendering alone does not mutate or repair the detail. The remaining work or data is: Disconnection remains until a user successfully resolves it. Contact readers see verification status.

### Hop 5 — Console, or an integration calling the dedicated actions

The failure boundary is: Version conflicts require reload. Dedicated APIs additionally refuse a row already resolved. The remaining work or data is: A failed save leaves the disconnected value available for another attempt. The console presents a toast or field error.

## Repeats and Races

The source-removal transaction and all disconnections have one commit boundary. After that commit, current contact versions order recovery against other edits. A dedicated action checks both version and still-disconnected state; a repeated resolution cannot quietly apply to a row already settled by somebody else.

The console’s collection patch uses general save validation instead of the dedicated action’s state predicate. An archived contact requires restoration before ordinary recovery. Calendar expiry alone creates unresolved read behavior, not disconnection provenance, and source removal is therefore not interchangeable with an employer departure.

## What Is Observable

The caller receives the result. Affected histories and contact reads show disconnection. Consumer logs expose failed projections. Contact readers see verification status. The console presents a toast or field error.

The person or initiating caller sees the synchronous result at its own boundary. Captured history establishes committed local changes; logs, durable job state and queue outcomes explain remaining work. A successful database change, accepted downstream request and completed delivery are different observations and must be investigated through the component that owns each one.

## Open Design Questions

The dedicated API and built console recovery paths have different state checks, as retained in the existing findings. Projection lag and verification of the person’s chosen replacement remain separate concerns. No source evidence establishes that viewing a warning or pressing Keep independently validates a postal address with the person.
