---
id: DOM-PFM-MTD-006
title: Relationship
realises: CON-PFM-MTD-006
product_area: platform
module: metadata
revision: 1
status: draft
design_intent: confirmed
origin: Custom objects architecture D-04, D-09, D-12, D-16 (approved 2026-10-18); PLAT-M6 plan approved 2026-10-21
  with D1 to D5 and assumptions A1 to A3 (services-platform:docs/milestones/PLAT-M6-custom-record-relationships.md;
  the milestone is In Progress, so acceptance is not established); CRM-M1 owner delete (PLAT-M5 decisions table)
implementation_evidence: observed
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
owned_by: SVC-PFM-MTD-associations
readable_by:
- SVC-PFM-MTD-custom-objects
lifecycle: STATE-PFM-MTD-010
custom_fields: none
identity: Type shape and link endpoints are fixed. Labels and permitted policies can change; link dates and annotations
  are replaceable. An end date stops occupancy only after that day passes.
attributes:
- name: type · endpoints
  kind: reference
  required: true
  computed: false
  notes: Each side names contacts, organizations or an active custom-object type in the workspace. Equal endpoint
    types are allowed, and those choices are fixed.
- name: type · labels
  kind: text
  required: true
  computed: false
  notes: Directional labels explain the connection from either side and contain one to one hundred characters.
- name: type · cardinality
  kind: enum
  required: true
  computed: false
  notes: The permanent shape allows one-to-one, one-to-many or many-to-many connections, interpreted from the first
    side.
- name: type · dated and primary
  kind: enum
  required: true
  computed: false
  notes: The type fixes whether dates are used and configures primary support, allowing one in-force primary connection
    per first-side record.
- name: type · deletion rules
  kind: enum
  required: true
  computed: false
  notes: Each endpoint has its own detach, restrict or cascade policy; cascades can target only custom records.
- name: type · required flags
  kind: enum
  required: false
  computed: false
  notes: Required-link settings are retained but do not cause record or link validation.
- name: type · active
  kind: enum
  required: true
  computed: false
  notes: Inactive types retain existing links and block new ones until reactivated.
- name: link · endpoints
  kind: reference
  required: true
  computed: false
  notes: Writes resolve and verify both endpoint records. Endpoint columns are not database references across services.
- name: link · dates
  kind: date
  required: false
  computed: false
  notes: Dates are inclusive in the workspace calendar. Missing boundaries are unbounded, and a link stays in force
    through its end day.
- name: link · primary, position and notes
  kind: text
  required: false
  computed: false
  notes: Primary marking requires type support, position cannot be negative, and notes contain at most four thousand
    characters.
- name: link · place keys
  kind: computed
  required: false
  computed: true
  notes: Internal cardinality keys support database occupancy constraints and are not returned to clients.
- name: version, lock and modification facts
  kind: computed
  required: true
  computed: true
  notes: System facts track versions and locks. Replacement and ending require a version; link and type removal
    do not.
relationships:
- to: DOM-PFM-MTD-001
  cardinality: many-to-many
  ownership: A type owns its category choices and resolves endpoints through the registry or a registered resolver.
  cross_service: true
- to: DOM-PFM-MTD-005
  cardinality: many-to-many
  ownership: Associations stores links to custom records; their store supplies endpoint resolution and cascade deletion.
  cross_service: false
- to: DOM-PFM-MTD-004
  cardinality: one-to-many
  ownership: A custom object selects one owner type, protecting that type from retirement while selected.
  cross_service: false
invariants:
- id: INV-DOM-PFM-MTD-006-01
  statement: Endpoint types, cardinality and dating are fixed for the life of a relationship type.
  enforced_at:
  - service
  provenance: test
  source: services-platform:modules/associations/tests/integration/services/persistence/association-types.integration.spec.js#refuses
    a change of cardinality, the shape o
- id: INV-DOM-PFM-MTD-006-02
  statement: Cascade policies cannot remove contacts or organizations.
  enforced_at:
  - service
  - database
  provenance: test
  source: services-platform:modules/associations/tests/integration/services/persistence/association-types.integration.spec.js#refuses
    a cascade that would delete a native
- id: INV-DOM-PFM-MTD-006-03
  statement: A type named as any object's owner cannot be removed or deactivated; removal of a currently used type
    deactivates it.
  enforced_at:
  - service
  provenance: test
  source: services-platform:modules/associations/tests/integration/services/persistence/association-types.integration.spec.js#refuses
    deactivating the type of one object,
- id: INV-DOM-PFM-MTD-006-04
  statement: Ordinary place checks reject overlapping spans for a pair, restricted endpoint or first-side primary,
    while adjacent spans are permitted.
  enforced_at:
  - service
  - database
  provenance: test
  source: services-platform:modules/associations/tests/integration/services/persistence/associations.in-force.integration.spec.js#refuses
    a second link of a one-place typ
- id: INV-DOM-PFM-MTD-006-05
  statement: Conflicting concurrent link writes cannot both acquire the same place.
  enforced_at:
  - service
  - database
  provenance: test
  source: services-platform:modules/associations/tests/integration/services/persistence/associations.in-force.integration.spec.js#refuses
    one of two writers of overlappin
- id: INV-DOM-PFM-MTD-006-06
  statement: Link identity retains type and endpoints, disallows self-links and admits ordered dates only for dated
    types.
  enforced_at:
  - service
  - database
  provenance: test
  source: services-platform:modules/associations/tests/integration/services/persistence/associations.integration.spec.js#refuses
    an end before the start on a dated type
- id: INV-DOM-PFM-MTD-006-07
  statement: Creation caps links at one thousand per record, type and side, counting ended links; merges may exceed
    the cap.
  enforced_at:
  - service
  provenance: test
  source: services-platform:modules/associations/tests/integration/services/persistence/associations.integration.spec.js#refuses
    an association past the per-record limit
- id: INV-DOM-PFM-MTD-006-08
  statement: Endpoint deletion applies side-specific detach, cascade and restrict policies in the caller's transaction
    and removes ended links regardless of policy.
  enforced_at:
  - database
  provenance: test
  source: services-platform:modules/associations/tests/integration/db/apply-endpoint-deletion.integration.spec.js#answers
    what endpointDeletionOutcome says for an open as
- id: INV-DOM-PFM-MTD-006-09
  statement: Deletion and merge functions restrict changes to the session workspace and authorized application roles.
  enforced_at:
  - database
  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
- id: INV-DOM-PFM-MTD-006-10
  statement: Merge redirects duplicate endpoints to the survivor, ends collisions on the merge day and clears extra
    primary flags; a repeated apply has no remaining moves.
  enforced_at:
  - database
  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 inst
physical:
- platform.association_types
- platform.associations
- platform.association_change_events
evidence:
- 'services-platform at ad3f6a65: modules/associations/services/persistence/association-types.ts, associations.ts,
  endpoints.ts; rules.ts; association-events.ts; history/association-history.config.ts; db/*.table.ts; services/maintenance/tenant-participant.ts;
  lib/record-merge/platform-references.ts; docs/associations.md; docs/record-merge-participant.md'
- 'react-console at baf6dae3: lib/custom-objects/relationship-types-api.ts, relationships.ts, related.ts; components/custom-objects/related-records.tsx'
---

# Relationship

## What It Models

Relationships let an organization connect records with its own vocabulary. A type defines direction, endpoint categories and policy; a link records a connection between specific records.

## Identity and Attributes

Type shape and link endpoints are fixed. Labels and permitted policies can change; link dates and annotations are replaceable. An end date stops occupancy only after that day passes.

## Relationships

Endpoints are contacts, organizations or custom records. A qualifying type can supply ownership, and every endpoint owner must apply the corresponding deletion and merge contract.

## Invariants

Locks and span checks protect ordinary occupancy. Policy prevents native cascades, protects owner types and couples endpoint deletion with link consequences.

## How It Is Stored and Shared

Associations writes types and links. CRM uses Platform’s database functions inside its own transaction, and database capture records changes for the first side and a custom second side.

## Open Design Questions

Required-link settings and type-count limits are not enforced. Native-record link history is retained here but still lacks a CRM timeline presentation.
