Resolver — resolve and probe¶
Resolver walks a CredentialRef through a fixed
chain of sources and returns the first hit.
Constructing a Resolver¶
| Constructor | Backend |
|---|---|
Resolver::new(store: Arc<dyn CredentialStore>) |
the store supplied |
Resolver::with_platform_default() |
KeyringStore::new() |
Resolver::default() |
identical to with_platform_default() |
All three are infallible and do no I/O. The injected store is used for the keychain leg only — the environment and literal legs do not consult it.
use rtb_credentials::{MemoryStore, Resolver};
use std::sync::Arc;
let resolver = Resolver::new(Arc::new(MemoryStore::new()));
The resolution chain¶
resolve returns the first source that produces a value:
cref.env—std::env::var(name). A set variable is a hit, even if its value is the empty string.cref.keychain—store.get(service, account)on the injected store.cref.literal— the embedded secret, unlessCI=true.cref.fallback_env—std::env::var(name).
A key that is absent is skipped. If every configured leg misses,
resolve returns CredentialError::NotFound.
The order is fixed in code and there is no way to reorder, disable or extend it. A tool that needs different precedence has to implement its own walk over the four fields.
A keychain backend error aborts the walk¶
The keychain leg distinguishes two kinds of failure:
| Store returns | Resolver does |
|---|---|
CredentialError::NotFound |
falls through to the next leg |
| any other error | returns that error immediately |
A locked, unavailable or misconfigured keychain therefore fails the
whole resolution even when literal or fallback_env is configured and
would have produced a value. Only a clean "no such entry" continues the
chain.
This is why a machine with a broken keychain can fail to start a tool that appears, from its config, to have a perfectly good environment-variable fallback.
literal under CI=true stops the chain rather than falling through¶
When the literal key is present and the process is running under
CI=true, resolve returns CredentialError::LiteralRefusedInCi.
It does not continue to fallback_env. A reference carrying both a
literal and a fallback_env fails in CI even when the fallback
variable is set and readable:
anthropic:
literal: sk-ant-... # present
fallback_env: ANTHROPIC_API_KEY # set in CI, but never reached
Removing the literal key from config that runs in CI is the way to
make the fallback reachable.
What counts as CI¶
The check is exact-match, case-sensitive:
Only the literal string true triggers refusal. CI=1, CI=TRUE,
CI=True and CI=yes all leave literals permitted. See
environment variables for which platforms
set the value this crate expects.
Which name appears in a NotFound error¶
When nothing resolves, the name field of NotFound is chosen by this
order — which is not the resolution order:
fallback_env, if set- otherwise
env, if set - otherwise
"{service}/{account}"fromkeychain, if set - otherwise the literal string
<unnamed credential>
A reference with both env: MYTOOL_KEY and
fallback_env: ANTHROPIC_API_KEY reports
credential not found: ANTHROPIC_API_KEY, naming the ecosystem variable
rather than the tool's own. The message names one source, not every
source that was tried.
probe reports the winning source without returning the secret¶
probe walks the same chain and reports which leg would win.
ResolutionOutcome |
Meaning |
|---|---|
Resolved(ResolutionSource) |
this leg produced a readable value |
LiteralRefusedInCi |
literal is configured and CI=true |
Missing |
nothing resolved |
ResolutionSource is Env, Keychain, Literal or FallbackEnv.
Both enums are #[non_exhaustive], so a match on either needs a
wildcard arm and adding a variant is not a breaking change.
LiteralRefusedInCi is an Ok outcome from probe and an Err from
resolve. The split is deliberate: a status command wants to display
"refused in CI" as a state, not to fail.
probe does the same I/O as resolve¶
probe is not a cheap, side-effect-free inspection. When keychain is
configured it performs a real store.get, reading the secret and
dropping it. On a platform keychain that means a genuine round-trip per
reference, and on some systems an unlock prompt.
What probe avoids is returning the secret to the caller, so a status
command can list credentials without a secret reaching a terminal.
probe and resolve can disagree on an empty keychain entry¶
probe's environment legs test std::env::var(name).is_ok(), and its
keychain leg tests whether get returned Ok. These are the same
conditions resolve uses, so the two agree on which leg wins.
The one behaviour worth knowing is shared by both: a configured source
holding an empty string counts as a hit. probe reports Resolved, and
resolve returns an empty SecretString. Neither treats "set but
empty" as a miss, so a credential can resolve successfully to nothing
and fail later at the point of use.