ADR-0005: Bidirectional Path Resolution via to_key¶
Status¶
| Field | Value |
|---|---|
| Status | Accepted |
| Supersedes | — |
| Superseded by | — |
| Amends | — |
Context¶
The Store API has a round-trip problem. Users pass store-relative keys
(e.g. "reports/q1.csv") into Store methods, and the Store joins them with
root_path before delegating to the backend. But the return path is broken:
list_filesandget_file_infodelegate to the backend, which returnsFileInfowith paths relative to the backend root — these include the store'sroot_pathprefix. AStore(root_path="data")listing returnsFileInfo.path = "data/reports/q1.csv", not"reports/q1.csv".- If the user feeds that path back into
store.read(str(info.path)), the Store prependsroot_pathagain →"data/data/reports/q1.csv"→NotFound.
A second, related problem: users receive absolute or backend-native paths from external sources (SFTP server logs, S3 event notifications, filesystem watchers) and need to convert them to store-relative keys. No public helper exists for this.
Both problems reduce to the same missing primitive: convert a backend-native/absolute path to a store-relative key.
Current ad-hoc handling¶
Each backend strips its own root differently:
- Local: Path.relative_to(self._root) inline in listing methods.
- S3: _rel_path() helper strips the bucket prefix.
- SFTP: String concatenation from input path + filename (no dedicated helper).
None of them strip the store root — that responsibility belongs to the Store layer, which currently doesn't do it at all.
Decision¶
- A
to_keyprimitive at two levels,Backend.to_keyandStore.to_key, same-named by design (clear intent, composable). Both the broken listing round-trip and external-path conversion reduce to one operation: stripping a layer's own root.Backend.to_keystrips the backend's native root/prefix;Store.to_keycomposes that with strippingroot_pathto yield a store-relative key. Reverse if a use case needs a path transform that is not root-stripping; thento_keyis the wrong primitive, not merely under-featured. Backend.to_keyis a concrete ABC method with an identity default. Only backends with a custom root override it; every other backend inherits the identity, so the change is zero-behavioral for them: the backward-compatibility invariant. Reverse if the identity default ever yields a wrong key for an un-overridden backend, which would mean the default is unsafe and the method must become abstract.to_keyis pure, deterministic, and total (no I/O, no side effects). That purity is what makes a concrete default safe to inherit and lets the inverse round-trip hold. Reverse if a backend's native→key mapping genuinely requires I/O, breaking totality.- The Store owns the round-trip guarantee. The Store layer strips
root_path; backends only know their own root. The path-returning listing methods (list_files,get_file_info,get_folder_info) striproot_pathso returned paths feed back into Store methods without double-prefixing;list_foldersreturns immediate subfolder names and is unaffected (spec 010 § NPR-015). Reverse if backends must become root-aware for another reason, making a single Store-level strip point insufficient.
Exact signatures, per-backend stripping examples, the composition sequence, and the full methods-that-strip enumeration are spec-rate and live in spec 010 (NPR-003, NPR-006/007/008, NPR-010/011, NPR-014/015).
Consequences¶
- Round-trip works —
FileInfo.pathfrom listing is directly usable as input toread,write,delete, etc. - External paths are supported — users can convert absolute paths from logs, events, and other systems to store keys via a public API.
- Backend path logic is centralized — each backend defines its native→relative conversion in one place instead of scattering it.
- RemotePath is untouched — all PATH-* spec invariants remain in force.
- Backward compatible — identity default means zero behavioral change for backends that don't override.