---
id: CAP-PFM-MTD-019
title: Preserve Relationships Through Deletion and Merge
feature: FEAT-PFM-MTD-06
product_area: platform
module: metadata
revision: 1
status: draft
product_intent: confirmed
implementation_evidence: observed
supersedes:
  - CAP-CO-ASC-006
  - CAP-CO-ASC-007
  - CAP-CO-ASC-009
  - CAP-CO-XAP-003
  - CAP-CO-XAP-004
  - CAP-CO-XAP-009
  - CAP-CO-XAP-012
  - CAP-CO-XAP-015
  - CAP-CO-XAP-016
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - 'services-platform:docs/milestones/PLAT-M5-custom-records.md#CRM-M1 (owner delete), PLAT-M5 B5'
  - >-
    services-platform:docs/milestones/plans/PLAT-M6-plan.md#4. Assumptions A2, A3; 5. D4 (Andrew, 2026-10-21:
    move to survivor)
  - >-
    services-platform:docs/milestones/PLAT-M6-custom-record-relationships.md#Changes requested (2026-10-21,
    "All or nothing")
purpose: >-
  Removing or consolidating endpoints must account for the information connected to them. Relationship
  policies keep those operations consistent across record owners.
outcomes:
  - Removing or consolidating endpoints must account for the information connected to them.
  - Planning reports the expected effects before application.
actors:
  - ACT-PFM-011
  - ACT-PFM-012
surfaces:
  - job
  - api
preconditions: []
postconditions: []
permissions:
  - key: >-
      rhythm_crm_app and rhythm_platform_app execution of the endpoint deletion and merge functions (no user
      key)
    where_checked: database
    scope: tenant
rules:
  - id: BR-CAP-PFM-MTD-019-01
    proved_by: [ATS-PFM-MTD-017-09, ATS-PFM-MTD-018-13, ATS-PFM-MTD-019-01, TST-PFM-MTD-037]
    statement: >-
      Endpoint deletion applies the configured side rule: detach removes links, cascade also deletes the
      opposite custom record, restrict refuses in-force links and ended links are removed regardless of
      policy.
    kind: policy
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/associations/tests/integration/db/apply-endpoint-deletion.integration.spec.js#answers
      what endpointDeletionOutcome says for an open association on the to side under restrict
    origin: 'services-platform:docs/milestones/plans/PLAT-M6-plan.md#4. Assumptions A1 (approved 2026-10-21)'
  - id: BR-CAP-PFM-MTD-019-02
    proved_by: [ATS-PFM-MTD-019-02, TST-PFM-MTD-037]
    statement: >-
      Cascades cannot delete contacts or organizations, stop at a depth of ten and roll back when the depth or
      a record lock prevents deletion.
    kind: validation
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/associations/tests/integration/db/apply-endpoint-deletion.integration.spec.js#refuses
      to delete a locked record in the cascade, naming it, and removes nothing
    origin: 'services-platform:docs/milestones/plans/PLAT-M6-plan.md#4. Assumptions A2'
  - id: BR-CAP-PFM-MTD-019-03
    proved_by: [ATS-PFM-MTD-019-03, TST-PFM-MTD-037, TST-PFM-MTD-133]
    statement: >-
      CRM endpoint deletion invokes relationship rules within its own transaction and reports restricting
      types while rolling back all resulting changes on refusal.
    kind: policy
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:tests/acceptance/custom-objects/deployed/associations/CO-ACC-0256.spec.js#step 3:
      Fulmar answers 404 and its associations are gone
    origin: 'services-platform:docs/milestones/PLAT-M5-custom-records.md#CRM-M1 (owner delete), applied 2026-10-19'
  - id: BR-CAP-PFM-MTD-019-04
    proved_by: [ATS-PFM-MTD-013-10, ATS-PFM-MTD-019-04, ATS-PFM-MTD-019-10, TST-PFM-MTD-037]
    statement: >-
      CRM-originated cascades perform the same value cleanup, asset detachment, audit, history and event work
      as direct custom-record deletion.
    kind: invariant
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/associations/tests/integration/db/apply-endpoint-deletion.integration.spec.js#leaves
      the same audit rows, events, revisions and no record, as the record store does
    origin: none
  - id: BR-CAP-PFM-MTD-019-05
    proved_by: [ATS-PFM-MTD-019-05, TST-PFM-MTD-037]
    statement: >-
      Deletion functions operate only within the transaction workspace and are executable only by the
      authorized CRM and Platform service roles.
    kind: permission
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/associations/tests/integration/db/apply-endpoint-deletion.integration.spec.js#refuses
      another tenant than the transaction’s with 42501, removing nothing
    origin: none
  - id: BR-CAP-PFM-MTD-019-06
    proved_by: [ATS-PFM-MTD-019-06, TST-PFM-MTD-037]
    statement: >-
      Owner policies continue to apply when the owned object is draft or archived, including cascades and
      restricting links.
    kind: policy
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/associations/tests/integration/db/apply-endpoint-deletion.integration.spec.js#cascades
      to a record of an archived custom object, deleting it
    origin: >-
      services-platform:docs/milestones/PLAT-M5-custom-records.md#PLAT-M5 B5 (Andrew, 2026-10-19: "Rule
      applies anyway")
  - id: BR-CAP-PFM-MTD-019-07
    proved_by: [ATS-PFM-MTD-019-07, TST-PFM-MTD-025, TST-PFM-MTD-039]
    statement: >-
      Merging CRM duplicates moves current and ended links to the survivor, ends overlapping duplicate or
      self-links on the merge day and clears an additional primary flag.
    kind: policy
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/associations/tests/integration/db/associations-merge.integration.spec.js#ends
      m’s association that repeats s’s, giving it s’s id, and leaves s’s as it was
    origin: 'services-platform:docs/milestones/plans/PLAT-M6-plan.md#4. Assumptions A3'
  - id: BR-CAP-PFM-MTD-019-08
    proved_by: [ATS-PFM-MTD-014-07, ATS-PFM-MTD-019-08, TST-PFM-MTD-053]
    statement: >-
      Merge also redirects custom-record reference values from merged contacts or organizations to the
      survivor and captures prior values in history.
    kind: policy
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/custom-objects/tests/integration/db/custom-object-records-merge.integration.spec.js#names
      the surviving contact in every property that named a merged one
    origin: 'services-platform:docs/milestones/plans/PLAT-M6-plan.md#5. D4 (Andrew, 2026-10-21: "Move to survivor")'
  - id: BR-CAP-PFM-MTD-019-09
    proved_by: [ATS-PFM-MTD-019-09, TST-PFM-MTD-039, TST-PFM-MTD-053]
    statement: >-
      Merge planning counts moved links, redirected references and endings, application follows those counts,
      repeat application moves nothing and per-record limits do not block a merge.
    kind: calculation
    enforced_at:
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/associations/tests/integration/db/associations-merge.integration.spec.js#counts
      the associations held by m, and says how many end instead of moving
    origin: none
  - id: BR-CAP-PFM-MTD-019-10
    proved_by: []
    statement: 'Workspace deletion clears relationships before their types, and reset empties both collections.'
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: documented
    source: 'services-platform:docs/associations.md#8. Tenant lifecycle'
    origin: none
scenarios:
  - id: SC-CAP-PFM-MTD-019-01
    given: Ola owns Fulmar under a cascading ownership policy.
    when: Delete Ola through CRM.
    then: Both endpoint and dependent boat disappear and no retained link names Ola.
    provenance: test
    source: >-
      services-platform:tests/acceptance/custom-objects/deployed/associations/CO-ACC-0256.spec.js#step 5: no
      association names Ola any more
edge_cases:
  - id: SC-CAP-PFM-MTD-019-02
    given: Jane duplicate has crew ties and is a boat Previous Owner reference.
    when: Merge the duplicate into Jane through CRM.
    then: 'Crew links, boat ownership and the reference identify the survivor.'
    provenance: test
    source: >-
      react-console:acceptance/custom-objects/playwright/journeys/PLAT-M6-S11.spec.ts#PLAT-M6-S11: after CRM
      merges Jane's duplicate her crew places, her boats and Osprey's Previous Owner name the surviving Jane
constraints: []
depends_on:
  - CAP-PFM-MTD-017
  - CAP-PFM-MTD-018
related:
  - CAP-PFM-MTD-013
  - CAP-PFM-MTD-014
journeys:
  - JNY-PFM-MTD-005
open_questions: []
concepts:
  - CON-PFM-MTD-006
evidence:
  - >-
    services-platform at ad3f6a6582843008ed0f5a17b131878d15829af1:
    modules/associations/services/persistence/associations.ts , rules.ts ,
    services/maintenance/tenant-participant.ts; tests
    integration/db/apply-endpoint-deletion.integration.spec.js,
    integration/db/associations-merge.integration.spec.js; modules/custom-objects tests
    integration/db/custom-object-records-merge.integration.spec.js;
    tests/acceptance/custom-objects/deployed/associations/CO-ACC-0256.spec.js, journeys/cross-application/**;
    docs/associations.md §6 to §8, docs/record-merge-participant.md , docs/custom-objects.md#A CRM merge moves
    reference properties to the survivor.
  - 'react-console at baf6dae382d6f41dce976c7f848667ccc37e076f: Playwright PLAT-M6-S08, S11.'
controls: [TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-DAT-004, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-PI-001, TC-PI-005, TC-PRV-001, TC-PRV-002, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-TEN-004, TC-VEN-001, TC-VEN-002, TC-VEN-003]
exercised_by: [FAS-PFM-MTD-06-02]
suites: [TST-PFM-MTD-025, TST-PFM-MTD-037, TST-PFM-MTD-039, TST-PFM-MTD-053, TST-PFM-MTD-133]
---

# Preserve Relationships Through Deletion and Merge

## Purpose

Removing or consolidating endpoints must account for the information connected to them. Relationship policies keep those operations consistent across record owners.

## How It Works

The deleting service invokes endpoint policies within its transaction. Restrict can stop the operation, detach can remove a connection and cascade can remove dependent custom records. Any refusal rolls back the operation. A CRM merge instead redirects links and reference properties to the survivor. Collisions are resolved according to merge policy while history retains the earlier references. Planning reports the expected effects before application. These functions isolate the calling workspace and reserve execution to the authorized services.

## Relationships

CRM owns its endpoint deletion and merge. Relationship storage supplies policy and custom-record storage supplies dependent cleanup and reference history.
