Skip to content

Archive Legal Hold — Operator Guide

Cesivi v1.2 (PLAN-1612). Updated: 2026-05-27.

Overview

A legal hold freezes one or more archive items in place: they cannot be deleted, recycled, or mutated by any user or process — regardless of the item's retention policy. Hold beats retention. Even if an item's retention window has expired, an active hold prevents modification.

This feature is designed for eDiscovery, regulatory audit, and litigation preservation obligations under HIPAA, GDPR, SOX, and FRCP Rule 37(e).


Compliance Mapping

HIPAA §164.316(b)(2) — Record Retention (6-year rule)

HIPAA requires covered entities to retain policies and procedures for a minimum of six years from the date of creation or last effective date.

Cesivi mapping: Apply a site-scope or list-scope legal hold with caseNumber referencing the HIPAA audit identifier. The hold supersedes retention windows and prevents deletion until explicitly released by an authorised compliance officer.

GDPR Art. 17(3)(e) — Right to Erasure Exception

Article 17(1) grants data subjects the right to erasure ("right to be forgotten"). Art. 17(3)(e) carves out an exception when processing is necessary for the establishment, exercise, or defence of legal claims.

Cesivi mapping: When a data-subject erasure request conflicts with a pending legal hold, the REST API will return HTTP 423 Locked. The compliance officer must explicitly release the hold (POST /_api/archive/legalhold/release/{holdId}) before erasure can proceed. The release reason and timestamp are WORM-audited.

SOX §103 — Audit Record Retention

The Sarbanes-Oxley Act §103 requires auditors to retain audit work papers and records for seven years.

Cesivi mapping: Apply a site-scope hold with caseNumber referencing the SOX engagement. All blocked mutation attempts are logged in the WORM audit trail (ArchiveAuditEventTypes.ReadAccess / HoldApplied / HoldReleased).

FRCP Rule 37(e) — Chain of Custody for eDiscovery

Federal Rules of Civil Procedure Rule 37(e) addresses sanctions for failure to preserve electronically stored information (ESI).

Cesivi mapping: Legal hold provides documented chain-of-custody: - HoldApplied audit event records: timestamp, applying officer, case number, scope. - Every blocked deletion attempt is logged with caller identity and surface (REST/CSOM/SOAP). - HoldReleased audit event records: timestamp, releasing officer, release reason.


Scope Hierarchy

Cesivi legal holds support three scopes. A broader scope always covers narrower ones:

Scope Covers Required fields
Site All lists and items in a site archiveSiteId
List All items in a specific list archiveSiteId, listId
Item A single list item archiveSiteId, listId, itemId

Resolution order (gate evaluation): item-scope → list-scope → site-scope. An item is held if ANY of its containing scopes has an active hold.


Applying a Hold

Via ControlCenter UI

  1. Navigate to Archive → Legal Hold (/Archive/LegalHold).
  2. Click Apply Hold (top-right).
  3. Fill in Archive Site ID, Scope, Case Number, and Reason.
  4. Click Apply Hold. The hold appears in the Active Holds table immediately.

Via REST API

POST /_api/archive/legalhold/apply
Content-Type: application/json

{
  "archiveSiteId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "scope": "Site",
  "caseNumber": "CASE-2026-042",
  "reason": "DOJ eDiscovery request — Matter 2026-HQ"
}

Response: 201 Created with the hold record (includes HoldId).

Error responses: - 400 — Invalid scope or missing coordinates. - 409 — A hold with the same scope/coordinates already exists.


Releasing a Hold

Only explicitly authorised compliance officers should release holds. Every release is WORM-audited with timestamp and reason.

Via ControlCenter UI

Click the unlock icon (🔓) next to the hold in the Active Holds table, enter a release reason, and confirm.

Via REST API

POST /_api/archive/legalhold/release/{holdId}
Content-Type: application/json

{
  "reason": "Case closed — settlement reached 2026-05-27"
}

Response: 200 OK with the updated hold record (Released: true).


REST API Reference

Base path: /_api/archive/legalhold

Method Path Description
GET /status Active hold counts + by-scope breakdown
GET /active List all active holds (filter by ?siteId=)
GET /holds/{holdId} Full hold record
GET /holds/{holdId}/affected-items Items covered by hold
GET /holds/{holdId}/read-access Read-access log entries
POST /apply Create a new hold
POST /release/{holdId} Release an existing hold
GET /policy Read ReadLogMode + SampleRate
PATCH /policy Update ReadLogMode + SampleRate

Error Shapes

When a mutation is blocked by a legal hold, Cesivi returns:

Surface HTTP Body
REST 423 Locked { "error": { "code": "LegalHoldActive", "message": "..." } }
CSOM n/a (exception) ServerException.ServerErrorCode = -2147024891 (SP UnauthorizedAccessException)
SOAP 200 OK (SOAP fault in body) ErrorCode = 0x81020016

Read-Access Logging

Legal hold read-access logging records when users read items covered by an active hold. This supports eDiscovery chain-of-custody documentation.

Modes (configured via PATCH /policy or appsettings.json):

Mode Behaviour
Off (default) No read events logged
SampledRate Log 1-in-N reads (configurable via sampleRate)
EveryRead Log every read — highest fidelity, higher overhead

View logs: GET /_api/archive/legalhold/holds/{holdId}/read-access


Backend Pluggability (v1.3 Roadmap)

The IArchiveLegalHoldStore SPI supports alternative backends:

AWS S3 Object Lock supports a native Legal Hold flag that is independent of the Object Lock retention period. Cesivi v1.3 will implement S3ObjectLockLegalHoldStore that maps ApplyAsyncPUT Object Legal Hold and ReleaseAsyncDELETE Object Legal Hold.

Stub: Create a class deriving from ArchiveLegalHoldStoreContractTests and implement CreateStore() to validate all 16 contract scenarios pass against the S3 backend.

Azure Blob Storage supports Legal Hold policies on immutable storage containers. Cesivi v1.3 will implement AzureBlobLegalHoldStore that maps to Azure's hold tags.


Configuration

{
  "Cesivi": {
    "ArchiveLegalHold": {
      "StoreType": "FileSystem",
      "ReadLogMode": "Off",
      "SampleRate": 100,
      "ReadAccessRingSize": 10000
    }
  }
}
Setting Default Description
StoreType FileSystem InMemory or FileSystem
ReadLogMode Off Off, SampledRate, or EveryRead
SampleRate 100 Log 1-in-N reads when mode is SampledRate
ReadAccessRingSize 10000 Ring-buffer capacity per hold

FileSystem Layout

{DataRootPath}/
└── Archive/
    └── {archiveSiteId}/
        └── legal-hold/
            ├── holds.jsonl          # WORM hold log (append-only)
            └── read-access.jsonl    # Read-access log (rolled at 100 MB)

SignalR Live Updates

ControlCenter subscribes to LegalHoldHub at /signalr/legalhold. Events:

Event Payload Trigger
HoldApplied ArchiveLegalHold New hold applied via REST
HoldReleased ArchiveLegalHold Hold released via REST

Groups: legalhold:{siteId} (per-site) and legalhold-all (all sites).


Troubleshooting

Item deletion returns 423 unexpectedly
Check GET /_api/archive/legalhold/active?siteId={siteId} — a site-scope or list-scope hold may be blocking the item even if no item-scope hold was applied directly.

Hold not blocking CSOM deletes
Ensure ArchiveLegalHoldGate is registered in DI. Check Cesivi.Server/Program.cs for the AddSingleton<ArchiveLegalHoldGate> registration. CSOM gate is wired in CsomProcessor.RequestProcessing.cs:CheckCsomRetentionGateForItemAsync.

Read-access log is empty with mode=EveryRead
Verify the item's hold is active (GET /holds/{holdId}Released must be false). The logger only fires when an active hold is found covering the item.


See also: Archive Admin Bundle — ControlCenter Quick Tour

See also: Archive Tools Operator Guide

See also: Tutorial G — SharePoint On-Premises Retirement Archive

See also: Cesivi Archive Variant A — Whitepaper

See also: Compliance Cookbook — HIPAA/GDPR/SOX/FRCP

See also: Archive Cluster Deployment Guide