---
id: SVC-CRM-ENT-entities
title: Entities Service Module
product_area: crm
module: entities
repository: services-crm
source_module: modules/entities
revision: 1
status: draft
design_intent: proposed
implementation_evidence: observed
inference: []
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
owns:
  - DOM-CRM-ENT-001
  - DOM-CRM-ENT-002
  - DOM-CRM-ENT-003
  - DOM-CRM-ENT-033
does_not_own:
  - >-
    CRM Data Jobs owns bulk changes and imports, including routes hosted
    alongside contact and organization endpoints.
  - >-
    CRM Data Quality owns duplicate rules, candidates, creation blocking, merges
    and merged-identity resolution.
  - >-
    CRM Regions owns geography and automatic assignment through record change
    reactions.
  - >-
    CRM Activities owns activities and tasks referencing people and
    organizations.
  - >-
    Platform owns definitions, security, search indexes, communication templates
    and delivery, attention, file storage and number allocation.
  - >-
    The classification route host serves the catalogue API separately while
    Entities retains product and record ownership.
operations:
  - operation_id: getEntitiesHealth
    intent: Return deployment availability and build identity for the Entities API.
    access: public
    permission: 'none: public health route'
    realises: []
    published_to_submissions: 'not published: reports deployment health and captures no business request.'
  - operation_id: createContact
    intent: >-
      Persist a person aggregate with contact points, preferences, custom values
      and relationships, recognizing an identical create retry.
    access: console
    permission: >-
      crm.contacts.create (also evaluates
      crm.contacts.create-despite-duplicate-block)
    realises:
      - CAP-CRM-ENT-001
      - CAP-CRM-ENT-005
      - CAP-CRM-ENT-007
      - CAP-CRM-ENT-008
      - CAP-CRM-ENT-009
    published_to_submissions: >-
      not published: staff creation has no designed intake process or capture
      binding.
  - operation_id: listContacts
    intent: >-
      Return a number-ordered contact page with supported search, organization,
      selection and archive filters.
    access: console
    permission: crm.contacts.read
    realises:
      - CAP-CRM-ENT-003
      - CAP-CRM-ENT-013
    published_to_submissions: >-
      not published: retrieves or renders existing information rather than
      accepting a capture.
  - operation_id: recursivelyListContactsForOrganizationId
    intent: >-
      Return the unpaged current contact set for an organization and its
      descendant hierarchy.
    access: console
    permission: crm.contacts.read
    realises:
      - CAP-CRM-ENT-003
    published_to_submissions: >-
      not published: retrieves or renders existing information rather than
      accepting a capture.
  - operation_id: getContact
    intent: >-
      Retrieve the contact aggregate, optionally projecting the requested
      fields.
    access: console
    permission: crm.contacts.read
    realises:
      - CAP-CRM-ENT-003
      - CAP-CRM-ENT-005
    published_to_submissions: >-
      not published: retrieves or renders existing information rather than
      accepting a capture.
  - operation_id: getContactMailingAddress
    intent: >-
      Resolve today’s mailing destination from eligible seasonal selection or
      the preferred address.
    access: console
    permission: crm.contacts.read
    realises:
      - CAP-CRM-ENT-005
    published_to_submissions: >-
      not published: retrieves or renders existing information rather than
      accepting a capture.
  - operation_id: updateContact
    intent: >-
      Apply replacement semantics to the contact aggregate using the supplied
      read version.
    access: console
    permission: crm.contacts.update (also evaluates crm.contacts.unlock)
    realises:
      - CAP-CRM-ENT-004
      - CAP-CRM-ENT-005
      - CAP-CRM-ENT-006
      - CAP-CRM-ENT-007
      - CAP-CRM-ENT-008
      - CAP-CRM-ENT-009
      - CAP-CRM-ENT-011
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: patchContact
    intent: Apply requested contact changes through a version-tested JSON Patch.
    access: console
    permission: crm.contacts.update (also evaluates crm.contacts.unlock)
    realises:
      - CAP-CRM-ENT-004
      - CAP-CRM-ENT-005
      - CAP-CRM-ENT-006
      - CAP-CRM-ENT-007
      - CAP-CRM-ENT-008
      - CAP-CRM-ENT-009
      - CAP-CRM-ENT-011
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: deleteContact
    intent: >-
      Remove the live contact aggregate while retaining its historical
      revisions.
    access: console
    permission: crm.contacts.delete
    realises:
      - CAP-CRM-ENT-014
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: reconcileAffiliationDates
    intent: Run bounded workspace date settlement or report its dry-run effects.
    access: console
    permission: crm.contacts.bulk
    realises:
      - CAP-CRM-ENT-010
      - CAP-CRM-ENT-011
      - CAP-CRM-ENT-012
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: archiveContact
    intent: >-
      Record archive metadata at the current version while preserving contact
      status and data.
    access: console
    permission: crm.contacts.archive
    realises:
      - CAP-CRM-ENT-013
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: unarchiveContact
    intent: Clear contact archive metadata without changing the stored status.
    access: console
    permission: crm.contacts.archive
    realises:
      - CAP-CRM-ENT-013
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: verifyContactAddress
    intent: Convert a still-disconnected address into an owned retained value.
    access: console
    permission: crm.contacts.update
    realises:
      - CAP-CRM-ENT-006
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: relinkContactAddress
    intent: >-
      Replace a disconnected address with a valid effective-affiliation source
      link.
    access: console
    permission: crm.contacts.update
    realises:
      - CAP-CRM-ENT-006
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: removeContactAddress
    intent: Delete the selected still-disconnected contact address.
    access: console
    permission: crm.contacts.update
    realises:
      - CAP-CRM-ENT-006
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: verifyContactPhoneNumber
    intent: Retain a still-disconnected phone value as the contact’s own.
    access: console
    permission: crm.contacts.update
    realises:
      - CAP-CRM-ENT-006
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: relinkContactPhoneNumber
    intent: Connect a disconnected phone entry to a valid current organization source.
    access: console
    permission: crm.contacts.update
    realises:
      - CAP-CRM-ENT-006
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: removeContactPhoneNumber
    intent: Delete the selected still-disconnected phone entry.
    access: console
    permission: crm.contacts.update
    realises:
      - CAP-CRM-ENT-006
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: listContactHistory
    intent: >-
      Retrieve recent-first contact revisions with the supported actor,
      operation, source and time filters.
    access: console
    permission: crm.contacts.history (also evaluates crm.contacts.read)
    realises:
      - CAP-CRM-ENT-016
    published_to_submissions: >-
      not published: retrieves or renders existing information rather than
      accepting a capture.
  - operation_id: listContactHistoryActors
    intent: Return available actor choices for filtering contact revisions.
    access: console
    permission: crm.contacts.history (also evaluates crm.contacts.read)
    realises:
      - CAP-CRM-ENT-016
    published_to_submissions: >-
      not published: retrieves or renders existing information rather than
      accepting a capture.
  - operation_id: getContactRevision
    intent: Retrieve the field and child changes captured in one contact revision.
    access: console
    permission: crm.contacts.history (also evaluates crm.contacts.read)
    realises:
      - CAP-CRM-ENT-016
    published_to_submissions: >-
      not published: retrieves or renders existing information rather than
      accepting a capture.
  - operation_id: listMergedContactHistory
    intent: >-
      Read a merged-away person’s retained revision list through the surviving
      contact.
    access: console
    permission: crm.contacts.history (also evaluates crm.contacts.read)
    realises:
      - CAP-CRM-ENT-016
    published_to_submissions: >-
      not published: retrieves or renders existing information rather than
      accepting a capture.
  - operation_id: listMergedContactHistoryActors
    intent: >-
      Retrieve history actor choices for a merged-away contact through its
      survivor.
    access: console
    permission: crm.contacts.history (also evaluates crm.contacts.read)
    realises:
      - CAP-CRM-ENT-016
    published_to_submissions: >-
      not published: retrieves or renders existing information rather than
      accepting a capture.
  - operation_id: getMergedContactRevision
    intent: >-
      Read a selected revision of a merged-away contact through the surviving
      record.
    access: console
    permission: crm.contacts.history (also evaluates crm.contacts.read)
    realises:
      - CAP-CRM-ENT-016
    published_to_submissions: >-
      not published: retrieves or renders existing information rather than
      accepting a capture.
  - operation_id: lockContact
    intent: >-
      Apply the requested customer lock with a reason and current contact
      version.
    access: console
    permission: crm.contacts.lock
    realises:
      - CAP-CRM-ENT-015
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: unlockContact
    intent: >-
      Release the contact lock and selected or all element locks with audited
      reason and current version.
    access: console
    permission: crm.contacts.unlock
    realises:
      - CAP-CRM-ENT-015
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: listContactLockChanges
    intent: >-
      Retrieve the contact’s customer-lock audit entries, including actor and
      reason.
    access: console
    permission: crm.contacts.read
    realises:
      - CAP-CRM-ENT-015
    published_to_submissions: >-
      not published: retrieves or renders existing information rather than
      accepting a capture.
  - operation_id: createOrganization
    intent: >-
      Persist the organization aggregate and its initial calculated compliance
      in one transaction.
    access: console
    permission: >-
      crm.organizations.create (also evaluates
      crm.organizations.create-despite-duplicate-block)
    realises:
      - CAP-CRM-ENT-002
      - CAP-CRM-ENT-008
      - CAP-CRM-ENT-012
    published_to_submissions: >-
      not published: staff creation has no designed intake process or capture
      binding.
  - operation_id: listOrganizations
    intent: >-
      Retrieve a number-ordered organization page with supported search and
      archive options.
    access: console
    permission: crm.organizations.read
    realises:
      - CAP-CRM-ENT-003
      - CAP-CRM-ENT-013
    published_to_submissions: >-
      not published: retrieves or renders existing information rather than
      accepting a capture.
  - operation_id: getOrganization
    intent: >-
      Read the organization, owned details, derived hierarchy and stored
      compliance.
    access: console
    permission: crm.organizations.read
    realises:
      - CAP-CRM-ENT-002
      - CAP-CRM-ENT-003
      - CAP-CRM-ENT-012
    published_to_submissions: >-
      not published: retrieves or renders existing information rather than
      accepting a capture.
  - operation_id: listOrganizationAffiliations
    intent: >-
      Return dated organizational relationships with type and role filters,
      pagination and head count.
    access: console
    permission: crm.contacts.read
    realises:
      - CAP-CRM-ENT-009
      - CAP-CRM-ENT-010
      - CAP-CRM-ENT-003
    published_to_submissions: >-
      not published: retrieves or renders existing information rather than
      accepting a capture.
  - operation_id: updateOrganization
    intent: >-
      Replace editable organization aggregate values at the submitted read
      version.
    access: console
    permission: crm.organizations.update (also evaluates crm.organizations.unlock)
    realises:
      - CAP-CRM-ENT-002
      - CAP-CRM-ENT-004
      - CAP-CRM-ENT-005
      - CAP-CRM-ENT-006
      - CAP-CRM-ENT-008
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: patchOrganization
    intent: >-
      Apply a version-tested patch to selected organization fields and
      collections.
    access: console
    permission: crm.organizations.update (also evaluates crm.organizations.unlock)
    realises:
      - CAP-CRM-ENT-002
      - CAP-CRM-ENT-004
      - CAP-CRM-ENT-005
      - CAP-CRM-ENT-006
      - CAP-CRM-ENT-008
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: reprocessCompliance
    intent: Create a compliance scan or return the workspace’s existing running scan.
    access: console
    permission: crm.organizations.update
    realises:
      - CAP-CRM-ENT-012
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: previewContactRoleComplianceEmail
    intent: >-
      Test-send a contact-specific compliance message to the requested address
      using saved or supplied content.
    access: console
    permission: crm.organizations.update
    realises:
      - CAP-CRM-ENT-012
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: archiveOrganization
    intent: >-
      Archive the organization at its current version without discarding
      affiliations or status.
    access: console
    permission: crm.organizations.archive
    realises:
      - CAP-CRM-ENT-013
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: unarchiveOrganization
    intent: >-
      Remove organization archive metadata while retaining its previous status
      and relationships.
    access: console
    permission: crm.organizations.archive
    realises:
      - CAP-CRM-ENT-013
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: deleteOrganization
    intent: >-
      Delete an unprotected, unlocked organization free of child organizations
      and affiliation references, retaining history.
    access: console
    permission: crm.organizations.delete
    realises:
      - CAP-CRM-ENT-014
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: listOrganizationHistory
    intent: Retrieve the organization’s filtered recent-first revision page.
    access: console
    permission: crm.organizations.history (also evaluates crm.organizations.read)
    realises:
      - CAP-CRM-ENT-016
    published_to_submissions: >-
      not published: retrieves or renders existing information rather than
      accepting a capture.
  - operation_id: listOrganizationHistoryActors
    intent: Return actor choices present in the organization’s revisions.
    access: console
    permission: crm.organizations.history (also evaluates crm.organizations.read)
    realises:
      - CAP-CRM-ENT-016
    published_to_submissions: >-
      not published: retrieves or renders existing information rather than
      accepting a capture.
  - operation_id: getOrganizationRevision
    intent: Retrieve one organization revision and its captured changes.
    access: console
    permission: crm.organizations.history (also evaluates crm.organizations.read)
    realises:
      - CAP-CRM-ENT-016
    published_to_submissions: >-
      not published: retrieves or renders existing information rather than
      accepting a capture.
  - operation_id: lockOrganization
    intent: >-
      Set an organization customer lock using current version and audited
      reason.
    access: console
    permission: crm.organizations.lock
    realises:
      - CAP-CRM-ENT-015
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: unlockOrganization
    intent: >-
      Release organization or owned element locks through the versioned audit
      action.
    access: console
    permission: crm.organizations.unlock
    realises:
      - CAP-CRM-ENT-015
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: listOrganizationLockChanges
    intent: >-
      Retrieve organization lock changes with their actors, times and
      explanations.
    access: console
    permission: crm.organizations.read
    realises:
      - CAP-CRM-ENT-015
    published_to_submissions: >-
      not published: retrieves or renders existing information rather than
      accepting a capture.
  - operation_id: getCrmSettings
    intent: >-
      Read permitted sections of workspace form, layout and affiliation
      defaults.
    access: console
    permission: >-
      any of crm.contact-layouts.read, crm.organization-layouts.read,
      crm.affiliation-types.read; each section or kind is checked by the handler
    realises:
      - CAP-CRM-ENT-019
      - CAP-CRM-ENT-017
    published_to_submissions: >-
      not published: retrieves or renders existing information rather than
      accepting a capture.
  - operation_id: patchCrmSettings
    intent: >-
      Change only authorized settings sections using the settings version
      precondition.
    access: console
    permission: >-
      any of crm.contact-layouts.update, crm.organization-layouts.update,
      crm.affiliation-types.set-default; each section or kind is checked by the
      handler
    realises:
      - CAP-CRM-ENT-019
      - CAP-CRM-ENT-017
    published_to_submissions: >-
      not published: performs authorized record maintenance or operational work
      outside a capture process.
  - operation_id: getEffectiveDefinitions
    intent: >-
      Resolve permitted contact or organization form and layout definitions
      after defaults and overrides.
    access: console
    permission: >-
      any of crm.contacts.read, crm.organizations.read; each section or kind is
      checked by the handler
    realises:
      - CAP-CRM-ENT-019
    published_to_submissions: >-
      not published: retrieves or renders existing information rather than
      accepting a capture.
  - operation_id: renderEmailPreview
    intent: >-
      Render supplied compliance notice content against an authorized
      organization and contact.
    access: console
    permission: >-
      crm.organization-types.update (also evaluates crm.organizations.read,
      crm.contacts.read)
    realises:
      - CAP-CRM-ENT-012
    published_to_submissions: >-
      not published: retrieves or renders existing information rather than
      accepting a capture.
events:
  - name: contact change event
    direction: produced
    when: >-
      Capture a committed contact aggregate insertion, update or deletion,
      including changed owned rows.
    payload_summary: >-
      The owner and changed child snapshots include before and after values,
      changed collections and actor, source and correlation context.
  - name: organization change event
    direction: produced
    when: >-
      Capture a committed organization aggregate change, including contact
      points, custom values and compliance maintenance.
    payload_summary: >-
      Organization owner and child snapshots use the same aggregate capture
      envelope as contact changes.
  - name: compliance reprocess change event
    direction: produced
    when: >-
      Capture creation of a compliance scan and later changes to its
      continuation counter.
    payload_summary: >-
      Before-and-after run values let insertion or continuation events request a
      worker; other updates do not start work.
  - name: contact change event (organization-compliance-recalculation)
    direction: consumed
    when: >-
      React when a contact event changes affiliations or roles, or deletes the
      contact.
    payload_summary: >-
      Before-and-after references select organizations for recalculation from
      current committed relationships.
  - name: contact and organization change events (quick-search-indexer)
    direction: consumed
    when: Forward every contact and organization event without a field filter.
    payload_summary: >-
      Captured snapshots go to Platform in batches of at most 100, with an
      outcome required for each event.
  - name: compliance reprocess change event (compliance-reprocess-runner)
    direction: consumed
    when: >-
      Start or continue a worker for a newly recorded run or changed
      continuation counter.
    payload_summary: >-
      The run identity selects persisted cursor and counts for resumable running
      work.
  - name: crm.organizations_out_of_compliance attention item
    direction: produced
    when: >-
      Publish or resolve compliance attention during record work and republish
      it during the enabled daily sweep.
    payload_summary: >-
      The advisory item includes organization and violation details; its since
      value uses modification time rather than a stored transition time.
jobs:
  - >-
    The five-minute job reconciler attempts automatic-role settlement for
    affiliations due in each workspace calendar.
  - >-
    The job reconciler also attempts date-window settlement when the deployment
    switch is enabled; pinned defaults enable development and disable
    production.
  - >-
    The 15-minute asset sweep and its queue consumer remove abandoned prepared
    copies or retire unreferenced record files.
  - >-
    The daily attention sweep republishes noncompliant organizations only while
    attention publication is enabled.
  - >-
    Compliance scans continue through their own change-event messages rather
    than a scheduled scan job.
integrations:
  - system: Platform communications
    direction: outbound
    contract: >-
      Write bounded request objects and insert-only communication submission
      pointers with the compliance transaction. Test sends invoke Platform
      directly.
  - system: Platform communication templates
    direction: outbound
    contract: >-
      Use IAM-owned-template save and clear operations for notice content; the
      type read hydrates template content from Platform.
  - system: Platform Quick Search
    direction: outbound
    contract: >-
      Invoke the configured indexer with a versioned stream-and-event envelope
      and require per-event outcomes.
  - system: Platform attention
    direction: outbound
    contract: >-
      Publish attention inside the business transaction only when its deployment
      flag enables it.
  - system: Platform associations
    direction: outbound
    contract: >-
      Call the published endpoint-deletion function inside record deletion only
      when owner policy is enabled.
  - system: Platform files and number seeds
    direction: outbound
    contract: >-
      Prepare permanent file copies before record persistence and attach them
      transactionally. Allocate record numbers in a separate short transaction.
  - system: 'Platform metadata, layouts and security'
    direction: inbound
    contract: >-
      Read custom definitions, presentation definitions, actor labels and signed
      security context. Contact and organization registrations declare their
      supported capabilities.
  - system: CRM Data Quality
    direction: outbound
    contract: >-
      Evaluate duplicate creation blocks before persistence and capture
      authorized override audit with the new aggregate.
consistency:
  - rule: >-
      The contact aggregate, including relationships and custom values, commits
      together with one changed-owner version step and revision.
    kind: transactional
    compensation: A failed aggregate transaction rolls back all its database writes.
  - rule: >-
      Organization creation commits owner, child and custom data with initial
      compliance.
    kind: transactional
    compensation: Transactional failure rolls back the organization aggregate.
  - rule: >-
      File preparation precedes the record transaction, and attachment occurs
      within it.
    kind: eventual
    compensation: >-
      Prepared copies from failed saves remain cleanup work for the bounded
      sweep and consumer.
  - rule: >-
      Compliance follows contact reactions, date work and requested scans using
      committed relationships.
    kind: eventual
    compensation: >-
      Queue-driven failures retry and dead-letter; other entry points report or
      log their own failures. Old compliance remains until successful
      recalculation.
  - rule: >-
      Accepted compliance submission pointers commit with the status transition
      they describe.
    kind: transactional
    compensation: >-
      Rollback removes pointers but can leave request objects until the 30-day
      storage expiry. Delivery remains independent.
  - rule: 'Search, region and duplicate projections react after record commit.'
    kind: eventual
    compensation: >-
      Failed queue handlers retry independently and can dead-letter without
      undoing the saved record.
  - rule: >-
      Organization type persistence precedes mutation of its Platform-owned
      template.
    kind: eventual
    compensation: >-
      Attempt another CRM write to delete or restore on template failure; that
      compensation can fail and creates no classification history. Cleanup
      failure after deletion is logged.
  - rule: >-
      Organization source removal commits all resulting contact disconnections
      in its own transaction.
    kind: transactional
    compensation: >-
      Any transactional trigger failure rolls back source removal and affected
      contact changes.
evidence:
  - >-
    services-crm at 7ea48a41: modules/entities/aws/api/*.routes.ts (route
    declarations, expanded with tsx), aws/entities.module.ts,
    aws/logic/*.lambda.ts, aws/crm.service.ts (permission tiers),
    aws/crm.change-events.ts, aws/lambda/JOB_RECONCILER.ts, aws/crm.names.ts and
    aws/cdk/resources/affiliation-date-reconciliation.ts (switches),
    docs/change-events.md, docs/entities-api.md, modules/entities/services/**.
  - >-
    services-platform at ad3f6a65:
    docs/standards/submission-compatible-operations.md;
    docs/standards/record-write-contracts.md; docs/persistence.md;
    docs/queued-work.md; docs/jobs.md; docs/data-jobs; docs/record-history.md;
    docs/row-level-security.md; docs/indexing.md.
controls:
  - TC-AVL-004
  - TC-AZ-001
  - TC-AZ-002
  - TC-AZ-003
  - TC-AZ-004
  - TC-AZ-005
  - TC-AZ-006
  - TC-DAT-001
  - TC-DAT-002
  - TC-DAT-003
  - TC-DAT-004
  - TC-IAM-001
  - 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-002
  - TC-PI-001
  - TC-PI-002
  - TC-PI-003
  - TC-PI-004
  - TC-PI-005
  - TC-PRV-001
  - TC-PRV-002
  - TC-PRV-004
  - TC-TEN-001
  - TC-TEN-002
  - TC-TEN-003
  - TC-TEN-004
  - TC-VEN-001
  - TC-VEN-003
narrative:
  status: verified
  review: NARRATIVE-REVIEW-2026-10-23
narrative_sources:
  - section: What This Service Is For
    provenance: observed
    source: >-
      services-crm:modules/entities/aws/api/contacts.routes.ts;
      services-crm:modules/entities/aws/api/organizations.routes.ts;
      services-crm:modules/entities/aws/api/crm-settings.routes.ts;
      services-crm:modules/entities/aws/api/effective-definitions.routes.ts;
      services-crm:modules/entities/aws/api/email-previews.routes.ts
  - section: Boundary
    provenance: observed
    source: >-
      services-crm:modules/entities/aws/api/contacts.routes.ts;
      services-crm:modules/entities/aws/api/organizations.routes.ts;
      services-crm:modules/entities/aws/api/crm-settings.routes.ts;
      services-crm:modules/entities/aws/api/effective-definitions.routes.ts;
      services-crm:modules/entities/aws/api/email-previews.routes.ts
  - section: What It Owns
    provenance: observed
    source: >-
      services-crm:modules/entities/aws/api/contacts.routes.ts;
      services-crm:modules/entities/aws/api/organizations.routes.ts;
      services-crm:modules/entities/aws/api/crm-settings.routes.ts;
      services-crm:modules/entities/aws/api/effective-definitions.routes.ts;
      services-crm:modules/entities/aws/api/email-previews.routes.ts
  - section: Operations in Words
    provenance: observed
    source: >-
      services-crm:modules/entities/aws/api/contacts.routes.ts;
      services-crm:modules/entities/aws/api/organizations.routes.ts;
      services-crm:modules/entities/aws/api/crm-settings.routes.ts;
      services-crm:modules/entities/aws/api/effective-definitions.routes.ts;
      services-crm:modules/entities/aws/api/email-previews.routes.ts
  - section: Permissions
    provenance: documented
    source: 'rhythm-v2-docs:specs/crm/AUTHORIZATION.md#Role Entitlement Matrix'
  - section: Events and Jobs
    provenance: observed
    source: >-
      services-crm:modules/entities/aws/api/contacts.routes.ts;
      services-crm:modules/entities/aws/api/organizations.routes.ts;
      services-crm:modules/entities/aws/api/crm-settings.routes.ts;
      services-crm:modules/entities/aws/api/effective-definitions.routes.ts;
      services-crm:modules/entities/aws/api/email-previews.routes.ts
  - section: Consistency Guarantees
    provenance: observed
    source: >-
      services-crm:modules/entities/aws/api/contacts.routes.ts;
      services-crm:modules/entities/aws/api/organizations.routes.ts;
      services-crm:modules/entities/aws/api/crm-settings.routes.ts;
      services-crm:modules/entities/aws/api/effective-definitions.routes.ts;
      services-crm:modules/entities/aws/api/email-previews.routes.ts
  - section: What This Service Is For
    provenance: inferred
    source: >-
      services-crm at 7ea48a41: modules/entities/aws/api/*.routes.ts (route
      declarations, expanded with tsx), aws/entities.module.ts,
      aws/logic/*.lambda.ts, aws/crm.service.ts (permission tiers),
      aws/crm.change-events.ts, aws/lambda/JOB_RECONCILER.ts, aws/crm.names.ts
      and aws/cdk/resources/affiliation-date-reconciliation.ts (switches),
      docs/change-events.md, docs/entities-api.md,
      modules/entities/services/**.; services-platform at ad3f6a65:
      docs/standards/submission-compatible-operations.md;
      docs/standards/record-write-contracts.md; docs/persistence.md;
      docs/queued-work.md; docs/jobs.md; docs/data-jobs; docs/record-history.md;
      docs/row-level-security.md; docs/indexing.md.
    note: >-
      The explicitly labeled rationale interprets ownership and transaction
      boundaries; existing implementation gaps remain findings.
  - section: Consistency Guarantees
    provenance: inferred
    source: >-
      services-crm at 7ea48a41: modules/entities/aws/api/*.routes.ts (route
      declarations, expanded with tsx), aws/entities.module.ts,
      aws/logic/*.lambda.ts, aws/crm.service.ts (permission tiers),
      aws/crm.change-events.ts, aws/lambda/JOB_RECONCILER.ts, aws/crm.names.ts
      and aws/cdk/resources/affiliation-date-reconciliation.ts (switches),
      docs/change-events.md, docs/entities-api.md,
      modules/entities/services/**.; services-platform at ad3f6a65:
      docs/standards/submission-compatible-operations.md;
      docs/standards/record-write-contracts.md; docs/persistence.md;
      docs/queued-work.md; docs/jobs.md; docs/data-jobs; docs/record-history.md;
      docs/row-level-security.md; docs/indexing.md.
    note: >-
      The explicitly labeled rationale interprets ownership and transaction
      boundaries; existing implementation gaps remain findings.
---

# Entities Service Module

## What This Service Is For

Entities establishes and maintains the continuing identities other association work depends on. Staff create people and organizations, correct their details, connect them through dated relationships and inspect what changes. A contact owns its affiliations and responsibilities, while an organization owns its own details and carries the calculated role assessment.

Our reading is that a shared identity boundary avoids separate applications inventing incompatible person or company records. The service provides guarded mutations, attributed history and committed events. It does not turn classification into membership entitlement, a successful save into immediate projection freshness or accepted notice submission into proven delivery.

## Boundary

The record API exposes console-channel operations plus a public health response. Signed workspace execution and CRM permission evaluation determine reach; a request body cannot choose a different authorized customer context. No operation here is published to Submissions under a versioned contract, so a local route is not evidence of a published cross-application API.

Data Quality owns duplicates, blocking and merge-survivor handling; Regions owns geographic assignment; Activities owns interaction records. Platform owns definitions, access, query infrastructure, search indexing, communication delivery, attention and file storage. Entities owns its aggregate changes and the transport boundaries declared below, not those consumers’ business policies.

## What It Owns

**Contact.** Entities owns the person aggregate: details, preferences, customer-defined values, affiliations and role periods commit through contact persistence. Editing a relationship from an organization still changes this owned person.

**Organization.** Entities owns organizational identity, hierarchy, details, custom values and its stored assessment. Readers receive ancestry derived from the current parent chain rather than a second stored hierarchy copy.

**Affiliation.** The contact owns the dated relationship and responsibilities. There is no independently versioned affiliation save that bypasses the person’s read version or aggregate restrictions.

**Record Protection and History.** Owner versions, archive metadata and locks control changes; database capture and read-only revision access explain committed differences. Workspace defaults are maintained through section-authorized settings. Callers cannot choose arbitrary historical revisions or safeguards as writable replacements for current records.

## Operations in Words

### getEntitiesHealth

Operations tooling calls this public route to inspect the API host’s availability and build identity. It requires no CRM record grant and returns no person aggregate. A successful health response establishes that boundary’s availability; it does not prove a dependent index, notification or scheduled reconciliation has completed.

Permission: none; the public response is limited to host health information. Access channel: public. The dispatcher enforces route authority independently of console visibility.

### createContact

The console create form or an authorized integration establishes a person with its points, preferences, custom values and relationships. Name and reference validation, lock-write restrictions and duplicate blocking can refuse the whole creation. A explained override needs its additional evaluated grant. A stable supplied identity and matching validated fingerprint can return an earlier successful contact instead of creating again.

Permission: `crm.contacts.create` (Administrator, Power User, Standard User); `crm.contacts.create-despite-duplicate-block` (Administrator, Power User). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### listContacts

Readers and person selectors request a number-ordered page with supported search, organization, selectable and archive options. Current organizational membership can include an effective secondary tie; the primary label answers a different question. The service rejects malformed paging or filter values and missing read authority rather than using the screen’s visible rows as authorization.

Permission: `crm.contacts.read` (Administrator, Power User, Standard User, Read Only). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### recursivelyListContactsForOrganizationId

Authorized staff ask for the unpaged current people of a body and its descendants. The query traverses organizational context and identifies each current person through effective affiliations, including secondary ties. It needs contact read authority and is not a restored portal listing. Query and identity problems remain service refusals rather than an empty permission-filtered roster.

Permission: `crm.contacts.read` (Administrator, Power User, Standard User, Read Only). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### getContact

Record readers load the person aggregate, including its details and relationships, with an optional field projection. A stored archive overlay does not itself forbid the direct read. Absent or merged identities are handled explicitly, and missing read authority cannot be bypassed by a direct URL or by asking for fewer projected fields.

Permission: `crm.contacts.read` (Administrator, Power User, Standard User, Read Only). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### getContactMailingAddress

Staff or authorized integrations ask for the postal destination applicable on the workspace day. The selector considers explicitly enabled eligible seasonal information before the ordinary preferred address. Missing contact authority or identity is refused; absence of a qualifying address is not evidence that a generic default postal location should be invented.

Permission: `crm.contacts.read` (Administrator, Power User, Standard User, Read Only). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### updateContact

Integrations replace editable contact values at the submitted read version. The current implementation clears some omitted editable fields and collections, retains omitted affiliations and applies custom-field replacement separately. That differs from the approved complete-payload requirement. Stale versions, archive, locks, invalid references and collection problems refuse the save; the evaluated unlock grant does not make arbitrary lock changes acceptable.

Permission: `crm.contacts.update` (Administrator, Power User, Standard User) is required for the save. The dispatcher also evaluates `crm.contacts.unlock` (Administrator, Power User) for supported lock-removal checks; it is not an additional unconditional update grant. Access channel: console.

### patchContact

The console editor, record actions or an integration submits JSON Patch anchored by the original version test. The route applies directives to the read record and validates the resulting aggregate. Missing tests, stale versions, archive, element restrictions and invalid relationship or detail results are refused. Current-version no-ops avoid capture, while a stale no-op still conflicts.

Permission: `crm.contacts.update` (Administrator, Power User, Standard User) is required for the save. The dispatcher also evaluates `crm.contacts.unlock` (Administrator, Power User) for supported lock-removal checks; it is not an additional unconditional update grant. Access channel: console.

### deleteContact

A delete operator removes the live person and owned details and ties in one transaction, clears reciprocal spouse context and retains deletion history. Owner or element restrictions, Rhythm protection and enabled restrictive association policy can refuse it. The operation has no read-version argument; downstream search and file retirement follow their separate recovery boundaries.

Permission: `crm.contacts.delete` (Administrator, Power User). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### reconcileAffiliationDates

An authorized bulk operator requests a bounded date pass or a dry run. The response reports actual role, link and compliance work plus the window outcome. Missing bulk authority and invalid simulation inputs are refused. Dry run rolls database changes back but acquires real locks and can leave external notice objects; its result is not proof of a purely read-only inspection.

Permission: `crm.contacts.bulk` (Administrator, Power User). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### archiveContact

An archive operator sets the record aside with a current version and optional reason. Existing business status and owned information remain, while ordinary selection and correction become restricted. A stale version, full lock or invalid reason refuses the action. The resulting metadata and revision explain preservation separately from hard deletion.

Permission: `crm.contacts.archive` (Administrator, Power User). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### unarchiveContact

An archive operator restores ordinary availability while retaining the record’s stored business status and relationships. The request compares its read version and can retain an optional explanation. A stale version or full lock refuses restoration. The action changes the archive overlay and capture, and does not recreate a deleted identity or assign active status automatically.

Permission: `crm.contacts.archive` (Administrator, Power User). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### verifyContactAddress

A contact editor resolves one still-disconnected address using the current contact version. Keeping the snapshot makes it an owned value and clears disconnection provenance. The action refuses a row already resolved, stale version, archived or restricted contact and invalid target ownership. The console currently reaches related behavior through a general collection patch.

Permission: `crm.contacts.update` (Administrator, Power User, Standard User). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### relinkContactAddress

A contact editor resolves one still-disconnected address using the current contact version. Relinking chooses one valid source belonging to an effective affiliation and clears the old disconnection provenance. The action refuses a row already resolved, stale version, archived or restricted contact and invalid target ownership. The console currently reaches related behavior through a general collection patch.

Permission: `crm.contacts.update` (Administrator, Power User, Standard User). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### removeContactAddress

A contact editor resolves one still-disconnected address using the current contact version. Removal deletes that contact detail rather than its organization source. The action refuses a row already resolved, stale version, archived or restricted contact and invalid target ownership. The console currently reaches related behavior through a general collection patch.

Permission: `crm.contacts.update` (Administrator, Power User, Standard User). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### verifyContactPhoneNumber

A contact editor resolves one still-disconnected phone using the current contact version. Keeping the snapshot makes it an owned value and clears disconnection provenance. The action refuses a row already resolved, stale version, archived or restricted contact and invalid target ownership. The console currently reaches related behavior through a general collection patch.

Permission: `crm.contacts.update` (Administrator, Power User, Standard User). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### relinkContactPhoneNumber

A contact editor resolves one still-disconnected phone using the current contact version. Relinking chooses one valid source belonging to an effective affiliation and clears the old disconnection provenance. The action refuses a row already resolved, stale version, archived or restricted contact and invalid target ownership. The console currently reaches related behavior through a general collection patch.

Permission: `crm.contacts.update` (Administrator, Power User, Standard User). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### removeContactPhoneNumber

A contact editor resolves one still-disconnected phone using the current contact version. Removal deletes that contact detail rather than its organization source. The action refuses a row already resolved, stale version, archived or restricted contact and invalid target ownership. The console currently reaches related behavior through a general collection patch.

Permission: `crm.contacts.update` (Administrator, Power User, Standard User). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### listContactHistory

A history reader requests recent-first revisions, optionally restricted by actor, source, operation and time. Pagination supplies a continuation rather than the whole change history at once. The route requires history and the applicable record-read authority and refuses invalid filter or subject requests. Retained deletion evidence is distinct from requiring the live owner to exist.

Permission: `crm.contacts.history` (Administrator, Power User, Standard User, Read Only); `crm.contacts.read` (Administrator, Power User, Standard User, Read Only). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### listContactHistoryActors

A record-history reader loads available actor choices for filtering this subject’s revisions. The choices come from captured history rather than an editable user catalogue. History authority and the corresponding record-read check protect the result; invalid subject or access requests are refused, and the operation does not grant current-user administration.

Permission: `crm.contacts.history` (Administrator, Power User, Standard User, Read Only); `crm.contacts.read` (Administrator, Power User, Standard User, Read Only). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### getContactRevision

A history reader retrieves one captured revision and its field or owned-child differences. Historical labels and attribution explain the committed transaction rather than a proposed draft. The route requires history and the applicable record-read authority and refuses absent or inaccessible revision context. This is read-only inspection, with no restore or rollback action.

Permission: `crm.contacts.history` (Administrator, Power User, Standard User, Read Only); `crm.contacts.read` (Administrator, Power User, Standard User, Read Only). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### listMergedContactHistory

A history reader loads the loser’s retained revision page through the surviving contact. Filters describe captured changes, not a recreation of the removed person. History and applicable survivor read reach remain required; absent or invalid merge context and unauthorized access are refused rather than treating an old identity as unrestricted history access.

Permission: `crm.contacts.history` (Administrator, Power User, Standard User, Read Only); `crm.contacts.read` (Administrator, Power User, Standard User, Read Only). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### listMergedContactHistoryActors

A history reader requests actor filter choices from a merged-away person’s retained history through the survivor. The operation checks applicable history and record-read reach rather than resurrecting the loser. Missing survivor access, absent merge context or invalid parameters are refused, so knowing the loser’s old identity cannot bypass the surviving record’s authority.

Permission: `crm.contacts.history` (Administrator, Power User, Standard User, Read Only); `crm.contacts.read` (Administrator, Power User, Standard User, Read Only). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### getMergedContactRevision

A history reader inspects one captured revision of a merged-away person through the surviving contact. Historical changes remain evidence about the original subject, not writable values of the survivor. The route checks history and survivor read reach and refuses an inaccessible or absent revision or invalid merge context.

Permission: `crm.contacts.history` (Administrator, Power User, Standard User, Read Only); `crm.contacts.read` (Administrator, Power User, Standard User, Read Only). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### lockContact

An authorized lock operator sets or strengthens the customer restriction with a current read version and required explanation. The dedicated operation can change a full customer lock, but cannot change Rhythm protection. Invalid reasons, lock values, absent subjects or stale versions are refused. The committed audit records actual changed restrictions rather than every unchanged request.

Permission: `crm.contacts.lock` (Administrator, Power User). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### unlockContact

An authorized unlock operator releases the owner restriction and supported selected or all element restrictions. The request names the current version and a nonblank reason. Stale reads, invalid element ownership and Rhythm protection refuse the action without partial release. Successful changed restrictions are audited; requesting an unchanged owner can still matter when elements are released.

Permission: `crm.contacts.unlock` (Administrator, Power User). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### listContactLockChanges

A reader inspects the subject’s lock audit with actor, time, explanation and before-and-after restrictions. These entries explain dedicated safeguard changes rather than all ordinary profile corrections. The corresponding read grant is required; malformed paging or absent subject context can be refused. Reading an audit does not grant lock or unlock authority.

Permission: `crm.contacts.read` (Administrator, Power User, Standard User, Read Only). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### createOrganization

The configured console form or an authorized integration establishes an organization, its children and initial compliance assessment. Required name, parent-cycle, reference, detail or duplicate validation can refuse it. A separate evaluated grant and reason control a duplicate-block override. Initial compliance does not send a transition notice, and this creation lacks the contact fingerprint replay guarantee.

Permission: `crm.organizations.create` (Administrator, Power User, Standard User); `crm.organizations.create-despite-duplicate-block` (Administrator, Power User). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### listOrganizations

Readers and organization selectors request a number-ordered page with the supported search and archive options. Default availability and explicit archive filtering differ. Invalid query arguments or missing read authority are refused; a page is a partial list with its continuation cursor, rather than proof that the returned bodies are the entire workspace catalogue.

Permission: `crm.organizations.read` (Administrator, Power User, Standard User, Read Only). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### getOrganization

Record readers load organizational details, read-derived ancestry and the stored compliance result. The assessment reflects its last successful evaluation rather than an immediate calculation for every request. Absent or merged identities and missing read authority are explicit refusals. A direct archived read retains the organization context needed by past affiliations.

Permission: `crm.organizations.read` (Administrator, Power User, Standard User, Read Only). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### listOrganizationAffiliations

The organization Contacts tab and authorized callers request dated relationship rows with current, past, all or as-of interpretation and supported type and role filters. The operation returns relationship pagination and head count. It checks contact read authority, so organization read alone does not authorize this people view; malformed dates or filters are refused.

Permission: `crm.contacts.read` (Administrator, Power User, Standard User, Read Only). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### updateOrganization

Integrations replace editable organization data against a submitted read version. The service validates the whole resulting owner and children and refuses stale reads, archive, full locks, hierarchy cycles and invalid references or details. Ordinary replacement does not directly set compliance values or trigger immediate type-policy reevaluation; the approved complete-payload gap remains visible.

Permission: `crm.organizations.update` (Administrator, Power User, Standard User) is required for the save. The dispatcher also evaluates `crm.organizations.unlock` (Administrator, Power User) for supported lock-removal checks; it is not an additional unconditional update grant. Access channel: console.

### patchOrganization

Built type actions and integrations patch selected organization values with a version test. The result passes aggregate validation before persistence. A newer compliance or record change can make the request stale, and archived or fully restricted subjects are refused. Echoed computed assessment is not authority to overwrite the evaluator’s stored result.

Permission: `crm.organizations.update` (Administrator, Power User, Standard User) is required for the save. The dispatcher also evaluates `crm.organizations.unlock` (Administrator, Power User) for supported lock-removal checks; it is not an additional unconditional update grant. Access channel: console.

### reprocessCompliance

An organization updater requests a durable scan, commonly after changing policy. The service creates a run or returns the workspace’s existing running run and answers promptly with request information. Missing authority or persistence failure refuses the request. Later workers own batch progress and final status, and a failed terminal run requires another request rather than redrive-based reopening.

Permission: `crm.organizations.update` (Administrator, Power User, Standard User). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### previewContactRoleComplianceEmail

An organization updater test-sends a compliance message for selected records to a specified destination, using saved template content or a supplied draft. Schema validation requires a coherent choice of template or draft. Missing records and Platform configuration or destination refusals are returned at their own boundaries. This operation is a test send, distinct from the read-only render preview.

Permission: `crm.organizations.update` (Administrator, Power User, Standard User). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### archiveOrganization

An archive operator sets the record aside with a current version and optional reason. Existing business status and owned information remain, while ordinary selection and correction become restricted. A stale version, full lock or invalid reason refuses the action. The resulting metadata and revision explain preservation separately from hard deletion.

Permission: `crm.organizations.archive` (Administrator, Power User). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### unarchiveOrganization

An archive operator restores ordinary availability while retaining the record’s stored business status and relationships. The request compares its read version and can retain an optional explanation. A stale version or full lock refuses restoration. The action changes the archive overlay and capture, and does not recreate a deleted identity or assign active status automatically.

Permission: `crm.organizations.archive` (Administrator, Power User). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### deleteOrganization

A delete operator removes the organization and owned values only when no child or affiliation of any date still depends on it. Locks, Rhythm protection and enabled restrictive association policy also refuse deletion. The unversioned operation retains historical evidence and schedules separate file and projection recovery rather than promising instant removal from every dependent surface.

Permission: `crm.organizations.delete` (Administrator, Power User). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### listOrganizationHistory

A history reader requests recent-first revisions, optionally restricted by actor, source, operation and time. Pagination supplies a continuation rather than the whole change history at once. The route requires history and the applicable record-read authority and refuses invalid filter or subject requests. Retained deletion evidence is distinct from requiring the live owner to exist.

Permission: `crm.organizations.history` (Administrator, Power User, Standard User, Read Only); `crm.organizations.read` (Administrator, Power User, Standard User, Read Only). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### listOrganizationHistoryActors

A record-history reader loads available actor choices for filtering this subject’s revisions. The choices come from captured history rather than an editable user catalogue. History authority and the corresponding record-read check protect the result; invalid subject or access requests are refused, and the operation does not grant current-user administration.

Permission: `crm.organizations.history` (Administrator, Power User, Standard User, Read Only); `crm.organizations.read` (Administrator, Power User, Standard User, Read Only). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### getOrganizationRevision

A history reader retrieves one captured revision and its field or owned-child differences. Historical labels and attribution explain the committed transaction rather than a proposed draft. The route requires history and the applicable record-read authority and refuses absent or inaccessible revision context. This is read-only inspection, with no restore or rollback action.

Permission: `crm.organizations.history` (Administrator, Power User, Standard User, Read Only); `crm.organizations.read` (Administrator, Power User, Standard User, Read Only). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### lockOrganization

An authorized lock operator sets or strengthens the customer restriction with a current read version and required explanation. The dedicated operation can change a full customer lock, but cannot change Rhythm protection. Invalid reasons, lock values, absent subjects or stale versions are refused. The committed audit records actual changed restrictions rather than every unchanged request.

Permission: `crm.organizations.lock` (Administrator, Power User). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### unlockOrganization

An authorized unlock operator releases the owner restriction and supported selected or all element restrictions. The request names the current version and a nonblank reason. Stale reads, invalid element ownership and Rhythm protection refuse the action without partial release. Successful changed restrictions are audited; requesting an unchanged owner can still matter when elements are released.

Permission: `crm.organizations.unlock` (Administrator, Power User). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### listOrganizationLockChanges

A reader inspects the subject’s lock audit with actor, time, explanation and before-and-after restrictions. These entries explain dedicated safeguard changes rather than all ordinary profile corrections. The corresponding read grant is required; malformed paging or absent subject context can be refused. Reading an audit does not grant lock or unlock authority.

Permission: `crm.organizations.read` (Administrator, Power User, Standard User, Read Only). Access channel: console. The dispatcher enforces route authority independently of console visibility.

### getCrmSettings

Configuration readers retrieve only authorized workspace default sections. The route admits a supported read grant, then omits sections whose grants the caller lacks instead of turning withheld information into null. This preserves the distinction between an unset contact layout and one the caller cannot inspect. It is not a general record-read shortcut for configuration administration.

Permission: `crm.contact-layouts.read` (Administrator, Power User, Standard User, Read Only); `crm.organization-layouts.read` (Administrator, Power User, Standard User, Read Only); `crm.affiliation-types.read` (Administrator, Power User, Standard User, Read Only). Access channel: console. The handler checks the requested kind or section in addition to the route’s alternative-grant entry gate.

### patchCrmSettings

Configuration administrators submit selected sections with the settings read version. Each named section requires its own update or set-default grant before any write begins. An unauthorized section refuses the entire combined save, and a stale version conflicts. Unnamed sections remain intact, so independently configured contact, organization and affiliation defaults need not overwrite one another.

Permission: `crm.contact-layouts.update` (Administrator, Power User); `crm.organization-layouts.update` (Administrator, Power User); `crm.affiliation-types.set-default` (Administrator, Power User). Access channel: console. The handler checks the requested kind or section in addition to the route’s alternative-grant entry gate.

### getEffectiveDefinitions

Record consumers request the effective contact or organization presentation assignments for a type and workspace. Each slot resolves independently through type override and workspace fallback. The handler requires the named entity’s read grant; having contact read does not authorize organization definitions. Layout administration is unnecessary for using an already effective layout, and missing configuration remains unset.

Permission: `crm.contacts.read` (Administrator, Power User, Standard User, Read Only); `crm.organizations.read` (Administrator, Power User, Standard User, Read Only). Access channel: console. The handler checks the requested kind or section in addition to the route’s alternative-grant entry gate.

### renderEmailPreview

A notice-template administrator renders a supplied draft against the selected organization and contact. The route checks organization-type update plus both record read grants, because preview content incorporates those records. Invalid content, unreadable or absent subjects and rendering failures are refused. This operation stores no template and sends no message.

Permission: `crm.organization-types.update` (Administrator, Power User, Standard User); `crm.organizations.read` (Administrator, Power User, Standard User, Read Only); `crm.contacts.read` (Administrator, Power User, Standard User, Read Only). Access channel: console. The dispatcher enforces route authority independently of console visibility.

## Permissions

The CRM authorization model is the source of built-in grants. Administrator and Power User receive the administrative operations listed here; Standard User and Read Only receive narrower grants according to the table. A business Contact Role such as Billing Contact grants none of these security permissions, and Portal Member has no built-in CRM grants.

| Permission | What it protects | Built-in roles | Checked at |
|---|---|---|---|
| `crm.affiliation-types.read` | Read affiliation classifications. | Administrator, Power User, Standard User, Read Only | getCrmSettings |
| `crm.affiliation-types.set-default` | Choose the default kind of affiliation classifications. | Administrator, Power User | patchCrmSettings |
| `crm.contact-layouts.read` | Read contact presentation definitions. | Administrator, Power User, Standard User, Read Only | getCrmSettings |
| `crm.contact-layouts.update` | Maintain contact presentation definitions. | Administrator, Power User | patchCrmSettings |
| `crm.contacts.archive` | Archive and restore people. | Administrator, Power User | archiveContact, unarchiveContact |
| `crm.contacts.bulk` | Submit bulk work for people. | Administrator, Power User | reconcileAffiliationDates |
| `crm.contacts.create` | Create people. | Administrator, Power User, Standard User | createContact |
| `crm.contacts.create-despite-duplicate-block` | Create despite blocking duplicate evidence for people. | Administrator, Power User | createContact |
| `crm.contacts.delete` | Remove people. | Administrator, Power User | deleteContact |
| `crm.contacts.history` | Inspect past values of people. | Administrator, Power User, Standard User, Read Only | listContactHistory, listContactHistoryActors, getContactRevision, listMergedContactHistory, listMergedContactHistoryActors, getMergedContactRevision |
| `crm.contacts.lock` | Apply record locks to people. | Administrator, Power User | lockContact |
| `crm.contacts.read` | Read people. | Administrator, Power User, Standard User, Read Only | listContacts, recursivelyListContactsForOrganizationId, getContact, getContactMailingAddress, listContactHistory, listContactHistoryActors, getContactRevision, listMergedContactHistory, listMergedContactHistoryActors, getMergedContactRevision, listContactLockChanges, listOrganizationAffiliations, getEffectiveDefinitions, renderEmailPreview |
| `crm.contacts.unlock` | Release record locks on people. | Administrator, Power User | updateContact, patchContact, unlockContact |
| `crm.contacts.update` | Maintain people. | Administrator, Power User, Standard User | updateContact, patchContact, verifyContactAddress, relinkContactAddress, removeContactAddress, verifyContactPhoneNumber, relinkContactPhoneNumber, removeContactPhoneNumber |
| `crm.organization-layouts.read` | Read organization presentation definitions. | Administrator, Power User, Standard User, Read Only | getCrmSettings |
| `crm.organization-layouts.update` | Maintain organization presentation definitions. | Administrator, Power User | patchCrmSettings |
| `crm.organization-types.update` | Maintain organization classifications. | Administrator, Power User, Standard User | renderEmailPreview |
| `crm.organizations.archive` | Archive and restore organizations. | Administrator, Power User | archiveOrganization, unarchiveOrganization |
| `crm.organizations.create` | Create organizations. | Administrator, Power User, Standard User | createOrganization |
| `crm.organizations.create-despite-duplicate-block` | Create despite blocking duplicate evidence for organizations. | Administrator, Power User | createOrganization |
| `crm.organizations.delete` | Remove organizations. | Administrator, Power User | deleteOrganization |
| `crm.organizations.history` | Inspect past values of organizations. | Administrator, Power User, Standard User, Read Only | listOrganizationHistory, listOrganizationHistoryActors, getOrganizationRevision |
| `crm.organizations.lock` | Apply record locks to organizations. | Administrator, Power User | lockOrganization |
| `crm.organizations.read` | Read organizations. | Administrator, Power User, Standard User, Read Only | listOrganizations, getOrganization, listOrganizationHistory, listOrganizationHistoryActors, getOrganizationRevision, listOrganizationLockChanges, getEffectiveDefinitions, renderEmailPreview |
| `crm.organizations.unlock` | Release record locks on organizations. | Administrator, Power User | updateOrganization, patchOrganization, unlockOrganization |
| `crm.organizations.update` | Maintain organizations. | Administrator, Power User, Standard User | updateOrganization, patchOrganization, reprocessCompliance, previewContactRoleComplianceEmail |

A route permission is only the entry decision. Version comparison, archive, customer restrictions, Rhythm protection, valid references and dependencies can still refuse an authorized actor. History evaluates record-read authority as well as history authority. Settings and effective-definition handlers check the particular requested section or kind, preventing an alternative-grant route gate from exposing everything.

## Events and Jobs

**contact change event.** Capture a committed contact aggregate insertion, update or deletion, including changed owned rows. The owner and changed child snapshots include before and after values, changed collections and actor, source and correlation context.

**organization change event.** Capture a committed organization aggregate change, including contact points, custom values and compliance maintenance. Organization owner and child snapshots use the same aggregate capture envelope as contact changes.

**compliance reprocess change event.** Capture creation of a compliance scan and later changes to its continuation counter. Before-and-after run values let insertion or continuation events request a worker; other updates do not start work.

**contact change event (organization-compliance-recalculation).** React when a contact event changes affiliations or roles, or deletes the contact. Before-and-after references select organizations for recalculation from current committed relationships.

**contact and organization change events (quick-search-indexer).** Forward every contact and organization event without a field filter. Captured snapshots go to Platform in batches of at most 100, with an outcome required for each event.

**compliance reprocess change event (compliance-reprocess-runner).** Start or continue a worker for a newly recorded run or changed continuation counter. The run identity selects persisted cursor and counts for resumable running work.

**crm.organizations_out_of_compliance attention item.** Publish or resolve compliance attention during record work and republish it during the enabled daily sweep. The advisory item includes organization and violation details; its since value uses modification time rather than a stored transition time.

The five-minute job reconciler attempts automatic-role settlement for affiliations due in each workspace calendar.

The job reconciler also attempts date-window settlement when the deployment switch is enabled; pinned defaults enable development and disable production.

The 15-minute asset sweep and its queue consumer remove abandoned prepared copies or retire unreferenced record files.

The daily attention sweep republishes noncompliant organizations only while attention publication is enabled.

Compliance scans continue through their own change-event messages rather than a scheduled scan job.

These workers use deterministic logic and declare no inference operation. Queue acceptance, completed worker changes and communication delivery have separate evidence. Deployment switches in pinned configuration do not establish the present runtime state of every environment.

## Consistency Guarantees

The guarantees below describe the verified persistence and handoff boundaries. Transactional means the named local writes share a commit; eventual means later work can remain pending. Our reading is that this distinction is essential when interpreting an error or a saved record, because a rollback does not undo every prepared file or external request object.

**transactional boundary.** The contact aggregate, including relationships and custom values, commits together with one changed-owner version step and revision. Recovery: A failed aggregate transaction rolls back all its database writes.

**transactional boundary.** Organization creation commits owner, child and custom data with initial compliance. Recovery: Transactional failure rolls back the organization aggregate.

**eventual boundary.** File preparation precedes the record transaction, and attachment occurs within it. Recovery: Prepared copies from failed saves remain cleanup work for the bounded sweep and consumer.

**eventual boundary.** Compliance follows contact reactions, date work and requested scans using committed relationships. Recovery: Queue-driven failures retry and dead-letter; other entry points report or log their own failures. Old compliance remains until successful recalculation.

**transactional boundary.** Accepted compliance submission pointers commit with the status transition they describe. Recovery: Rollback removes pointers but can leave request objects until the 30-day storage expiry. Delivery remains independent.

**eventual boundary.** Search, region and duplicate projections react after record commit. Recovery: Failed queue handlers retry independently and can dead-letter without undoing the saved record.

**eventual boundary.** Organization type persistence precedes mutation of its Platform-owned template. Recovery: Attempt another CRM write to delete or restore on template failure; that compensation can fail and creates no classification history. Cleanup failure after deletion is logged.

**transactional boundary.** Organization source removal commits all resulting contact disconnections in its own transaction. Recovery: Any transactional trigger failure rolls back source removal and affected contact changes.
