Holding credentials is a posture¶
This module offers two ambient rungs that do the same work and differ only in what they keep:
azureclient.Ambient(...) // resolve once, share the result
azureclient.PerCall(...) // resolve afresh every time, retain nothing
PerCall is not a degraded Ambient. It exists because how long a component
holds a credential is a security decision belonging to that component, not to
this module.
A signing component may deliberately decline to hold an identity between mints, so that credentials are not resident longer than an operation requires. Offering only a memoising rung would force such a component either to keep its own duplicate resolution — defeating the point of a shared module — or to change its posture to adopt ours.
Both return the same Source, so swapping is one word at the construction site.
Concurrent PerCall calls are deliberately not collapsed into one attempt:
collapsing would share a resolution between callers who explicitly asked not to
share one.
Why isolation is the default¶
Two components that each take their ambient default resolve the chain twice, on purpose. A hidden process-wide cache would silently share an identity between components that may deliberately differ — a different tenant, a different managed identity — and would make one component's transient failure everybody's, invisibly.
Sharing is therefore something you do deliberately, by building one source and injecting it.
The footprint boundary¶
Resolving DefaultAzureCredential costs +7 modules — azidentity, MSAL,
golang-jwt, pkg/browser and their graph — measured on the config adapters.
That is why importing this module is an explicit act, and why the Azure-backed
config adapters put their ambient rung behind an ambient subpackage. A consumer
who never wants ambient credentials never pays for them, and each subpackage
carries a test asserting its own cost.
The estate rule: an adapter's root package must not grow the credential
graph. If you never import the ambient subpackage, your footprint is exactly
what it was before the ladder existed.
The token refreshes; the credential is a factory¶
An azcore.TokenCredential is not a token — it is a thing that produces tokens.
The azcore pipeline calls GetToken again whenever the current one is near
expiry, so a long-lived process holding a credential from Ambient does not go
stale.
That distinction — a means of obtaining a credential versus a credential — decides whether a value is safe to hold indefinitely. Azure, AWS and GCP all hand out a means. Vault and Consul hand out a token, which is why those go stale and these do not.
Where a value cannot renew,
clientlifecycle offers an
invalidatable strategy. Azure does not need it, and taking it here would be
strictly worse: an invalidation path nobody should call, and a fan-out hazard if
anyone does.
Where this is specified¶
org spec 0003 — isolation as the default (P-5), the non-memoised rung (P-12), the import boundary (P-7), and the typed-nil validation (P-13).