docs: resolve discussion #14 — keyed capability surface (ADR-0011) - #17
Merged
Merged
Conversation
Adds ADR-0011 and amends 0001/0005/0006/0007/0010 + architecture.md + state-of-play.md. Settled (D4-D9): - delete_many becomes a free function, not a store method. Destructive + key-taking + delegated is the shape already destroying data in sibling packages. - Option E (capability as a Mapping-valued ATTRIBUTE) deferred: verified that a wrapper does NOT re-wrap such an attribute, so it needs the same key resolution as everything else and buys ergonomics, not correctness. - Option D (rebind delegated methods) rejected, citing dol's own issue-18 design doc: rebinding binds to the INNERMOST wrap, so it does not fix the Pipe case it exists to fix. - Prefix pushdown closed rather than deferred: a hint protocol would have one implementer, violating ADR-0009's own rule. - New upstream findings: export dol.dig.inner_most_key; dol.content_url resolves with the outer key; dol.filesys.is_valid_key is confirmed-live broken; wrapped_self has a lost-reference hole. Key finding that reframes ADR-0001: azuredol is robust because its container store has ~zero key-taking methods (the rich surface is on BlobHandle, keyed at construction), not because its prefix lives in the leaf. Prefix-in-leaf is necessary, not sufficient. D1/D2/D3 (how capabilities are surfaced) left as Proposed. An adversarial review refuted three claims of the first draft -- corrections folded in: - "undetectable" was false; a guard is writable - free functions are not categorically safe (they break on non-Store layers, where the method form is right) - s3_store(...).handle(k) is NOT an instance of the bug; only a user-applied key codec triggers it and surfaced a fourth option (capability as a sibling STORE, keyed through __getitem__ -- correct by construction) that the draft never evaluated. See the "Open fork" section. Every empirical claim was executed against the local dol 0.3.58 checkout; repros are in state-of-play.md section 9.
Replaces the WIP draft after an adversarial review refuted three of its claims and surfaced a better option. DECISION. Layer B carries no key-taking public methods. A keyed capability becomes a SIBLING STORE over the same key space -- s3dol.handles(store)[k] / .urls / .info -- keyed through __getitem__, the one thing dol maps correctly at every wrapper depth. Non-keyed operations (sub, prefixes, delete_many, delete_bucket) are free functions. url_for survives as a single guarded method purely to satisfy dol.SupportsUrlFor. This is correct BY CONSTRUCTION: sibling stores call no key-resolution primitive, so they are immune to both holes found below. Verified correct unreferenced, under Pipe, under cached_keys, under a value codec, and under a non-Store passthrough layer. WHY NOT THE ALTERNATIVES (all refuted by running code, not argument): - Hardened METHODS: inner_most_key(wrapped_self(self), k) is silently wrong when nothing references the wrapper -- and because the prefix lives in the leaf, the wrong answer is a plausible str, so a type check cannot catch it. Reproduced on CPython 3.10-3.14. - FREE FUNCTIONS everywhere: not categorically safe either. With a hand-rolled non-Store layer in the chain they return a plausible wrong key in three configurations, two of which the method form gets right. - Option E as an ATTRIBUTE: verified that a wrapper does not re-wrap a Mapping-valued attribute. Adopted as a STORE instead, which is what makes it work. The #14 lean (C+E with B) was right in substance; E's value comes from being a store, not an attribute. CORRECTIONS to the earlier draft, kept visible in the ADR: - "undetectable" was false; a guard is writable, so methods were rejected on cost/benefit, not impossibility - s3_store(...).handle(k) is NOT an instance of the bug; s3_store returns a bare leaf or a value-codec wrap, both correct. Only a user-applied KEY codec triggers it - the census is overwhelmingly LATENT, not shipping-destructive, and 12 of the survey claims were refuted outright Also: EndpointStore.delete becomes s3dol.delete_bucket (last destructive delegated method removed); ADR-0011 added to the docs index; the superseded surface-growth rule marked in 0005 and 0009; 0001/0006/0007/0010 amended.
This was referenced Aug 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves the first of the two open v1 design questions: discussion #14, layered
transformations and the unmapped-key problem. Docs only — no code.
Decision
Layer B carries no key-taking public methods. A keyed capability becomes a sibling store
over the same key space —
s3dol.handles(store)[k],.urls,.info— keyed through__getitem__, which is the one thingdolmaps correctly at every wrapper depth. Non-keyedoperations (
sub,prefixes,delete_many,delete_bucket) are free functions taking thestore first.
url_forsurvives as a single guarded method purely to satisfydol.SupportsUrlFor.This is correct by construction: sibling stores invoke no key-resolution primitive, so they
are immune to both failure modes found below. Verified correct unreferenced, under
Pipe, undercached_keys, under a value codec, and under a hand-rolled non-Storepassthrough layer.Why not the alternatives — all refuted by running code
inner_most_key(wrapped_self(self), k)— the form ADR-0006 §2prescribed — is silently wrong when nothing holds a reference to the wrapper. Because the
prefix lives in the leaf, the wrong answer is a plausible
str, so a type check cannotcatch it. Reproduced on CPython 3.10–3.14.
Storelayer in thechain they return a plausible wrong key in three configurations, two of which the method form
gets right.
(verified). Adopted as a store instead — which is what makes it work. Design: layered transformations and the unmapped-key problem #14's lean (C+E with
B) was right in substance; E's value comes from being a store, not an attribute.
dol/misc/docs/dol_issue18_design.mdwithbetter reasons than blast radius: rebinding binds to the innermost wrap, so it does not fix
the
Pipecase it exists to fix.Corrections carried in the ADR
An adversarial review refuted three claims of the first draft. All are kept visible rather than
quietly fixed:
not impossibility.
s3_store(...).handle(k)is not an instance of the bug:s3_storereturns a bare leaf ora value-codec wrap, both correct. Only a user-applied key codec triggers it.
claims were refuted outright.
Also in this PR
endpoint.delete(name, force=True)becomess3dol.delete_bucket(endpoint, name, force=True).dol.dig.inner_most_key, and fixdol.content_url, which resolves with the outer key and so blocks thedol.contentintegration that
dol/content.pyexplicitly names s3dol for.wrapped_selflost-reference hole, anddol.filesys.is_valid_key, which is confirmed-live broken.0001/0006/0007/0010 amended.
Every empirical claim was executed against the local
dol0.3.58 checkout; runnable repros arein
state-of-play.md§9.Discussion #14 stays open until this merges, then gets the resolution comment.