Skip to content

fix: key-taking operations become free functions that honour dol wrappers (#1) - #2

Merged
thorwhalen merged 2 commits into
masterfrom
fix/1-key-taking-free-functions
Oct 13, 2026
Merged

thorwhalen merged 2 commits into
masterfrom
fix/1-key-taking-free-functions

Conversation

@thorwhalen

@thorwhalen thorwhalen commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

Closes #1.

What

Every store method that took a key ran bound to the leaf under a dol wrapper and got the caller's unmapped key. They move to cosmodol.ops (re-exported from cosmodol), each taking the store first and mapping the key through every layer with dol.inner_most_key:

old method new function
CosmosItems.replace(k, v, etag=) replace_item(store, k, v, etag=) (value via each layer's _data_of_obj)
CosmosItems.batch(sdk_triples) batch_items(store, [(op, k[, v]), ...]): ids are now explicit, hence mappable
CosmosPartitionedItems.partition(pk) partition(store, pk, *, wrap=None): refuses a wrapped store unless wrap= says how to wrap the child
CosmosDatabase.add_container(name, ...) add_container(db_store, name, ...)
CosmosDatabase.delete(name, force=) delete_container(db_store, name, force=)
CosmosAccount.add_database(name, ...) add_database(account_store, name, ...)
CosmosAccount.delete(name, force=) delete_database(account_store, name, force=)

The old names raise an AttributeError naming the replacement (so hasattr is False and class-wrapping still works). query stays a method (it takes SQL, not a key) and is now documented as speaking raw Cosmos ids/documents. A conformance test inventories every public method of the four store classes and fails on any new one, forcing the "does this take a key?" judgement at review. Design follows s3dol ADR-0011: free functions are handed the outer store, so they are also immune to wrapped_self's lost-reference hole (tested with a temporary wrapper).

Also fixed

  • delete_container(force=True) deleted each item with partition_key=item.get("_partitionKey"), a field that does not exist unless the pk path is literally /_partitionKey; it now projects the container's real partition-key path.
  • FakeContainerProxy.execute_item_batch (all-or-nothing) so batches are testable offline.
  • The cross-partition warning and docs pointed at a .with_filter(...) that was never implemented.

Breaking

Pre-1.0 (0.0.x), no fleet dependents (no package in the fleet imports cosmodol). Existing tests that called the removed methods were migrated to the functions with the same assertions.

Tests

All issue repros, on both delegation routes (class-wrap and instance-wrap): tests/test_ops.py. pytest --doctest-modules cosmodol: 63 passed (emulator tests deselected as before).

Adversarial review

A fresh Opus reviewer read the diff and the issue and tried to break it. Verdict: ship-after-fixes, 4 confirmed defects, all fixed in the second commit:

  1. Wrappers that scope rather than map (filt_iter) were bypassed by destructive ops. Fixed: operations on existing resources first check k in store through the outer store (replace_item, batch read/replace/patch/delete, forced delete_container/delete_database). The docstring now states exactly what a function can and cannot honour.
  2. add_database created the database then raised when the wrapper hid the name. Fixed: returns the leaf's value.
  3. Hierarchical (MultiHash) partition keys broke the forced cascade. Fixed: projected as a list; items without a partition key use NonePartitionKeyValue.
  4. from dol import inner_most_key needs dol >= 0.3.59. Fixed: pinned.

Also taken from its "plausible" list: per-op kwargs in batch_items (ETag-conditional ops), a clear error for raw SDK triples, a conformance test that also sees classmethods and overridden Mapping methods, a clearer partition() refusal. Deferred (not regressions; the old methods bypassed them too): write guards, value caches, preset= hooks, scope on creating ops → #3.

Behaviour change worth noting: delete_container(db, name) without force is del db[name] and refuses a non-empty container (the old db.delete(name) dropped it).

🤖 Generated with Claude Code

thorwhalen and others added 2 commits October 13, 2026 04:34
…pers (#1)

Every non-dunder method of the stores that took a key (replace, batch,
partition, add_container, add_database, CosmosDatabase.delete,
CosmosAccount.delete) ran bound to the leaf under a dol wrapper and acted on
the caller's unmapped key: under a key codec, replace overwrote another
document, delete(force=True) emptied and dropped the wrong container or
database, and partition() stripped every wrapper.

They move to cosmodol.ops (re-exported): replace_item, batch_items,
partition, add_container, delete_container, add_database, delete_database.
Each takes the store first and maps the key through every layer with
dol.inner_most_key (values through _data_of_obj). partition refuses a wrapped
store unless given wrap=. batch_items takes (op, key[, value]) so its ids are
mappable. The old method names raise AttributeError naming the replacement;
a conformance test inventories every public method.

Also: delete_container(force=True) now deletes items with the container's real
partition key (the old cascade read a non-existent _partitionKey field), and
FakeContainerProxy gains execute_item_batch.

Same design as s3dol ADR-0011.

Closes #1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iew of #2)

- Operations on existing resources check k in store through the outer store
  first, so a filt_iter scope stops replace_item, batch read/replace/patch/
  delete, and the forced delete_container/delete_database.
- add_database returns the leaf's value instead of re-reading through the
  wrapper (which could raise after the database was created).
- Forced container delete projects hierarchical (MultiHash) partition keys as
  a list and passes NonePartitionKeyValue for items without one.
- batch_items accepts per-op SDK kwargs and names the SDK-triple mistake.
- Conformance test also sees classmethods and overridden Mapping methods.
- dol>=0.3.59 (inner_most_key exported).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thorwhalen
thorwhalen merged commit 3c8129b into master Oct 13, 2026
12 checks passed
@thorwhalen
thorwhalen deleted the fix/1-key-taking-free-functions branch October 13, 2026 04:43
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.

Key transforms silently leak: every non-dunder method of the cosmodol stores acts on the UNMAPPED key (four are destructive)

1 participant