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¶
- Navigate to Archive → Legal Hold (
/Archive/LegalHold). - Click Apply Hold (top-right).
- Fill in Archive Site ID, Scope, Case Number, and Reason.
- 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:
S3 Object Lock + Legal Hold Flag (planned v1.3)¶
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 ApplyAsync → PUT Object Legal Hold and
ReleaseAsync → DELETE 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 Legal Hold (planned v1.3)¶
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