ADR-0030: Azure HNS Is an Explicit Declaration, Not Auto-Detected¶
Status¶
Accepted
Context¶
ADLS Gen2 (Hierarchical Namespace, HNS) accounts and plain Blob Storage
accounts differ in semantics the Azure backend must adapt to: atomic
rename_file vs copy+delete move, real directories vs virtual prefixes,
single-call recursive delete vs iterate-and-delete. The backend originally
discovered which mode an account was in by probing GetAccountInfo
(get_account_information() -> is_hns_enabled) on first use and caching the
result for the instance lifetime.
That implicit probe was the root cause of a family of failure modes:
- Sticky misdetection. A wrong cached result (e.g. an RBAC-propagation
403on the account-level call right after provisioning) degraded an HNS account to flat semantics for the entire instance lifetime. - Per-operation re-probe storm. The interim mitigation (do not cache a failed probe) meant a persistently failing probe re-ran on every operation.
- Torn reads within one operation. Because an uncached probe could
re-evaluate mid-operation,
move/copyhad to snapshot the HNS state at entry so a single logical operation could not straddle the HNS and non-HNS code paths.
Each mitigation bounded the damage without removing the cause: the account's nature was being guessed at runtime from a network call that can fail, return stale authorization state, or be denied by least-privilege credentials.
The account's HNS status is a fixed, deployment-time fact. Treating a fixed fact as a runtime discovery is the design error.
Decision¶
The Azure backend no longer auto-detects HNS. The account's nature is a mandatory, explicit constructor and config input:
AzureBackend(..., hns: bool)andAsyncAzureBackend(..., hns: bool)— there is no default. A backend constructed withouthns, or with a non-boolvalue, raisesValueErrorat construction time (fail loud, never silently infer). The declaration must be a real boolean, not a truthy/falsy proxy: config env-var resolution yields strings, so a${VAR}placeholder resolving to"false"would otherwise coerce toTrueviabool(...)and silently re-enable HNS — the very misdetection class this decision removes._hnsbecomes an immutable attribute set from the declared value. No probe, no cache, no warn-once state, no per-operation snapshot.
For users who do not know an account's HNS status, a public one-shot helper discovers it explicitly:
AzureUtils.detect_hns(...)(sync) andAzureUtils.adetect_hns(...)(async) issue a singleGetAccountInfocall and return abool. Unlike the former implicit probe, these are fail-loud: a probe error is mapped to aremote_storeerror and raised, not swallowed and degraded to flat semantics.
This mirrors the established helper pattern for connection facts that are
discoverable but should not be silently inferred: SFTPUtils.scan_host_keys
and GraphUtils.resolve_drive_id.
Why mandatory rather than a detected default¶
A detected default reintroduces every failure mode above: the probe can fail,
return stale authorization state, or be denied. A declared value cannot. The
small one-time cost — the user must state a fact they already know, or call
detect_hns() once — buys the removal of an entire failure class and makes the
backend's behaviour deterministic from construction.
Migration¶
This is a breaking change: every existing Azure call site must add hns=.
Pre-v1 semver permits the change in a minor bump. The migration guide documents
the before/after and the detect_hns() discovery recipe.
Consequences¶
- The backend is deterministic from construction: no network call decides which semantics apply, and no operation can observe a different HNS state than the one declared.
- The sticky-misdetection, re-probe-storm, and torn-read failure modes are removed by construction, not bounded.
AzureUtils.detect_hns()/adetect_hns()join the public API as the sanctioned discovery path, fail-loud by contract.- Existing users must declare
hns=; the change is breaking and documented in the migration guide. - The
GetAccountInfoprobe logic is relocated (intodetect_hns), not deleted — discovery remains available, but only when the user explicitly asks for it.