Skip to content

docs: resolve discussion #14 — keyed capability surface (ADR-0011) - #17

Merged
thorwhalen merged 2 commits into
masterfrom
claude/adr-0011-keyed-capability-surface
Aug 10, 2026
Merged

docs: resolve discussion #14 — keyed capability surface (ADR-0011)#17
thorwhalen merged 2 commits into
masterfrom
claude/adr-0011-keyed-capability-surface

Conversation

@thorwhalen

Copy link
Copy Markdown
Member

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 thing dol maps correctly at every wrapper depth. Non-keyed
operations (sub, prefixes, delete_many, delete_bucket) are free functions taking the
store first. url_for survives as a single guarded method purely to satisfy
dol.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, under
cached_keys, under a value codec, and under a hand-rolled non-Store passthrough layer.

Why not the alternatives — all refuted by running code

  • Hardened methods. inner_most_key(wrapped_self(self), k) — the form ADR-0006 §2
    prescribed — 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 cannot
    catch it. Reproduced on CPython 3.10–3.14.
  • Free functions everywhere. Not categorically safe either: with a 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. A wrapper does not re-wrap a Mapping-valued attribute
    (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.
  • Option D (rebind). Already rejected upstream in dol/misc/docs/dol_issue18_design.md with
    better reasons than blast radius: rebinding binds to the innermost wrap, so it does not fix
    the Pipe case 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:

  • "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 family census is overwhelmingly latent, not shipping-destructive, and 12 survey
    claims were refuted outright
    .

Also in this PR

  • The last destructive delegated method is removed: endpoint.delete(name, force=True) becomes
    s3dol.delete_bucket(endpoint, name, force=True).
  • Two new blocking upstream items (ADR-0006 §3): export dol.dig.inner_most_key, and fix
    dol.content_url, which resolves with the outer key and so blocks the dol.content
    integration that dol/content.py explicitly names s3dol for.
  • Two non-blocking upstream findings: the wrapped_self lost-reference hole, and
    dol.filesys.is_valid_key, which is confirmed-live broken.
  • ADR-0011 added to the docs index; the superseded surface-growth rule marked in 0005 and 0009;
    0001/0006/0007/0010 amended.

Every empirical claim was executed against the local dol 0.3.58 checkout; runnable repros are
in state-of-play.md §9.

Discussion #14 stays open until this merges, then gets the resolution comment.

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.
@thorwhalen
thorwhalen merged commit 2422c54 into master Aug 10, 2026
8 checks passed
@thorwhalen
thorwhalen deleted the claude/adr-0011-keyed-capability-surface branch August 10, 2026 16:20
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.

1 participant