fix(ilm): require an authoritative cross-pool history before lifecycle expiry

The merged all-pool check from #279 decided on the versions that a read
quorum of each set merged non-strictly. A drive that missed one metadata
update could therefore supply the revision used for the decision, and a
partially unreadable set could drop a committed version from the history.
Both let an expiry delete data the rule did not cover, including a version
under legal hold.

- objectVersions accepts a revision only when max(n/2+1, the version's
  metadata read quorum) drives hold it, identifies revisions by the header
  plus a sha256 over a canonical, length-prefixed encoding of the
  normalized metadata, filters non-accepted revisions before the strict
  merge, and fails closed on omitted versions, too many unreadable drives,
  or cancellation.
- Lock fields are read with the evaluator's key precedence. Cross-pool
  copies that disagree on hold or retention must be strictly ordered by
  valid timestamps; copies with different ModTime are separate writes and
  an older protected write refuses the object. The set safeguard re-checks
  protection and pending purges on its fresh read for checked version
  deletes.
- Duplicate copies with semantically different replication or purge state
  refuse the object.
- Batch expiry announces and audits only completed or already-absent
  removals.

Reviewed adversarially over seven rounds; the final round approved with no
remaining findings in scope. All-versions expiry through deletePrefix and
tiered delete-all dispatch are tracked separately.

Signed-off-by: Feng Ruohang <rh@vonng.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Feng Ruohang
2026-11-05 00:16:27 +08:00
parent f321f7f1c7
commit 9f7b49369d
8 changed files with 1940 additions and 55 deletions
+4
View File
@@ -163,6 +163,10 @@ type ExpirationOptions struct {
// object in all pools (checkExpiryAllPools). A set does not check it
// again with the versions it holds alone.
CheckedAllPools bool
// ApprovedProtection: the Object Lock state of the version with which
// the check approved its expiry. The set compares it with the state it
// reads (checkedExpiryRefused). In memory only.
ApprovedProtection objectLockState `msg:"-"`
}
// TransitionOptions represents object options for transition ObjectLayer operation