---
id: FEAT-CRM-ENT-07
title: Record Protection and History
product_area: crm
module: entities
revision: 1
status: approved
product_intent: proposed
source_sha:
  services-crm: 7ea48a414c42eade10d60753b8bcc323b1dd726f
  react-console: da63075876b2c5f4a6af07d457bb24ea8bf4c0c7
purpose: >-
  Staff preserve records, control changes and inspect their history. Administrators manage the
  protections and lifecycle actions that require additional authority.
outcomes:
  - 'Staff preserve records, control changes and inspect their history.'
concepts:
  - CON-CRM-ENT-033
actors:
  - ACT-CRM-001
  - ACT-CRM-002
  - ACT-CRM-003
  - ACT-CRM-004
  - ACT-CRM-005
acceptance: FAS-CRM-ENT-07
capabilities:
  - CAP-CRM-ENT-004
  - CAP-CRM-ENT-013
  - CAP-CRM-ENT-014
  - CAP-CRM-ENT-015
  - CAP-CRM-ENT-016
journeys:
  - JNY-CRM-ENT-003
approved_by: Andrew
approved_at: 2026-10-22T00:00:00.000Z
narrative:
  status: verified
  review: NARRATIVE-REVIEW-2026-10-23
narrative_sources:
  - section: Why It Exists
    provenance: inferred
    source: CAP-CRM-ENT-004; CAP-CRM-ENT-013; CAP-CRM-ENT-014; CAP-CRM-ENT-015; CAP-CRM-ENT-016
    note: >-
      Operational consequences are the reviewer’s reading of the child contracts, not attributed
      historical motivation. The affiliation milestone supplies the current-membership distinction
      where named.
  - section: Decisions and Tradeoffs
    provenance: inferred
    source: "CAP-CRM-ENT-004; CAP-CRM-ENT-013; CAP-CRM-ENT-014; CAP-CRM-ENT-015; CAP-CRM-ENT-016"
    note: The tradeoffs interpret the child contracts; no historical motive is attributed.
  - section: How It Works
    provenance: observed
    source: >-
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 400 naming sys_version to
      a patch that tests no version;
      services-crm:modules/entities/tests/integration/services/persistence/record-saves.no-op.integration.spec.js#writes
      no revision; services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#clears a field a
      later patch removes;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#empties
      the collection;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#moves
      the second address into the type the first left, and the preferred flag with it;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.field-rules.integration.spec.js#refuses
      a replacement whose contact number differs from the stored one, naming number;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#rolls
      back the contact row the transaction had already updated;
      services-crm:modules/entities/tests/integration/services/persistence/organizations.integration.spec.js#refuses
      the create as ObjectInvalid, listing both problems;
      services-crm:modules/entities/tests/unit/api/contact-api-mapper.spec.js#drops a blank e-mail
      element rather than refusing the null the string standard made of it;
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 409 naming the system,
      not a colleague, when the system cleared the spouse link while the contact was edited (M1-D6);
      react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0009.spec.ts#CRM-ACC-0009;
      services-crm:docs/milestones/CRM-M1-contacts-and-organizations.plan.md#A14;
      services-crm:docs/entities-api.md#Not yet under the complete-payload rule;
      services-crm:modules/entities/tests/e2e/archive.e2e.spec.js#refuses a caller who may update
      contacts but not archive them, 403;
      services-crm:modules/entities/tests/e2e/archive.e2e.spec.js#refuses an archive with no
      version, 400, naming it;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.archive.integration.spec.js#keeps
      the status it had: archive is not a status;
      services-crm:modules/entities/tests/integration/services/persistence/organizations.archive.integration.spec.js#keeps
      the trimmed reason as unarchive_reason;
      services-crm:modules/entities/tests/e2e/archive.e2e.spec.js#refuses a replacement, 409
      RecordArchived; services-crm:modules/entities/tests/e2e/archive.e2e.spec.js#lists it for
      archived=only, and only it; services-crm:docs/archive.md#Lists and pickers;
      services-crm:modules/entities/tests/integration/services/persistence/organizations.archive.integration.spec.js#keeps
      the ended affiliation naming the organization's id, with its name and its archive instant;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.archive.integration.spec.js#is
      kept through a plain save, which never writes it;
      react-console:acceptance/crm/playwright/journeys/contacts/CRM-ACC-0046.spec.ts#CRM-ACC-0046;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#removes
      every owned element with it, the foreign keys cascading;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#bumps
      the version of that contact and stamps the system, not the caller that deleted (M1-D6);
      services-crm:modules/entities/tests/integration/services/persistence/contacts.integration.spec.js#refuses
      the delete as IllegalOperation, naming the element that is locked;
      services-crm:modules/entities/tests/e2e/organizations.e2e.spec.js#answers 409 to a delete
      while the contact is affiliated with it;
      services-crm:modules/entities/tests/integration/services/assets/retire-entity-assets.integration.spec.js#detaches
      the photo in the delete's own transaction;
      services-crm:modules/entities/tests/unit/services/persistence/endpoint-deletions.spec.js#never
      runs a statement when it is off;
      services-crm:docs/milestones/CRM-M1-contacts-and-organizations.md#CRM-Q10;
      services-crm:modules/entities/aws/api/record-locks.routes.ts:defineRecordLockRoutes;
      services-crm:modules/entities/tests/integration/services/persistence/record-locks.integration.spec.js#leaves
      the role at its version and lock, and records nothing;
      services-crm:modules/entities/tests/integration/services/persistence/record-locks.queries.integration.spec.js#answers
      the newest two changes of the record, every property mapped, and where the page ended;
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 403 naming sys_locked for
      a replacement that sets a lock (E-14);
      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;
      services-crm:modules/entities/tests/integration/services/persistence/record-locks.integration.spec.js#refuses
      an element id the organization does not have, releasing nothing;
      services-crm:modules/entities/tests/e2e/contacts.e2e.spec.js#answers 409 to a replacement that
      only clears an element lock without the unlock key, and 200 with it (CRM-ACC-0035);
      services-crm:modules/entities/tests/integration/services/persistence/record-locks.integration.spec.js#refuses
      the unlock, naming the protection; services-crm:docs/record-locks.md#Not covered yet;
      services-crm:modules/entities/tests/integration/services/persistence/contacts.history.integration.spec.js#writes
      exactly one revision, whatever the save touched;
      services-crm:modules/entities/tests/e2e/organizations-history.e2e.spec.js#answers a caller
      with the history key but no read of organizations 404, as the organization itself would;
      services-crm:modules/entities/tests/e2e/contacts-history.e2e.spec.js#refuses a query parameter
      it does not know, rather than ignoring the filter;
      services-crm:modules/entities/tests/integration/services/persistence/record-revisions.queries.integration.spec.js#still
      answers for a user who has been deleted, because former users are named too;
      services-crm:modules/entities/tests/e2e/contacts-history.e2e.spec.js#shows the role removed
      and the role added, each under the name it has now;
      services-crm:modules/entities/aws/api/contacts.routes.ts:absorbedContactHistoryRoutes;
      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;
      services-crm:docs/record-history.md#Fail closed, and the maintenance helper;
      services-crm:docs/record-history.md#Retention
acceptance_suite: FAS-CRM-ENT-07
---

# Record Protection and History

## Overview

Record Protection and History preserves the meaning of a CRM record while people and automated processes change it. Staff need to know whether an edit is based on current information, whether a record is deliberately protected, and what earlier changes explain its present condition.

Different operations answer different needs. Archive sets a record aside while retaining it; deletion removes the live record; locks constrain changes; history preserves attributed revisions. Combining these into one status or one destructive action would hide distinctions that staff need to make deliberately.

## Why It Exists

Our reading is that shared CRM information needs safeguards beyond validating a single form. A coordinator can open Ruth's record before another editor corrects her phone; saving the older form without a version comparison could erase that correction. A firm can close while ended affiliations still need its identity to explain former employment. Deleting every closed body would remove that context.

Versions, archive, customer locks and attributed history address these different risks. A version checks what the editor has read; archive changes ordinary availability; a lock restricts mutation; history explains committed changes. Keeping their meanings separate gives staff a deliberate choice and helps later investigation. The recorded complete-payload and safe-delete boundaries remain visible rather than being inferred as completed safeguards from the existence of save or delete operations.

## Who Uses It

The CRM Editor saves corrections and resolves conflicts. The CRM Reader inspects permitted revisions and retained record information. The CRM Administrator archives, restores, deletes, or changes locks under the specific authority each action requires. The Data Importer must respect the same version and lifecycle constraints, and the CRM System contributes separately attributed changes.

## How It Works

A save carries the version read. Rhythm checks the current record, validates the resulting aggregate, and commits the change and its revision together. A stale edit receives a conflict; a current unchanged save leaves version and history alone. The approved complete-payload replacement policy remains a recorded implementation gap, distinct from today's omission behavior.

Archive hides a record from ordinary discovery and prevents ordinary editing while preserving its status, collections, and relationships. Authorized restoration makes it available again. Locks and protected records add separate restrictions. Hard deletion remains a service and bulk operation with dependency checks, while record-page actions offer archive. Revisions remain readable under history authority after deletion and are not a restore mechanism.

An editor begins with the record version she actually read. The service applies a patch to that record or interprets a replacement under its existing omission rules, validates the resulting aggregate, and commits all owned changes together. An invalid element, lock or stale version leaves the prior aggregate intact. On conflict, the contact editor offers reload or comparison. Keep Editing does not silently adopt the latest version. The person must review current information before submitting against it, including when a system change rather than a colleague caused the conflict.

Current unchanged saves avoid new revisions. Stale unchanged saves still conflict under today's behavior, a separately recorded product question. Product's approved complete-payload replacement requirement is also separate from the implementation: a replacement currently can clear omitted values, with preservation exceptions for affiliations, custom values and other stated cases. Clients must understand that gap rather than infer that every omission is already rejected. The console's changed-field patch workflow does not certify that the service replacement gap has disappeared.

Archive is a preservation choice. It retains identity, status, collections and relationships while excluding the record from ordinary discovery and refusing ordinary editing. Restoration requires the specific action and current version. A customer full lock can block updates as well as deletion; a delete lock still permits ordinary correction. Releasing a restriction uses authorized audited actions with a reason. System protection belongs to another authority and cannot be released by customer unlock permission, although its ordinary fields have their documented editing boundary. The pinned seeds contain no protected CRM record.

Hard deletion removes the live record and owned information subject to blockers, including organization affiliation dependencies. Record pages offer archive, while service and bulk paths retain deletion. The broader safe-delete impact workflow is not complete here. History survives live-record deletion and groups old and new values with attribution; it supports explanation, not restoring a snapshot. Current display labels and captured fallbacks must also be distinguished from a promise that every related name is historically frozen.

## Examples

A closed member firm still appears in former employees' ended affiliations. An administrator cannot delete it while those dependencies remain, so she archives it with a reason. Ordinary discovery no longer offers it, but opening it directly retains its information and affiliation context. If it reopens, authorized unarchive restores availability without reconstructing the firm or its ties.

Two coordinators open Ruth's contact. Elena saves the corrected phone first; Sam's later title edit is refused as stale. Sam compares, reloads the corrected phone, reenters the title and saves against the latest version. The resulting history explains both changes. If a spouse cleanup made the intervening change instead, the conflict identifies the system; it does not silently merge Sam's edit over it.

An administrator places a delete lock on Harbor West Chapter while retention is being reviewed. Ordinary correction remains possible, but deletion does not. She later releases the restriction with a current version and recorded reason. When the chapter closes, ended affiliations still name it. Dependency checks prevent hard deletion, and archive preserves those references while taking the chapter out of ordinary discovery. Opening it directly explains the closed organization's context. If it reopens, unarchive returns the same identity to availability. Staff inspect retained history without assuming that reading an earlier value offers a rollback action.

## Decisions and Tradeoffs

Our reading is that a version protects the editor's knowledge of the whole record. Automatically accepting the latest token would make an unreviewed change look authorized. Atomic aggregate validation prevents a refused address or affiliation from leaving a partly corrected person behind; no-op handling prevents unchanged submissions from obscuring meaningful history.

Archive, locks and protection preserve different invariants. Combining them into one inactive status would obscure whether a record is unavailable for selection, temporarily immutable, or reserved by system authority. Hard deletion and archive likewise have different consequences for dependent history. Keeping deletion available does not make it the safe choice for every closed body, and the deferred safe-delete boundary must remain visible. These are interpretations of the formal behavior rather than an invented history of design deliberation.

## Scope and Boundaries

The feature owns version-aware record saves, archive state, hard-deletion behavior, customer locks, system protection, and revision reads. Platform security owns permission evaluation, and Platform files owns storage cleanup. Data Quality owns merge decisions and survivors. The broader safe-delete architecture remains an intended boundary; it must not be presented as a completed impact-preview workflow here.

## Outcomes

Staff can protect records, explain their changes, and handle closed or obsolete identities without confusing archive with deletion. The Record Protection and History acceptance suite joins concurrency, lifecycle, locks, and audit outcomes. Archive a closed organization and inspect its retained history provides a staff narrative for preservation, with unresolved safe-delete and protection choices kept visible in findings.

## Its Capabilities

Save corrections without overwriting another change anchors edits to the version read. Set records aside and restore them manages archive independently of status. Remove records and their owned information performs checked hard deletion. Control record locks and system protection distinguishes customer restrictions from Rhythm protection. Inspect a record’s changes over time exposes attributed revisions. The archive journey shows preservation across these safeguards.
