---
id: DOM-CRM-ENT-033
title: Record Protection and History
realises: CON-CRM-ENT-033
product_area: crm
module: entities
revision: 1
status: draft
design_intent: proposed
implementation_evidence: observed
source_sha:
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
owned_by: SVC-CRM-ENT-entities
lifecycle: STATE-CRM-ENT-007
custom_fields: none
identity: >-
  Record protection is embedded in the contact, organization or catalogue record
  it governs. Lock audit rows and revisions have independent identities and
  identify their subject. Protected-record entries key workspace, record type
  and identity; CRM settings use one row per workspace.
attributes:
  - name: version
    kind: number
    required: true
    computed: true
    notes: >-
      Records begin at version 1. Writes that change owner or child data advance
      the owner version, including system changes.
    set_by: system
    rules: >-
      Records begin at version 1. Writes that change owner or child data advance
      the owner version, including system changes.
    example: 7
  - name: creator and modifier stamps
    kind: computed
    required: true
    computed: true
    notes: >-
      Creation and modification retain actor and time; reads resolve current
      actor display names through Platform.
    set_by: system
    rules: >-
      Creation and modification retain actor and time; reads resolve current
      actor display names through Platform.
    example: Coordinator on 2026-10-23
  - name: lock
    kind: enum
    required: false
    computed: false
    notes: >-
      Locks allow all actions, prohibit deletion, or prohibit updates and
      deletion. Contacts and organizations change locks through dedicated
      actions.
    set_by: staff
    rules: >-
      Locks allow all actions, prohibit deletion, or prohibit updates and
      deletion. Contacts and organizations change locks through dedicated
      actions.
    example: deletes
  - name: element locks
    kind: enum
    required: false
    computed: false
    notes: Contact points and affiliations may have additional element locks.
    set_by: staff
    rules: Contact points and affiliations may have additional element locks.
    example: Work address locked
  - name: lock change entry
    kind: collection
    required: true
    computed: true
    notes: >-
      Lock audit entries retain actor, time, a reason of at most 1,000
      characters, old and new locks and released elements.
    set_by: system
    rules: >-
      Lock audit entries retain actor, time, a reason of at most 1,000
      characters, old and new locks and released elements.
    example: 'Reason: verified by membership staff'
  - name: protected record
    kind: collection
    required: false
    computed: false
    notes: >-
      Operator or migration records distinguish Rhythm protection from customer
      locks. The pinned seed code inserts no protected records.
    set_by: system
    rules: >-
      Operator or migration records distinguish Rhythm protection from customer
      locks. The pinned seed code inserts no protected records.
    example: Rhythm-owned Other type
  - name: archive information
    kind: computed
    required: false
    computed: true
    notes: >-
      Archive and unarchive actions own timestamps, actors and reasons, each
      reason limited to 500 characters.
    set_by: system
    rules: >-
      Archive and unarchive actions own timestamps, actors and reasons, each
      reason limited to 500 characters.
    example: 'Archived: firm closed'
  - name: revision
    kind: collection
    required: true
    computed: true
    notes: >-
      Changed contact and organization transactions produce a revision with
      operation, field changes, historical labels and actor names, source,
      correlation and time.
    set_by: system
    rules: >-
      Changed contact and organization transactions produce a revision with
      operation, field changes, historical labels and actor names, source,
      correlation and time.
    example: Phone changed by Ruth’s coordinator
  - name: workspace default layouts and forms
    kind: reference
    required: false
    computed: false
    notes: >-
      Workspace defaults select contact and organization console and portal
      layouts and forms from Platform definitions.
    set_by: staff
    rules: >-
      Workspace defaults select contact and organization console and portal
      layouts and forms from Platform definitions.
    example: Workspace contact edit form
  - name: workspace default affiliation type
    kind: reference
    required: false
    computed: false
    notes: >-
      The Organization command and Add Person use this affiliation type; the
      initial default is Other.
    set_by: staff
    rules: >-
      The Organization command and Add Person use this affiliation type; the
      initial default is Other.
    example: Other
  - name: dates reconciled through
    kind: date
    required: false
    computed: true
    notes: >-
      The compliance date-window cursor advances monotonically. It does not
      prove that bounded pending link work is exhausted.
    set_by: system
    rules: >-
      The compliance date-window cursor advances monotonically. It does not
      prove that bounded pending link work is exhausted.
    example: '2026-10-22'
  - name: settings version
    kind: number
    required: true
    computed: true
    notes: The settings version remains 0 until the first persisted settings write.
    set_by: system
    rules: The settings version remains 0 until the first persisted settings write.
    example: 2
  - name: Creation / actor
    kind: text
    required: false
    computed: true
    set_by: system
    rules: System identity attributed to original creation.
    example: Association coordinator
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Creation / time
    kind: text
    required: false
    computed: true
    set_by: system
    rules: 'Creation timestamp, distinct from a revision’s capture time.'
    example: '2026-10-23T14:00:00Z'
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Modification / actor
    kind: text
    required: false
    computed: true
    set_by: system
    rules: System identity attributed to the latest saved modification.
    example: CRM System
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Modification / time
    kind: text
    required: false
    computed: true
    set_by: system
    rules: Timestamp of the latest saved modification.
    example: '2026-10-23T14:30:00Z'
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Archive / time
    kind: text
    required: false
    computed: true
    set_by: system
    rules: Timestamp written by the dedicated archive action.
    example: '2026-10-23T14:00:00Z'
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Archive / actor
    kind: text
    required: false
    computed: true
    set_by: system
    rules: Identity responsible for archive.
    example: Association administrator
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Archive / reason
    kind: text
    required: false
    computed: true
    set_by: system
    rules: 'Dedicated action explanation, limited to 500 characters.'
    example: Organization closed
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Unarchive / reason
    kind: text
    required: false
    computed: true
    set_by: system
    rules: 'Dedicated action explanation, limited to 500 characters.'
    example: Closure was recorded in error
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Lock change / identity
    kind: text
    required: false
    computed: true
    set_by: system
    rules: Append-only audit identity retained after ordinary subject deletion.
    example: Recorded lock action
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Lock change / record type
    kind: text
    required: false
    computed: true
    set_by: system
    rules: Subject kind whose customer lock changes.
    example: crm.contacts
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Lock change / record identity
    kind: text
    required: false
    computed: true
    set_by: system
    rules: 'The subject identity, rather than a cascading live-owner reference.'
    example: Ruth’s contact
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Lock change / operation
    kind: text
    required: false
    computed: true
    set_by: system
    rules: Locked or unlocked audit action.
    example: unlocked
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Lock change / before
    kind: text
    required: false
    computed: true
    set_by: system
    rules: Restriction before the action; null means none.
    example: deletes
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Lock change / after
    kind: text
    required: false
    computed: true
    set_by: system
    rules: Restriction after the action; null means released.
    example: None
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Lock change / reason
    kind: text
    required: false
    computed: true
    set_by: system
    rules: 'Required action explanation, limited to 1,000 characters.'
    example: Verified authority to correct the record
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Lock change / actor
    kind: text
    required: false
    computed: true
    set_by: system
    rules: Verified actor making the dedicated action.
    example: Association administrator
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Lock change / recorded time
    kind: text
    required: false
    computed: true
    set_by: system
    rules: The audit action timestamp.
    example: '2026-10-23T14:00:00Z'
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Revision / identity
    kind: text
    required: false
    computed: true
    set_by: system
    rules: Immutable identity used to retrieve the capture.
    example: Saved contact revision
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Revision / sequence
    kind: text
    required: false
    computed: true
    set_by: system
    rules: >-
      Database-generated order establishes the subject timeline under its write
      lock.
    example: '48'
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Revision / record label
    kind: text
    required: false
    computed: true
    set_by: system
    rules: >-
      Historical subject label retained where captured, including deleted
      subjects.
    example: Ruth Moreno
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Revision / operation
    kind: text
    required: false
    computed: true
    set_by: system
    rules: 'Creation, change or deletion identifies the kind of capture.'
    example: updated
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Revision / changes
    kind: text
    required: false
    computed: true
    set_by: system
    rules: Compact before-and-after difference document for owner and child changes.
    example: Work phone changed
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Revision / changed fields
    kind: text
    required: false
    computed: true
    set_by: system
    rules: Top-level fields and collection names support timeline filtering.
    example: Phone numbers
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Revision / capture format
    kind: text
    required: false
    computed: true
    set_by: system
    rules: >-
      Format discriminator allows readers to interpret the stored difference
      document.
    example: '1'
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Revision / transaction identity
    kind: text
    required: false
    computed: true
    set_by: system
    rules: Groups records that changed within the same save.
    example: Shared save transaction
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Revision / recorded time
    kind: text
    required: false
    computed: true
    set_by: system
    rules: >-
      Capture clock time near settlement, not a claimed database commit
      timestamp.
    example: '2026-10-23T14:00:00Z'
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Revision / transaction start
    kind: text
    required: false
    computed: true
    set_by: system
    rules: 'Transaction beginning time, separate from capture time.'
    example: '2026-10-23T13:59:59Z'
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Revision / actor identity and label
    kind: text
    required: false
    computed: true
    set_by: system
    rules: Verified actor and captured historical display name.
    example: Ruth’s coordinator
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Revision / source
    kind: text
    required: false
    computed: true
    set_by: system
    rules: >-
      Verified request, job or maintenance context; not client-chosen
      attribution.
    example: console
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Revision / operation name
    kind: text
    required: false
    computed: true
    set_by: system
    rules: Verified operation attribution for the write.
    example: patchContact
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Revision / correlation identity
    kind: text
    required: false
    computed: true
    set_by: system
    rules: Context linking related execution evidence.
    example: Save correlation
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Revision / request identity
    kind: text
    required: false
    computed: true
    set_by: system
    rules: Verified request context when applicable.
    example: Console save request
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Revision / job identity
    kind: text
    required: false
    computed: true
    set_by: system
    rules: >-
      Owning Data Job identity for a batch-produced capture; absent for ordinary
      requests.
    example: Import job
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: Revision / job batch identity
    kind: text
    required: false
    computed: true
    set_by: system
    rules: Batch context distinguishes the job transaction that made the change.
    example: Import batch 3
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - name: contact default / console_page_layout
    kind: reference
    required: false
    computed: false
    set_by: staff
    rules: >-
      Optional Platform definition reference for this independent workspace
      slot; type-specific assignments override this default.
    example: contact console page layout definition
    provenance: observed
    source: 'services-crm:modules/entities/db/crm-settings.table.ts'
  - name: contact default / console_create_form
    kind: reference
    required: false
    computed: false
    set_by: staff
    rules: >-
      Optional Platform definition reference for this independent workspace
      slot; type-specific assignments override this default.
    example: contact console create form definition
    provenance: observed
    source: 'services-crm:modules/entities/db/crm-settings.table.ts'
  - name: contact default / console_edit_form
    kind: reference
    required: false
    computed: false
    set_by: staff
    rules: >-
      Optional Platform definition reference for this independent workspace
      slot; type-specific assignments override this default.
    example: contact console edit form definition
    provenance: observed
    source: 'services-crm:modules/entities/db/crm-settings.table.ts'
  - name: contact default / portal_page_layout
    kind: reference
    required: false
    computed: false
    set_by: staff
    rules: >-
      Optional Platform definition reference for this independent workspace
      slot; type-specific assignments override this default.
    example: contact portal page layout definition
    provenance: observed
    source: 'services-crm:modules/entities/db/crm-settings.table.ts'
  - name: contact default / portal_form
    kind: reference
    required: false
    computed: false
    set_by: staff
    rules: >-
      Optional Platform definition reference for this independent workspace
      slot; type-specific assignments override this default.
    example: contact portal form definition
    provenance: observed
    source: 'services-crm:modules/entities/db/crm-settings.table.ts'
  - name: organization default / console_page_layout
    kind: reference
    required: false
    computed: false
    set_by: staff
    rules: >-
      Optional Platform definition reference for this independent workspace
      slot; type-specific assignments override this default.
    example: organization console page layout definition
    provenance: observed
    source: 'services-crm:modules/entities/db/crm-settings.table.ts'
  - name: organization default / console_create_form
    kind: reference
    required: false
    computed: false
    set_by: staff
    rules: >-
      Optional Platform definition reference for this independent workspace
      slot; type-specific assignments override this default.
    example: organization console create form definition
    provenance: observed
    source: 'services-crm:modules/entities/db/crm-settings.table.ts'
  - name: organization default / console_edit_form
    kind: reference
    required: false
    computed: false
    set_by: staff
    rules: >-
      Optional Platform definition reference for this independent workspace
      slot; type-specific assignments override this default.
    example: organization console edit form definition
    provenance: observed
    source: 'services-crm:modules/entities/db/crm-settings.table.ts'
  - name: organization default / portal_page_layout
    kind: reference
    required: false
    computed: false
    set_by: staff
    rules: >-
      Optional Platform definition reference for this independent workspace
      slot; type-specific assignments override this default.
    example: organization portal page layout definition
    provenance: observed
    source: 'services-crm:modules/entities/db/crm-settings.table.ts'
  - name: organization default / portal_form
    kind: reference
    required: false
    computed: false
    set_by: staff
    rules: >-
      Optional Platform definition reference for this independent workspace
      slot; type-specific assignments override this default.
    example: organization portal form definition
    provenance: observed
    source: 'services-crm:modules/entities/db/crm-settings.table.ts'
relationships:
  - to: DOM-CRM-ENT-001
    cardinality: >-
      one-to-one safeguards per contact; one-to-many revisions and lock change
      entries
    ownership: >-
      This model specifies the contact’s version, locks, archive information and
      revisions.
    cross_service: false
    on_archive_or_delete: >-
      Archive retains the subject and its history; ordinary contact deletion
      retains revisions.
  - to: DOM-CRM-ENT-002
    cardinality: >-
      one-to-one safeguards per organization; one-to-many revisions and lock
      change entries
    ownership: >-
      This model specifies the organization’s version, locks, archive
      information and revisions.
    cross_service: false
    on_archive_or_delete: >-
      Archive retains the subject and its history; ordinary organization
      deletion retains revisions.
  - to: DOM-CRM-ENT-003
    cardinality: one default affiliation type per workspace; element locks on affiliations
    ownership: >-
      Settings own the default selection; the affiliation catalogue owns the
      selected type.
    cross_service: false
    on_archive_or_delete: >-
      Default selection blocks type deletion; affiliation locks remain governed
      through contact operations.
invariants:
  - id: INV-DOM-CRM-ENT-033-01
    statement: >-
      Versioned update operations require the caller’s read version and reject a
      stale version with modifier attribution. Unversioned deletion is a
      separate operation.
    enforced_at:
      - api
      - service
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400
      naming sys_version to a patch that tests no version
    protects: Corrections made from current information rather than silent overwrites.
  - id: INV-DOM-CRM-ENT-033-02
    statement: >-
      An unchanged contact or organization save preserves version and
      modification stamps and emits neither revision nor change event.
    enforced_at:
      - service
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/record-saves.no-op.integration.spec.js#writes
      no revision
    protects: >-
      A meaningful history containing changes rather than every unchanged round
      trip.
  - id: INV-DOM-CRM-ENT-033-03
    statement: >-
      Failure inside the contact or organization save transaction rolls back
      owner data, children, custom values and affiliations together.
    enforced_at:
      - service
      - database
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#rolls
      back the contact row the transaction had already updated
    protects: All-or-nothing aggregate correction.
  - id: INV-DOM-CRM-ENT-033-04
    statement: >-
      A full customer lock blocks ordinary update and deletion; a delete lock
      blocks deletion. Dedicated unlock actions audit release, with a legacy
      contact-role write exception.
    enforced_at:
      - service
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 409
      IllegalOperation, not 403, for a replacement of a contact locked against
      updates, the lock sent back unchanged
    protects: Customer restrictions that ordinary writes cannot bypass.
  - id: INV-DOM-CRM-ENT-033-05
    statement: >-
      Dedicated lock actions advance the owner version when any lock changes and
      record each changed lock. Requesting the current owner lock is unchanged
      only if no element locks are released.
    enforced_at:
      - service
      - database
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/record-locks.integration.spec.js#leaves
      the role at its version and lock, and records nothing
    protects: Attribution for restrictions changed on owner or children.
  - id: INV-DOM-CRM-ENT-033-06
    statement: >-
      General contact and organization writes may echo the current lock but
      cannot change it.
    enforced_at:
      - api
      - service
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 403
      naming sys_locked for a replacement that sets a lock (E-14)
    protects: Separation between ordinary editing and audited lock administration.
  - id: INV-DOM-CRM-ENT-033-07
    statement: >-
      Customer permissions cannot unlock, lock or delete a Rhythm-protected
      record. Protection does not prevent edits to ordinary fields.
    enforced_at:
      - service
      - database
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/record-locks.integration.spec.js#refuses
      the unlock, naming the protection
    protects: Rhythm-owned configuration protected beyond customer grants.
  - id: INV-DOM-CRM-ENT-033-08
    statement: >-
      One changed contact or organization transaction produces one aggregate
      revision.
    enforced_at:
      - database
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.history.integration.spec.js#writes
      exactly one revision, whatever the save touched
    protects: One intelligible explanation of a multi-element record save.
  - id: INV-DOM-CRM-ENT-033-09
    statement: >-
      The application has read-only revision access; record deletion does not
      remove retained revisions.
    enforced_at:
      - database
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/db/record-revisions.table.integration.spec.js#may
      not insert a revision, so nothing the application runs can forge one
    protects: >-
      Retained evidence that cannot be forged or erased by ordinary application
      writes.
  - id: INV-DOM-CRM-ENT-033-10
    statement: >-
      A change missing required actor and source context fails along with
      revision capture.
    enforced_at:
      - database
    provenance: documented
    source: >-
      services-crm:docs/record-history.md#Fail closed, and the maintenance
      helper
    protects: Fail-closed attribution when the record-change context is missing.
  - id: INV-DOM-CRM-ENT-033-11
    statement: >-
      General saves, imports and data jobs preserve archive metadata; only
      archive actions write it.
    enforced_at:
      - service
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/persistence/contacts.archive.integration.spec.js#is
      kept through a plain save, which never writes it
    protects: >-
      Archive decisions that an unrelated import or save cannot silently
      reverse.
  - id: INV-DOM-CRM-ENT-033-12
    statement: >-
      Archive blocks ordinary saves and disconnected-detail actions. Deletion,
      merges and authorized system maintenance remain possible.
    enforced_at:
      - service
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/archive.e2e.spec.js#refuses a
      replacement, 409 RecordArchived
    protects: A consistent distinction between preserving a record and editing it.
  - id: INV-DOM-CRM-ENT-033-13
    statement: >-
      Settings reads filter unauthorized sections. A write containing any
      unauthorized section is rejected as a whole.
    enforced_at:
      - api
      - service
    provenance: test
    source: >-
      services-crm:modules/entities/tests/e2e/page-layouts.e2e.spec.js#writes
      NEITHER section for that refused save: the decision is made before
      anything is written
    protects: Settings isolation between differently authorized sections.
  - id: INV-DOM-CRM-ENT-033-14
    statement: >-
      The date watermark advances only after the organization compliance window
      is settled and never moves backwards.
    enforced_at:
      - service
    provenance: test
    source: >-
      services-crm:modules/entities/tests/integration/services/affiliations/affiliation-date-reconciliation.integration.spec.js#finds
      the day settled and changes nothing, the organization’s version included
    protects: >-
      A forward-only calendar cursor that does not claim the link backlog is
      exhausted.
physical:
  - record_revisions
  - record_lock_changes
  - protected_records
  - crm_settings
evidence:
  - >-
    services-crm at 7ea48a41: modules/entities/db/record-revisions.table.ts,
    record-lock-changes.table.ts, protected-records.table.ts,
    crm-settings.table.ts, archive-columns.ts, layout-slots.ts;
    services/persistence/record-locks.ts, record-revisions.ts, crm-settings.ts,
    date-reconciliations.ts; history/contact-history.config.ts,
    organization-history.config.ts; docs/record-locks.md,
    docs/record-history.md, docs/archive.md, docs/entities-api.md (Versions and
    no-op saves).
  - >-
    services-platform at ad3f6a65: docs/standards/record-write-contracts.md (W1,
    W2); docs/record-history.md; docs/row-level-security.md.
permissions:
  - action: contacts read
    permission: crm.contacts.read
    roles: [Administrator, Power User, Standard User, Read Only]
  - action: contacts create
    permission: crm.contacts.create
    roles: [Administrator, Power User, Standard User]
  - action: contacts update
    permission: crm.contacts.update
    roles: [Administrator, Power User, Standard User]
    by_state: >-
      Ordinary update refuses archived or fully locked records; archive actions
      also check full locks.
  - action: contacts archive
    permission: crm.contacts.archive
    roles: [Administrator, Power User]
    by_state: >-
      Ordinary update refuses archived or fully locked records; archive actions
      also check full locks.
  - action: contacts delete
    permission: crm.contacts.delete
    roles: [Administrator, Power User]
  - action: contacts lock
    permission: crm.contacts.lock
    roles: [Administrator, Power User]
  - action: contacts unlock
    permission: crm.contacts.unlock
    roles: [Administrator, Power User]
  - action: contacts history
    permission: crm.contacts.history
    roles: [Administrator, Power User, Standard User, Read Only]
  - action: organizations read
    permission: crm.organizations.read
    roles: [Administrator, Power User, Standard User, Read Only]
  - action: organizations create
    permission: crm.organizations.create
    roles: [Administrator, Power User, Standard User]
  - action: organizations update
    permission: crm.organizations.update
    roles: [Administrator, Power User, Standard User]
    by_state: >-
      Ordinary update refuses archived or fully locked records; archive actions
      also check full locks.
  - action: organizations archive
    permission: crm.organizations.archive
    roles: [Administrator, Power User]
    by_state: >-
      Ordinary update refuses archived or fully locked records; archive actions
      also check full locks.
  - action: organizations delete
    permission: crm.organizations.delete
    roles: [Administrator, Power User]
  - action: organizations lock
    permission: crm.organizations.lock
    roles: [Administrator, Power User]
  - action: organizations unlock
    permission: crm.organizations.unlock
    roles: [Administrator, Power User]
  - action: organizations history
    permission: crm.organizations.history
    roles: [Administrator, Power User, Standard User, Read Only]
  - action: contact-roles read
    permission: crm.contact-roles.read
    roles: [Administrator, Power User, Standard User, Read Only]
  - action: contact-roles lock
    permission: crm.contact-roles.lock
    roles: [Administrator, Power User]
  - action: contact-roles unlock
    permission: crm.contact-roles.unlock
    roles: [Administrator, Power User]
  - action: contact-layouts read
    permission: crm.contact-layouts.read
    roles: [Administrator, Power User, Standard User, Read Only]
  - action: contact-layouts update
    permission: crm.contact-layouts.update
    roles: [Administrator, Power User]
  - action: organization-layouts read
    permission: crm.organization-layouts.read
    roles: [Administrator, Power User, Standard User, Read Only]
  - action: organization-layouts update
    permission: crm.organization-layouts.update
    roles: [Administrator, Power User]
  - action: select default affiliation type
    permission: crm.affiliation-types.set-default
    roles: [Administrator, Power User]
narrative:
  status: verified
  review: NARRATIVE-REVIEW-2026-10-23
narrative_sources:
  - section: What It Models
    provenance: observed
    source: 'services-crm:modules/entities/services/persistence/record-locks.ts'
  - section: Identity and Attributes
    provenance: observed
    source: 'services-crm:modules/entities/services/persistence/record-locks.ts'
  - section: Relationships
    provenance: observed
    source: 'services-crm:modules/entities/services/persistence/record-locks.ts'
  - section: Lifecycle
    provenance: observed
    source: 'services-crm:modules/entities/services/persistence/record-locks.ts'
  - section: How It Is Stored and Shared
    provenance: observed
    source: 'services-crm:modules/entities/services/persistence/record-locks.ts'
  - section: Permissions
    provenance: documented
    source: 'rhythm-v2-docs:specs/crm/AUTHORIZATION.md#Permission Catalogue'
  - section: Invariants
    provenance: inferred
    source: 'services-crm:modules/entities/services/persistence/record-locks.ts'
    note: >-
      Protection explanations interpret the existing invariant rows and
      enforcement boundaries; no new product decision is claimed.
  - section: What It Models
    provenance: inferred
    source: 'services-crm:modules/entities/services/persistence/record-locks.ts'
    note: >-
      The explicitly labeled aggregate rationale follows the object boundary and
      its owned records.
  - section: Open Design Questions
    provenance: documented
    source: 'rhythm-v2-docs:specs/crm/entities/FINDINGS.md#Findings'
  - section: Identity and Attributes
    provenance: observed
    source: >-
      services-crm:modules/entities/db/record-revisions.table.ts;
      services-crm:modules/entities/db/record-lock-changes.table.ts;
      services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts
  - section: Invariants
    provenance: inferred
    source: "services-crm:modules/entities/services/persistence/contacts.ts; services-crm:modules/entities/services/persistence/organizations.ts; services-crm:modules/entities/services/organizations/compliance/organization-compliance.ts; services-crm:modules/entities/services/persistence/record-locks.ts"
    note: "The rationale interprets this artifact’s sourced invariants and linked capability rules; it does not claim a recorded product decision."
---

# Record Protection and History

## What It Models

Record Protection and History models the safeguards around a change, rather than another customer business identity. Staff encounter a version when saving, a lock when an action is restricted, archive metadata when a record is set aside and revisions when investigating what changed. These mechanisms apply to contact and organization aggregates and the supported catalogue records.

Our reading is that independent safeguards answer different operational questions without conflating permission, preservation and mutation. A reader may inspect an archived person, an editor may encounter a customer lock, and an administrator may still be unable to change Rhythm-protected configuration. Workspace settings are included here as an existing storage facet, while CRM Settings remains the planning concept explaining their business meaning.

## Identity and Attributes

Archive keeps its own time, actor and explanation on the live owner. Reopening clears those archive fields and retains the reopening reason. It does not add a separate pair of reopening time and actor fields to that owner: the attributed revision and ordinary modification stamps explain the changed transaction. Revision attribution describes the verified calling channel, such as Console or an integration, rather than an arbitrary API label supplied by the client.

Record protection is embedded in the contact, organization or catalogue record it governs. Lock audit rows and revisions have independent identities and identify their subject. Protected-record entries key workspace, record type and identity; CRM settings use one row per workspace.

The table describes logical properties. Optional values can be absent; a required collection member is distinct from requiring the collection itself. Examples illustrate association information and do not establish new defaults. Service-owned values remain separate from fields that staff or integrations supply.

| Property | Meaning and writer | Constraints | Example |
|---|---|---|---|
| version | Records begin at version 1. Writes that change owner or child data advance the owner version, including system changes. Set by system. | Records begin at version 1. Writes that change owner or child data advance the owner version, including system changes. | 7 |
| creator and modifier stamps | Creation and modification retain actor and time; reads resolve current actor display names through Platform. Set by system. | Creation and modification retain actor and time; reads resolve current actor display names through Platform. | Coordinator on 2026-10-23 |
| lock | Locks allow all actions, prohibit deletion, or prohibit updates and deletion. Contacts and organizations change locks through dedicated actions. Set by staff. | Locks allow all actions, prohibit deletion, or prohibit updates and deletion. Contacts and organizations change locks through dedicated actions. | deletes |
| element locks | Contact points and affiliations may have additional element locks. Set by staff. | Contact points and affiliations may have additional element locks. | Work address locked |
| lock change entry | Lock audit entries retain actor, time, a reason of at most 1,000 characters, old and new locks and released elements. Set by system. | Lock audit entries retain actor, time, a reason of at most 1,000 characters, old and new locks and released elements. | Reason: verified by membership staff |
| protected record | Operator or migration records distinguish Rhythm protection from customer locks. The pinned seed code inserts no protected records. Set by system. | Operator or migration records distinguish Rhythm protection from customer locks. The pinned seed code inserts no protected records. | Rhythm-owned Other type |
| archive information | Archive and unarchive actions own timestamps, actors and reasons, each reason limited to 500 characters. Set by system. | Archive and unarchive actions own timestamps, actors and reasons, each reason limited to 500 characters. | Archived: firm closed |
| revision | Changed contact and organization transactions produce a revision with operation, field changes, historical labels and actor names, source, correlation and time. Set by system. | Changed contact and organization transactions produce a revision with operation, field changes, historical labels and actor names, source, correlation and time. | Phone changed by Ruth’s coordinator |
| workspace default layouts and forms | Workspace defaults select contact and organization console and portal layouts and forms from Platform definitions. Set by staff. | Workspace defaults select contact and organization console and portal layouts and forms from Platform definitions. | Workspace contact edit form |
| workspace default affiliation type | The Organization command and Add Person use this affiliation type; the initial default is Other. Set by staff. | The Organization command and Add Person use this affiliation type; the initial default is Other. | Other |
| dates reconciled through | The compliance date-window cursor advances monotonically. It does not prove that bounded pending link work is exhausted. Set by system. | The compliance date-window cursor advances monotonically. It does not prove that bounded pending link work is exhausted. | 2026-10-22 |
| settings version | The settings version remains 0 until the first persisted settings write. Set by system. | The settings version remains 0 until the first persisted settings write. | 2 |

### Policy and audit properties

These properties belong to the catalogue or safeguard facet named in the property column. They explain the implementation’s exposed policy and audit context without allocating another domain object. System attribution is read-only; catalogue policy is administered through its own permitted operation and is not changed by an ordinary record edit.

| Property | Meaning | Example |
|---|---|---|
| Creation / actor | System identity attributed to original creation. | Association coordinator |
| Creation / time | Creation timestamp, distinct from a revision’s capture time. | 2026-10-23T14:00:00Z |
| Modification / actor | System identity attributed to the latest saved modification. | CRM System |
| Modification / time | Timestamp of the latest saved modification. | 2026-10-23T14:30:00Z |
| Archive / time | Timestamp written by the dedicated archive action. | 2026-10-23T14:00:00Z |
| Archive / actor | Identity responsible for archive. | Association administrator |
| Archive / reason | Dedicated action explanation, limited to 500 characters. | Organization closed |
| Unarchive / reason | Dedicated action explanation, limited to 500 characters. | Closure was recorded in error |
| Lock change / identity | Append-only audit identity retained after ordinary subject deletion. | Recorded lock action |
| Lock change / record type | Subject kind whose customer lock changes. | crm.contacts |
| Lock change / record identity | The subject identity, rather than a cascading live-owner reference. | Ruth’s contact |
| Lock change / operation | Locked or unlocked audit action. | unlocked |
| Lock change / before | Restriction before the action; null means none. | deletes |
| Lock change / after | Restriction after the action; null means released. | None |
| Lock change / reason | Required action explanation, limited to 1,000 characters. | Verified authority to correct the record |
| Lock change / actor | Verified actor making the dedicated action. | Association administrator |
| Lock change / recorded time | The audit action timestamp. | 2026-10-23T14:00:00Z |
| Revision / identity | Immutable identity used to retrieve the capture. | Saved contact revision |
| Revision / sequence | Database-generated order establishes the subject timeline under its write lock. | 48 |
| Revision / record label | Historical subject label retained where captured, including deleted subjects. | Ruth Moreno |
| Revision / operation | Creation, change or deletion identifies the kind of capture. | updated |
| Revision / changes | Compact before-and-after difference document for owner and child changes. | Work phone changed |
| Revision / changed fields | Top-level fields and collection names support timeline filtering. | Phone numbers |
| Revision / capture format | Format discriminator allows readers to interpret the stored difference document. | 1 |
| Revision / transaction identity | Groups records that changed within the same save. | Shared save transaction |
| Revision / recorded time | Capture clock time near settlement, not a claimed database commit timestamp. | 2026-10-23T14:00:00Z |
| Revision / transaction start | Transaction beginning time, separate from capture time. | 2026-10-23T13:59:59Z |
| Revision / actor identity and label | Verified actor and captured historical display name. | Ruth’s coordinator |
| Revision / source | Verified request, job or maintenance context; not client-chosen attribution. | console |
| Revision / operation name | Verified operation attribution for the write. | patchContact |
| Revision / correlation identity | Context linking related execution evidence. | Save correlation |
| Revision / request identity | Verified request context when applicable. | Console save request |
| Revision / job identity | Owning Data Job identity for a batch-produced capture; absent for ordinary requests. | Import job |
| Revision / job batch identity | Batch context distinguishes the job transaction that made the change. | Import batch 3 |
| contact default / console_page_layout | Optional Platform definition reference for this independent workspace slot; type-specific assignments override this default. | contact console page layout definition |
| contact default / console_create_form | Optional Platform definition reference for this independent workspace slot; type-specific assignments override this default. | contact console create form definition |
| contact default / console_edit_form | Optional Platform definition reference for this independent workspace slot; type-specific assignments override this default. | contact console edit form definition |
| contact default / portal_page_layout | Optional Platform definition reference for this independent workspace slot; type-specific assignments override this default. | contact portal page layout definition |
| contact default / portal_form | Optional Platform definition reference for this independent workspace slot; type-specific assignments override this default. | contact portal form definition |
| organization default / console_page_layout | Optional Platform definition reference for this independent workspace slot; type-specific assignments override this default. | organization console page layout definition |
| organization default / console_create_form | Optional Platform definition reference for this independent workspace slot; type-specific assignments override this default. | organization console create form definition |
| organization default / console_edit_form | Optional Platform definition reference for this independent workspace slot; type-specific assignments override this default. | organization console edit form definition |
| organization default / portal_page_layout | Optional Platform definition reference for this independent workspace slot; type-specific assignments override this default. | organization portal page layout definition |
| organization default / portal_form | Optional Platform definition reference for this independent workspace slot; type-specific assignments override this default. | organization portal form definition |

## Relationships

Contacts and organizations each have their own version and restriction fields and potentially many revisions and lock-change entries. These are governed through their subject rather than through a second editable safeguard record. Ordinary deletion removes the subject’s live data but retains revision evidence; archive retains both the subject and its relationships.

Affiliation and contact-point restrictions participate in the contact aggregate. Workspace settings reference the default affiliation type and Platform presentation definitions rather than owning those definitions. The selected default prevents deleting its type. Lock changes can release selected element locks, with the audit identifying which restrictions changed.

## Lifecycle

The record-lock lifecycle explains no customer restriction, deletion restriction and restriction against updates and deletion. Dedicated lock and unlock actions require the applicable grant, the current version and a reason. Rhythm protection is independent and can refuse those customer actions even when the actor holds every ordinary administrative permission.

Archive and business status have their separate record lifecycle models. Revisions are immutable evidence produced by database capture, not an editable state machine or a restore service. Settings begin with an unpersisted version of zero and acquire stored versions when configured.

## Invariants

Customer locks and Rhythm protection answer different authority questions. A delete lock can permit a correction while stopping removal; a full customer lock prevents ordinary modification as well. Rhythm protection independently prevents customer lock changes and deletion. Treating those overlays as interchangeable would either block intended correction or allow customer administration to release a platform-owned restriction.

Dedicated lock changes retain the reason and before-and-after restrictions so staff can explain why an operation is blocked and who altered that boundary. Contacts and organizations can also carry restricted nested elements. Contact roles have their own owner action and the documented ordinary-save exception; their audit cannot be described as identical to every contact transaction.

Revision capture preserves the committed aggregate difference, verified actor and calling context. Its independence from the live owner allows inspection after deletion, while transaction attribution prevents a failed save from acquiring a successful-looking revision. Application read-only access supports tamper resistance against application writers; it does not establish immunity to a database administrator. Retention, page access and the distinction between transaction time and recorded time belong to the history contract rather than to a display claim that every operation is instantaneous.

## Permissions

The CRM authorization model separates workspace access from permission for a particular action.

The service checks these grants at the corresponding operation boundary. Additional record guards still apply: grants do not bypass archive, customer restrictions, dependencies, version comparison or Rhythm protection. Read access and history access are separate; record history checks the applicable read authority as well as the history grant.

## How It Is Stored and Shared

Owner rows retain current versions, locks and archive stamps. Separate revision and lock-change storage retains explanations keyed to the subject. Database capture constructs aggregate contact and organization history from committed work, and application access to revisions is read-only. A missing required change context fails the write rather than storing an unattributed correction.

CRM settings use one workspace record with independently authorized sections. Platform supplies referenced forms and layouts, actor names and permission evaluation. The date cursor supports reconciliation, but an advanced compliance window is not proof that every pending link was processed.

## Open Design Questions

The older contact-role write behavior remains an exception to dedicated lock auditing. Classification saves do not share the record no-op guarantee, and the date cursor does not measure all pending shared-link work. Existing findings preserve these differences; the design does not infer a broader guarantee from the common safeguard vocabulary.
