---
id: CAP-PFM-MTD-017
title: Define Meaningful Record Relationships
feature: FEAT-PFM-MTD-06
product_area: platform
module: metadata
revision: 1
status: draft
product_intent: confirmed
implementation_evidence: observed
supersedes:
  - CAP-CO-ASC-001
  - CAP-CO-ASC-005
  - CAP-CO-ASC-008
  - CAP-CO-ASC-010
  - CAP-CO-XAP-001
  - CAP-CO-XAP-014
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - >-
    services-platform:docs/milestones/plans/PLAT-M6-plan.md#4. Assumptions A1, A2; 5. D1, D3, D5 (approved
    2026-10-21)
  - 'services-platform:docs/proposals/custom-objects/09-decisions.md#D-04, D-12, D-16'
purpose: >-
  Administrators need a shared vocabulary for connections the organization tracks. Relationship types make
  those connections recognizable from either endpoint.
outcomes:
  - Administrators need a shared vocabulary for connections the organization tracks.
  - Retiring a used type preserves its existing links.
actors:
  - ACT-PFM-007
  - ACT-PFM-009
surfaces:
  - console
  - api
preconditions: []
postconditions:
  - Type mutations leave security events identifying the changed fields.
permissions:
  - key: platform.association-types.read
    where_checked: api
    scope: tenant
  - key: platform.association-types.create
    where_checked: api
    scope: tenant
  - key: platform.association-types.update
    where_checked: api
    scope: tenant
  - key: platform.association-types.delete
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-PFM-MTD-017-01
    proved_by: [ATS-PFM-MTD-017-01, TST-PFM-MTD-036, TST-PFM-MTD-041]
    statement: >-
      A relationship type has a permanent bounded key, labels from both sides, one-to-one, one-to-many or
      many-to-many cardinality, dating and primary settings and detach, cascade or restrict rules for each
      side, rejecting invalid choices.
    kind: validation
    enforced_at:
      - api
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/associations/tests/e2e/associations.e2e.spec.js#answers 400 to an unknown
      cardinality, naming the three
    origin: 'services-platform:docs/milestones/plans/PLAT-M6-plan.md#4. Assumptions A1'
  - id: BR-CAP-PFM-MTD-017-02
    proved_by: [ATS-PFM-MTD-017-02, TST-PFM-MTD-036, TST-PFM-MTD-041, TST-PFM-MTD-045]
    statement: >-
      Endpoints may be contacts, organizations or active custom objects of the workspace, including two sides
      of the same type, but not draft custom objects.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/associations/tests/e2e/associations.e2e.spec.js#answers 400 to a type of a
      draft custom object, naming it as not active
    origin: >-
      services-platform:docs/milestones/plans/PLAT-M6-plan.md#5. D1 (Andrew, 2026-10-21: contacts,
      organizations, custom records)
  - id: BR-CAP-PFM-MTD-017-03
    proved_by: [ATS-PFM-MTD-017-03, TST-PFM-MTD-016, TST-PFM-MTD-041]
    statement: A relationship type cannot define a cascade that would delete a contact or organization.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/associations/tests/integration/services/persistence/association-types.integration.spec.js#refuses
      a cascade that would delete a native record, writing nothing
    origin: 'services-platform:docs/milestones/plans/PLAT-M6-plan.md#4. Assumptions A2'
  - id: BR-CAP-PFM-MTD-017-04
    proved_by: [ATS-PFM-MTD-017-04, TST-PFM-MTD-041]
    statement: >-
      Endpoint types, cardinality and dating are immutable; versioned changes may alter labels and policies,
      and primary support cannot be disabled while an in-force primary remains.
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/associations/tests/integration/services/persistence/association-types.integration.spec.js#refuses
      a change of cardinality, the shape of a type being immutable, and leaves the row as it was
    origin: 'services-platform:docs/milestones/plans/PLAT-M6-plan.md#4. Assumptions A1'
  - id: BR-CAP-PFM-MTD-017-05
    proved_by: [ATS-PFM-MTD-017-05, TST-PFM-MTD-041]
    statement: >-
      Removal deletes an unused type or deactivates a used type, preserving links and blocking new ones while
      allowing subsequent revival.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/associations/tests/integration/services/persistence/association-types.integration.spec.js#deactivates
      a type an association names instead of deleting it
    origin: none
  - id: BR-CAP-PFM-MTD-017-06
    proved_by: [ATS-PFM-MTD-009-13, ATS-PFM-MTD-017-06, ATS-PFM-MTD-017-09, TST-PFM-MTD-023, TST-PFM-MTD-041]
    statement: >-
      A relationship named as an object owner cannot be deleted or deactivated regardless of object status,
      and the refusal names the affected objects and required owner change.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/associations/tests/integration/services/persistence/association-types.integration.spec.js#refuses
      deactivating the type of one object, naming it by its label and what to change first
    origin: >-
      services-platform:docs/milestones/plans/PLAT-M6-plan.md#5. D3 (Andrew, 2026-10-21: "Refuse, name the
      object")
  - id: BR-CAP-PFM-MTD-017-07
    proved_by: []
    statement: Required-link flags can be stored on a type but have no enforcement in record or link writes.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: unknown
    source: FND-PFM-MTD-013
    origin: none
  - id: BR-CAP-PFM-MTD-017-08
    proved_by: []
    statement: >-
      The approved relationship limits are one hundred types per workspace and twenty touching an object, but
      current code enforces neither limit.
    kind: validation
    enforced_at: []
    must_prove: true
    provenance: documented
    source: 'services-platform:docs/proposals/custom-objects/09-decisions.md#D-16'
    origin: 'services-platform:docs/proposals/custom-objects/09-decisions.md#D-16 (approved 2026-10-18)'
  - id: BR-CAP-PFM-MTD-017-09
    proved_by: [ATS-PFM-MTD-017-07, TST-PFM-MTD-041]
    statement: >-
      Type mutations audit changed field names and advance the workspace query description so relationship
      changes become available to queries.
    kind: policy
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/associations/tests/integration/services/persistence/association-types.integration.spec.js#records
      a delete and a deactivation in the security trail, each by its action
    origin: none
  - id: BR-CAP-PFM-MTD-017-10
    proved_by: [ATS-PFM-MTD-017-03, ATS-PFM-MTD-017-08, TST-PFM-MTD-016, TST-PFM-MTD-026, TST-PFM-MTD-041]
    statement: >-
      The relationship-type editor explains choices and validation issues, saves no invalid definition and
      remains unavailable to standard users.
    kind: policy
    enforced_at:
      - console
    must_prove: true
    provenance: test
    source: >-
      react-console:acceptance/custom-objects/playwright/journeys/PLAT-M6-S02.spec.ts#PLAT-M6-S02: a
      relationship type with a missing label, a cascade onto contacts or a duplicate id is refused and nothing
      is saved
    origin: 'services-platform:docs/milestones/plans/PLAT-M6-plan.md#PLAT-M6-S02, S12'
scenarios:
  - id: SC-CAP-PFM-MTD-017-01
    given: An administrator opens relationship configuration.
    when: Define dated many-to-many Crew between contacts and boats.
    then: The contact and boat pages offer the relationship card using the appropriate directional label.
    provenance: test
    source: >-
      react-console:acceptance/custom-objects/playwright/journeys/PLAT-M6-S01.spec.ts#PLAT-M6-S01: an
      administrator defines Crew and both Osprey's and Tom's pages show it under their own label
edge_cases:
  - id: SC-CAP-PFM-MTD-017-02
    given: Boat selects a contact ownership type.
    when: Attempt to retire that type.
    then: The refusal identifies Boat as a dependent owner definition.
    provenance: test
    source: >-
      react-console:acceptance/custom-objects/playwright/journeys/PLAT-M6-S09.spec.ts#PLAT-M6-S09: a
      relationship type is relabelled, retired, revived and removed, and Boat's owner type cannot be retired
constraints: []
depends_on: []
related:
  - CAP-PFM-MTD-009
  - CAP-PFM-MTD-018
  - CAP-PFM-MTD-019
journeys:
  - JNY-PFM-MTD-004
open_questions: []
concepts:
  - CON-PFM-MTD-006
evidence:
  - >-
    services-platform at ad3f6a6582843008ed0f5a17b131878d15829af1:
    modules/associations/aws/api/associations.routes.ts , services/persistence/association-types.ts,
    services/persistence/endpoints.ts, rules.ts, db/association-types.table.ts; tests
    e2e/associations.e2e.spec.js, integration/services/persistence/association-types*.integration.spec.js,
    integration/db/association-types.table.integration.spec.js, unit/rules.spec.ts,
    unit/endpoint-resolvers.spec.ts; docs/associations.md §1, §2, §5, §10.
  - >-
    react-console at baf6dae382d6f41dce976c7f848667ccc37e076f: components/configuration/relationship-types/*,
    components/configuration/resources/relationship-types.tsx; Playwright PLAT-M6-S01, S02, S09, S12.
controls: [TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-DAT-001, TC-DAT-003, TC-DAT-004, TC-LOG-002, TC-LOG-003, 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, TC-TEN-004, TC-VEN-001, TC-VEN-002, TC-VEN-003]
exercised_by: [FAS-PFM-MTD-06-01, FAS-PFM-MTD-06-03, FAS-PFM-MTD-06-04, FAS-PFM-MTD-06-05]
suites: [TST-PFM-MTD-016, TST-PFM-MTD-023, TST-PFM-MTD-026, TST-PFM-MTD-036, TST-PFM-MTD-041, TST-PFM-MTD-045]
---

# Define Meaningful Record Relationships

## Purpose

Administrators need a shared vocabulary for connections the organization tracks. Relationship types make those connections recognizable from either endpoint.

## How It Works

The administrator chooses the two record types and labels for both directions. The definition includes cardinality, dating, primary designation and deletion policy. Platform validates the endpoints and rejects unsupported cascades. A saved type lets staff create links under that vocabulary. Labels and mutable policies can be maintained while the type shape remains fixed. Retiring a used type preserves its existing links. Owner relationships cannot be retired while custom-object definitions depend on them.

## Relationships

Record types provide endpoints. Custom objects may select a relationship as owner, and link maintenance uses the type definition.
