0056 — Lock modes and store tiers: the lock is demanded where a framework demands it¶
Status: accepted; refined by 0068 (the lock is a property of the install preset's bucket, not a setting of the process) Date: 2026-09-23
0045 stands for the record tier
it describes. This adds a second tier beside it and says which profiles may
run on which.
Context¶
0045 made S3 Object Lock in compliance mode the record and everything else a projection. It was written for one deployer on one cloud, and it made two things mandatory that are not the same thing: the Object Lock API, and the reading that every framework demands WORM storage.
Neither survives contact with the deployments now being planned.
Not every store has the Object Lock API. The archive's calls are the S3
API's, and any store that speaks it — an S3-compatible service from another
provider, MinIO, Ceph — takes them, except for three: the lock headers on a
put, PutObjectRetention and PutObjectLegalHold. A store without them
refuses a put that names a lock mode outright, so a writer that always names
one cannot write to such a store at all, however good its digest chain is.
Not every framework demands the lock. The framework profiles read seven frameworks,
and they divide. PCI DSS 10.3.2 and 10.3.3, NEN 7513, the DORA RTS on ICT
risk management (Art. 12) and ETSI EN 319 401 7.10 each require that logs or
evidence be protected against tampering and deletion in front of a named
assessor, and storage the operator cannot delete from is the reading a
qualified assessor expects. NIS2 and ISO/IEC 27001 are management-system
standards: the entity chooses the control that protects its logs from
alteration, and a signed digest chain under a managed key, on a bucket whose
policy grants nobody a delete, is a valid control. AWR art. 52 asks for the
authenticity and integrity of the administration over the retention period,
which the chain provides; it does not mandate WORM. history is a product
decision and cites no framework at all.
A single mandatory lock therefore forced a deployment whose profiles need none onto the one kind of store that has it, and gave a deployment on any other store no way to run at all.
Decision¶
There are two store tiers, and a profile says which it needs.
record is 0045 unchanged:
Object Lock in compliance mode, retention per object at write time,
deletes denied by policy, legal holds. It needs the Object Lock API:
PutObject with the lock headers, PutObjectRetention,
PutObjectLegalHold, beside GetObject, HeadObject, ListObjectsV2
and presigning.
attested is the same archive with no lock: the same keys, the same
objects, the same hourly signed digest chain, on a store that answers
PutObject, GetObject, HeadObject, ListObjectsV2 and presigning and
nothing more. The chain proves that nothing was changed or removed since
it was sealed; what the lock alone covered — the objects of the hour not
yet sealed, and the operator's own ability to delete — is covered by the
compensating controls below, or not at all, and the profile says whether
that is acceptable.
Every framework profile's object_lock_mode is now the least lock its framework
demands: compliance, governance or none. Composition takes the
strictest, so a profile composed from any framework profile that demands compliance
demands it. pci-dss, nen-7513, dora and evidence-etsi demand
compliance; security, history and billing-nl demand none. Each
framework profile carries a one-line note saying why.
The deployment's lock mode (--lock-mode, the chart's lockMode) is what
the writer, the digest job and the verify job write with:
compliance, governance or none. A component refuses to start when a
profile's composed mode is stricter than the deployment's, naming the
profile and both modes: a compliance profile on a governance or unlocked
store, a governance profile on an unlocked store. A stricter store than a
profile asks is always fine.
On an unlocked store, ExtendRetention and SetLegalHold answer
store.ErrNotLockable. The writer treats a retention it could not extend as
it always did — the addendum is written, the failure is in the trail and
counted — and a legal hold placed by hand is refused with that error and
recorded as the attempt it was. audit verify, given the deployment,
reports an object with no lock as unlocked under a profile that demands
none and as INVALID under one that does.
Compensating controls for the attested tier¶
These are what the deployment supplies in place of the lock, and the S3 guide says how.
- A managed signing key is required, not recommended. On the record tier a local signer proved that objects had not changed since signing, and the lock proved nobody could have chosen what to sign. Without the lock only a key the operator cannot use to re-sign — KMS, a transit engine — proves the second, so on this tier it is the only signer that proves anything.
- A shorter digest interval. The unsealed window is the one gap the
lock alone covered: an object of the current hour can be deleted before
the chain names it. Running
audit digestmore often narrows it to minutes. - No delete permission, anywhere. As on the record tier, no
component's credentials carry
DeleteObjectorDeleteObjectVersion, and versioning is on where the store has it. This is the control ISO 27001 A.8.15 accepts. - An out-of-band bucket rule where the store offers one. Some stores can refuse deletes on a bucket or prefix by an administrative rule an administrator can also remove. That is governance-shaped protection — worth having, not worth mistaking for compliance mode — and a deployment that has it says so in its own records.
Consequences¶
- A deployment that composes only
security,historyorbilling-nlmay run on any S3-compatible store, withlockMode: none, and the chart passes an endpoint, path-style addressing and static credentials to every component that touches the archive. - A deployment that composes
pci-dss,nen-7513,doraorevidence-etsistill needs a bucket with Object Lock, and the writer says so at start-up rather than at audit time. - The exports bucket is unchanged: it always was an unlocked store, and it may now be on a store of its own.
- Retention on the attested tier is a lifecycle rule, not a lock: the store keeps objects until the rule expires them, and nothing stops an administrator shortening the rule. A profile that needs the retention to be unshortenable is a profile that demands the lock, and says so.
- The digest job on the attested tier writes its digests unlocked too. The chain is still self-certifying — each digest names the previous one's hash and signature — and a removed digest is a gap the verifier reports.
Alternatives considered¶
- Keep the lock mandatory and refuse every other store. Simple, and it kept a deployment whose frameworks demand no lock off every store but one. The frameworks, not the store, should decide.
- Make the lock optional everywhere, with a warning. A compliance profile would then run unlocked on a mistake in one values line, and every copy written before the warning was read would be the evidence the framework asked for, deletable. The refusal is the point.
- Emulate a lock on stores that lack one, by refusing deletes in the writer's own code. The archive's guarantee has never rested on what this code refuses to do; it rests on what the store refuses to do for everyone. A lock the writer enforces protects against the writer.
- Governance mode as the attested tier. It needs the Object Lock API the attested tier exists to do without, and it is bypassable by anyone holding the permission — which makes it the wrong name for "no lock".
Amendment: no Object Lock on an S3-compatible endpoint¶
A store at an endpoint of its own (archive.bucket.endpoint) is written with
lockMode: none and nothing else. Some such stores accept the lock headers, but
the lock is an AWS S3 guarantee that they do not make as AWS does, and an archive
that believes it is locked when it is not is worse than one that says it is not.
A lock mode on an endpoint is refused by the configuration and by the store, and
the attested preset is refused there. See archive on R2.