Managing Legal Holds on Spatial Datasets

A legal hold arrives with a scope described in words — “all survey and imagery relating to the northern corridor works between 2019 and 2022” — and has to become a set of object keys that cannot be deleted, with evidence that the scope was applied completely. This walkthrough is for the archive operator and compliance owner who must translate that sentence into storage-layer state, keep it in force for an unknown duration, and release it cleanly when the matter closes.

Holds Are Not Retention Periods

The two mechanisms look similar and behave differently in the ways that matter during litigation.

Retention periods and legal holds compared Retention periods have a known end and expire automatically; legal holds have no end date, are applied and removed explicitly, and compose with retention so protection lasts until the later of the two. propertyretention periodlegal hold end dateknown at the time it is setunknown appliedat write, usually by a default ruleexplicitly, per object removedexpires on its ownonly by a deliberate action interactionprotection lasts until the later of the two ends independent of retention on expired retentionobject becomes deletable a hold can still be applied

Step-by-Step Procedure

Step 1 — Turn the scope into a query, then into a manifest

The scope is described in domain terms; the archive is organised by prefix, tag and time. Bridging the two is the step that determines whether the hold is complete, and it belongs to the archivist rather than to the storage platform.

-- Scope: survey and imagery for the northern corridor works, 2019-2022
CREATE TABLE hold_2026_014_manifest AS
SELECT i.key, i.storage_class, c.collection, c.datetime
FROM   spatial_archive_inventory i
JOIN   stac_items c ON c.asset_key = i.key
WHERE  c.collection IN ('aerial-imagery', 'lidar-classified', 'survey-control',
                        'utilities-asbuilt')
  AND  c.datetime BETWEEN TIMESTAMP '2019-01-01' AND TIMESTAMP '2022-12-31'
  AND  ST_Intersects(c.footprint,
                     ST_GeomFromText('POLYGON((...northern corridor...))', 27700));

Record the query itself alongside the manifest. When someone asks in two years how the scope was determined, the query is the answer — a list of keys is not.

Step 2 — Apply the hold as a batch operation

aws s3control create-job --account-id 123456789012 \
  --operation '{"S3PutObjectLegalHold":{"LegalHold":{"Status":"ON"}}}' \
  --manifest '{"Spec":{"Format":"S3BatchOperations_CSV_20180820","Fields":["Bucket","Key"]},
               "Location":{"ObjectArn":"arn:aws:s3:::spatial-archive-logs/holds/2026-014.csv",
                           "ETag":"7c41…"}}' \
  --report '{"Bucket":"arn:aws:s3:::spatial-archive-logs","Format":"Report_CSV_20180820",
             "Enabled":true,"Prefix":"holds/2026-014","ReportScope":"AllTasks"}' \
  --priority 30 --role-arn arn:aws:iam::123456789012:role/archive-batch --region us-east-1

Step 3 — Prove the hold is complete and in force

# Every object in the manifest reports a hold; count must equal the manifest length
while read -r bucket key; do
  aws s3api get-object-legal-hold --bucket "$bucket" --key "$key" \
    --query 'LegalHold.Status' --output text
done < holds/2026-014.csv | sort | uniq -c
#  18432 ON
Life of a legal hold from scoping to release A timeline showing scoping and application, monthly re-scoping to catch newly in-scope objects, continued lifecycle transitions, refused deletions, and eventual release. week 1 scope + apply 18,432 objects monthly re-run the scope query new objects enter scope ongoing tiering continues deletions refused release hold removed retention resumes The step most often missed A hold applied once covers the objects that existed then. Data arriving later within the scope is not held unless the scope query is re-run.

Step 4 — Release deliberately, and record it

aws s3control create-job --account-id 123456789012 \
  --operation '{"S3PutObjectLegalHold":{"LegalHold":{"Status":"OFF"}}}' \
  --manifest file://holds/2026-014-release.json \
  --report file://holds/2026-014-release-report.json \
  --priority 30 --role-arn arn:aws:iam::123456789012:role/archive-batch --region us-east-1

Validation & Verification

python -m archive.holds audit --hold-id 2026-014
# manifest objects            18,432
# holds ON                    18,432
# in scope but not held            0
# held but out of scope            0
# last re-scope             2026-08-01
# AUDIT PASS

Expected output is zero in both mismatch rows. “In scope but not held” is the serious one: it means data that should be preserved is deletable, and it appears whenever the scope query has not been re-run since new material arrived.

Troubleshooting

Symptom Root cause Fix
Hold applies but objects still deletable Bucket lacks Object Lock, enabled only at creation The bucket cannot be retrofitted; migrate to a lock-enabled bucket
New data not covered Scope applied once, never re-run Schedule the scope query monthly for the life of the hold
Lifecycle expiry fails silently Hold blocking deletion, as designed Not an error; report it so nobody “fixes” the lifecycle rule
Release leaves some objects held Release manifest generated from a stale scope Release from the applied manifest, not from a fresh query
Cannot determine why an object is held No hold identifier recorded on the object Tag held objects with the hold id as well as setting the flag

Operational Execution Checklist

What the Hold Costs While It Runs

A hold has no expiry, so its cost accrues indefinitely and is worth stating when it is applied. For spatial data the figure is usually modest and occasionally not, and the difference comes from what the hold blocks rather than from the storage it occupies.

Cost components of a hold in force Four cost components of an active legal hold, with which are material for a spatial archive. component material? note storage that cannot expire yes the main standing cost transitions still permitted no tiering savings continue normally restores if called for occasionally budget for one full restore of the scope monthly re-scoping small one query and a batch job Because transitions are unaffected, a long hold on cold material costs little — the expensive case is a hold on data that would otherwise have been deleted.

State the annual figure when the hold is applied and revisit it at each review. A hold whose cost has grown because the scope keeps widening is one worth discussing with whoever requested it, and that conversation is easier with a number than with an impression.

Frequently Asked Questions

Can held objects still be moved between storage classes?

Yes. A legal hold blocks deletion and overwriting; it does not block transitions, and lifecycle rules continue to operate normally on held objects. That is usually desirable — a hold lasting years should not force the data to stay in an expensive class — but it means a held object may be in Deep Archive when it is called for, so plan the restore path as part of the response rather than as a surprise.

What happens to a hold if the matter ends and nobody removes it?

The objects stay protected indefinitely, which is a failure of a different kind: storage that cannot be released and an archive whose retention policy no longer describes its actual state. Holds should have an owner and a review date even though they have no expiry, and the review should ask whether the matter is still open rather than whether the hold is still needed.

How is a hold applied to data that has not been created yet?

It cannot be, directly — a hold is applied to objects. The practical arrangement is a standing scope query re-run on a schedule, combined with an ingest-time check that flags material matching an active hold’s scope so it is held on arrival rather than a month later. The gap between arrival and the next scope run is the exposure, and it is worth stating explicitly.

Who should be able to apply and release a hold?

Applying should be available to the archive operators, since delay creates risk; releasing should require the requesting authority’s confirmation, since a premature release destroys the protection the hold existed to provide. Separating the two permissions is a small piece of access-control design that prevents the most consequential mistake in this area.

How is a hold documented for someone who arrives later?

With the scope query, the manifest, the requesting reference, the date applied and the owner. Those five facts let a colleague two years later answer what is held, why, on whose authority, and whether the scope still matches the request — which is precisely the set of questions that arises when the original owner has moved on.

Can a hold cover data held by an upstream system rather than the archive?

Not through the archive’s controls, and the gap is worth naming explicitly. A hold applied to archived objects says nothing about the same material sitting in a working system, and preservation obligations usually cover both. Coordinating with the systems upstream is part of applying the hold, even though only the archive’s part is technically enforceable here.

What happens if the archive is migrated while a hold is in force?

The hold does not travel with the objects — it is storage-layer state, not file content — so a migration must reapply it in the destination before the source is released. That makes a migration under an active hold materially more complex than one without, and it is a reason to complete or at least stabilise holds before scheduling a platform move.

Can a hold be applied to a whole bucket rather than to objects?

Some platforms offer container-level immutability, which is coarser and simpler. It suits an archive whose holds are effectively permanent and organisation-wide; it suits a matter-scoped hold badly, because releasing it releases everything. Per-object holds with a recorded scope are the right instrument when the scope is narrower than the bucket.

Should holds be visible in the catalogue?

To operators, yes; to general users, usually not. Operators need to know why an object cannot be deleted; users mostly do not, and exposing an active matter’s scope through a public catalogue is rarely appropriate. A held flag visible to authenticated operators strikes the right balance.

Up one level: Retention Policy Frameworks.