ADR-0009: Glob - Three-Tier Design¶
Status¶
| Field | Value |
|---|---|
| Status | Accepted |
| Supersedes | — |
| Superseded by | — |
| Amends | — |
Context¶
Glob/pattern matching for file listing has been an open design question
since v0.6.0 (BK-002, ID-007). The original Capability.GLOB was removed
in AF-002 because four backends claimed GLOB support with no glob()
method — a ghost capability.
The core tension: some backends have efficient native pattern matching (Local via pathlib, S3 via prefix filtering) while others have no server-side glob at all (SFTP, Memory). A single design must serve both cases without forcing a lowest-common-denominator approach.
An initial two-tier design (core capability + extension fallback) was
considered but rejected in review for three reasons: store.glob() throws
on most backends (discoverability pit), simple name filtering requires an
extension, and two entry points create confusion about which to use.
Decision¶
Three tiers of pattern matching, each covering a case the tier below cannot.
A single lowest-common-denominator API and a two-tier design were both
rejected: a bare store.glob() throws on most backends (a discoverability
pit), and simple name filtering should not require an extension.
- Tier 1 (
list_files(pattern=…)):fnmatchname filtering at the Store level. Works on every backend withLIST, no new capability; covers the common "give me the CSVs in this folder" case. Reverse if every backend gains cheap recursive matching, collapsing the need for higher tiers. - Tier 2 (
store.glob(pattern)): native backend glob, gated onCapability.GLOB. Likeunwrap(), opt-in direct access to a backend-specific feature for users who know their backend and want native semantics. The gate exists because native glob support is unequal across backends; only backends with a genuine native implementation declareGLOB(the current roster is spec-rate; see spec 018 § GLOB-005/018/019/020). Reverse if native glob becomes universal, making the gate meaningless. - Tier 3 (
ext.glob.glob_files(store, pattern)): portable full recursive glob. Delegates tostore.glob()whenGLOBis available, else falls back tolist_files+ client-side matching. This fallback is why the design is three tiers, not two: portable recursive glob cannot be guaranteed at the Store level. Reverse if a portable recursive glob can be guaranteed for every backend, letting Tier 3 fold into the Store API.
Pattern grammar, exact signatures, and the fnmatch/regex-converter mechanics
are spec-rate and live in spec 018 (Overview, GLOB-001, GLOB-005, GLOB-006,
GLOB-009, GLOB-014).
Consequences¶
- Pit of success. The easiest API (
list_files(pattern=)) works everywhere. Users only escalate when they need more power. unwrapanalogy holds.store.glob()is for users who know their backend, same asstore.unwrap().- Extension has a clear role.
ext.glob.glob_files()is for whenlist_files(pattern=)isn't enough (recursive patterns, directory wildcards) but you want portable code. - AF-002 reconciled.
Capability.GLOBis back, but justified: it gates native access, not the only way to filter.list_files(pattern=)needs onlyLIST. - Additive change.
patternparameter onlist_filesis optional and backward-compatible. No existing API is modified. - Future work. S3/Azure can implement prefix-optimized
glob()and declare GLOB without changing the contract.