Skip to content

docs: propose the S3 object-lock design (per-key version-state tree) - #77

Open
frrist wants to merge 3 commits into
mainfrom
design/s3-object-lock
Open

docs: propose the S3 object-lock design (per-key version-state tree)#77
frrist wants to merge 3 commits into
mainfrom
design/s3-object-lock

Conversation

@frrist

@frrist frrist commented Aug 11, 2026

Copy link
Copy Markdown
Member

Implementation spec for S3 Object Lock, following the versioning-doc workflow: this PR is the design; the implementation follows as a stacked PR on this branch.

The shape of the feature: versitygw's controller layer owns every WORM decision (auth.CheckObjectAccess runs before Put/Copy/Delete/Complete reach the backend), so ingot's job is storage plus exact sentinel errors — no enforcement logic on our write or delete paths (§1–§2).

The decision to review (§3): where per-version retention and legal holds live.
Proposed: a per-key version-state tree — the leaf gains a State sub-tree beside Prev, mapping revSeqKey(seq) to a small VersionState block. Mutations rewrite only positional blocks, so manifests stay immutable and versioning invariant 5 holds verbatim; the state rides the catalog plane to Forge (the replication/recovery argument); stamping and cleanup are atomic with the commits that create and remove versions. The alternative (registry rows: simpler, SQL-queryable, but post-commit crash windows and catalog-incomplete) is specified alongside; manifest rewrite, inline leaf state, and a parallel bucket tree are rejected in Appendix A.

Also of note:

  • VersionState reserves a Tags field now so object tagging (next up) lands as handlers with zero format change; the merge/empty-elision/cleanup rules are written feature-generically (§4.1, §9).
  • Governance bypass defers to the bucket-policy feature: the controller authorizes bypass off the bucket policy document, so until policies land GOVERNANCE denies exactly like COMPLIANCE, with a documented same-mode-replacement escape hatch (§10).
  • Conformance scoping (§11): new itest lock categories, the TestWORMProtection exclusions (upstream's unversioned-gateway semantics that a versioned ingot bucket can never exhibit), and the XFail rows that flip when this lands.
  • §12 records the landing path for each deferred feature (policies, tagging, object ACLs, Batch Ops, lifecycle, materialized defaults) — none reshapes this design.

🤖 Generated with Claude Code

Object Lock as storage plus sentinel errors: versitygw's controller layer
owns all WORM enforcement (auth.CheckObjectAccess), so ingot's backend
stores lock state and returns the exact absent-case sentinels. Per-version
retention and legal holds live in a per-key version-state tree beside the
prev tree: state mutations rewrite only positional blocks (state block,
state tree, leaf, top path), so manifests stay immutable and version
identity (invariant 5 of the versioning design) holds verbatim, while the
state rides the catalog plane and ships to Forge. Stamping and cleanup are
atomic with the commits that create and remove versions. The VersionState
block is the general home for mutable per-version service state: lock is
its first tenant, and the reserved Tags field lets object tagging follow as
handlers with no format change. The registry-rows alternative is specified
alongside for review; manifest rewrite, inline leaf state, and a parallel
bucket tree are rejected in Appendix A. Governance bypass defers to the
bucket-policy feature: the controller authorizes bypass off the bucket
policy document, so until policies land GOVERNANCE denies exactly like
COMPLIANCE.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@frrist frrist self-assigned this Aug 11, 2026
Three details the implementation run against the versitygw suite pinned
differently than first specified: key existence outranks the lock-enabled
gate in the §6 check order (GetObjectRetention_non_existing_object runs
against a lock-free bucket and expects NoSuchKey, matching posix); the
missing-configuration error splits by path, with the spaced variant on the
four per-version methods and the NoSpaces variant on all three
creation-time header paths; and the controller passes an absent
retain-until header as a pointer to the zero time. §11 also records the
versioning-disabled-mode exclusion precisely and the versioned-teardown
home for lock-enabled-bucket cases from plain-conf groups.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Backwards compatibility is not a requirement at this stage: per the
repo's dev-only data posture there is no pre-lock reader to protect, so
the State field joins ObjectLeaf under the existing "/objectleaf/0"
union key with no compatibility shim. The union's format-revision
mechanism stays in reserve for revisions that need loud incompatibility.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@hannahhoward hannahhoward left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I did do a fairly close read but I don't see anything I object to. This looks like a very sound design. Thank you versitygw for enforcing the rules for us.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants