v1.x — deferred collections interfaces (uploads, versions, tags, bucket config, annotations, fsspec) #12
Description
Deferred from v1 (ADR-0009). Each of these is a genuinely natural collections.abc fit; they wait because v1 scope is core + large-object I/O, and because over-scoping is the top risk (zarr's v3 rewrite replaced a MutableMapping store interface with a 15-method async ABC and killed five of its own backends in the process).
Roughly in the order they should arrive:
- In-flight multipart uploads as a
Mapping.ListMultipartUploads+AbortMultipartUploadis literally a mapping whose__delitem__aborts:for k in uploads: del uploads[k]replaces the loop AWS's own docs tell you to write. Orphaned uploads are the classic silent S3 bill. Highest portability of this set, andclient.exceptions.NoSuchUploadactually exists (one of only 13 modelled S3 error shapes), so it maps cleanly toKeyError. Key:(key, upload_id). - Object versions (+ a delete-marker view). Key:
(key, version_id)orversion_id. - Object tags (
MutableMapping[str, str], ≤10 entries) and user metadata (x-amz-meta-). Both natural, both portability 2–4/5, so each needs capability detection. Note user-metadata writes are aCopyObject— surprising enough to document loudly. - Bucket configuration as one
Mapping:cfg['lifecycle'] = {...}over the Get/Put/Delete triples (lifecycle, cors, policy, encryption, tagging, versioning, …). One spec-driven store rather than 20 methods. - Object annotations. Natively a per-object
MutableMapping[str, bytes]— the first S3 feature that is a nested store rather than a flat blob store, which for adol-shaped library is a gift:store.annotations['some/key']['embedding'] = .... Deferred on two hard facts, not design: no S3-compatible provider implements it (portability 0/5), and it needs a botocore floor too fresh for a storage library. Feature-detect withhasattr(client, 'put_object_annotation'). - fsspec adapter —
to_fsspec()/from_fsspec(). One adapter buys pandas, dask, pyarrow, and zarr-v3-via-FsspecStore. The mandatory surface is small:_strip_protocol+ls(detail=True)+_open/_fetch_range. s3dol[fast]— obstore for the object level only (it has no bucket operations), if an independent re-measurement justifies it.s3dol.aio— async mirroring the sync surface.
Explicitly never
GetObjectTorrent— dead.SelectObjectContent— AWS closed it to new customers in July 2024 and points at Athena / Object Lambda. A Mapping over a service new users cannot enable is negative value.- Native async
StoreABC / zarr-v3 store. An s3dol store is already a working zarr v2 store for free. zarr v3 leftMutableMappingbehind deliberately, for async and byte-range coalescing; chasing it means becoming a different package. Adapt via fsspec instead.
Not ours at all → botodol
The line is mechanical — the endpoint test: can it be reached through the same boto3.client('s3', endpoint_url=...) a MinIO or R2 user already holds? If not, it isn't s3dol's. s3control is a separate service model with a required AccountId that no S3-compatible provider implements; same for s3tables, s3vectors, s3outposts, glacier. That test puts access points, batch jobs, Storage Lens, Access Grants and MRAP in botodol, and it never needs arguing.
Two cases the test resolves correctly rather than by exception: access points are consumed as bucket names (so s3dol accepts an access-point ARN wherever it accepts a bucket name — one line in the normalizer; administering them is botodol), and S3 Express / directory buckets are one row in the preset registry, not a new abstraction.
One obligation this creates: the spec-driven sub-resource store (item 4) and the paginated-list store are boto-generic, not S3-specific — the same code serves DynamoDB tables, SQS queues, Secrets Manager secrets. When built, they go in a documented public module and botodol depends on s3dol for them. Don't build a shared base package speculatively; don't let botodol reimplement them.
Activity
thorwhalen commentedon Aug 10, 2026
ADR-0011 (merged in #17) changes the shape of three deferrals here, and makes them cheaper
rather than closing them.
A presigned-URL view, an
ObjectInfo-from-LIST view, and object handles are no longer threeseparate features: under ADR-0011 §D2 they are three instances of one mechanism — a sibling
Layer B
KvReaderover the same key space, sharing a private base withBucketReader. SoBucketHandleslands in v1 (it is the only per-object entry point), andBucketUrls/BucketInfobecome near-free once it exists.What stays deferred is the attribute form —
store.urls[k]as a property on the data store.That is verified broken at dol 0.3.58: a wrapper does not re-wrap a Mapping-valued attribute, so
store.urlsreturns an inner-keyed mapping andKeyErrors for every key the outer store has.Making it work needs upstream re-wrapping (i2mint/dol#10) or the
.metasidecar design; trackedthere, not here.
Consequence for users in v1: if you wrap the data store with a key codec, wrap the sibling in
parallel.
s3dol.handles(store)raises on a wrapped store rather than silently handing back anunwrapped sibling.
thorwhalen commentedon Aug 11, 2026
ADR-0012 (merged in #18) adds
one deferral here and removes an assumption.
Newly deferred:
anon='auto'. v1 shipsanon: bool. Wideningbool → bool | 'auto'later isfully compatible and nothing in the ecosystem uses it today. The reason to defer is the shape of
the evidence rather than the difficulty: across three review rounds the guard count went
4 → 6 → 7, each round finding another silent-credential-downgrade path — which is the signal
a feature whose purpose is to guess emits. It is the only path on which s3dol could silently
read the public view of data the caller believed was authenticated; every other credential
path either resolves or fails loudly. Re-decide it on real
diagnose()telemetry: if nobody setsit, the case is closed.
Not deferred, contrary to what one might assume: provider detection from a bare endpoint URL
supplies the full preset row in v1, including the ADR-0003 §3 checksum fix. Measured, only
addressing_styleandendpoint_urlcan move the wire target — checksum and payload-signingprovably cannot — so the blast radius of detection is exactly one announced path→virtual flip,
and all three known endpoint-passing dependents resolve to
generic-s3, byte-identical to today.Also removed from the deferral space: a live-boto3-client escape hatch on
S3Connectionwasconsidered and rejected rather than deferred, so it should not resurface as a v1.x item. It
would create two permanent classes of connection — one picklable, one not — and no known
dependent passes
client=.