Show where a credential resolves from¶
Two pieces combine into a "where do my credentials come from" command:
CredentialBearing enumerates the credentials a config carries, and
Resolver::probe reports which source would win without returning the
secret.
Enumerate your config's credentials¶
Implement CredentialBearing on the struct that owns them:
use rtb_credentials::{CredentialBearing, CredentialRef};
struct Config {
anthropic: Section,
github: Section,
}
struct Section { cred: CredentialRef }
impl CredentialBearing for Config {
fn credentials(&self) -> Vec<(&'static str, &CredentialRef)> {
vec![
("anthropic", &self.anthropic.cred),
("github", &self.github.cred),
]
}
}
The &'static str is the operator-facing name for the credential — the
thing you print, and the thing you would accept as a command argument.
There is no derive macro. The list is written by hand, which means a credential added to the config and not added here is silently absent from every listing.
Probe each one¶
use rtb_credentials::{ResolutionOutcome, ResolutionSource, Resolver};
for (name, cref) in config.credentials() {
let status = match resolver.probe(cref).await? {
ResolutionOutcome::Resolved(ResolutionSource::Env) => "environment variable",
ResolutionOutcome::Resolved(ResolutionSource::Keychain) => "OS keychain",
ResolutionOutcome::Resolved(ResolutionSource::Literal) => "literal in config",
ResolutionOutcome::Resolved(ResolutionSource::FallbackEnv) => "fallback variable",
ResolutionOutcome::LiteralRefusedInCi => "literal — refused under CI",
ResolutionOutcome::Missing => "not configured",
_ => "unknown",
};
println!("{name}: {status}");
}
Both ResolutionOutcome and ResolutionSource are
#[non_exhaustive], so the wildcard arm is required and new variants
will not break the build.
Know what probe costs¶
probe is not a cheap inspection. When keychain is configured it
performs a real store.get, reads the secret and drops it. Against a
platform keychain that is a genuine round-trip per credential, and on
macOS it can raise an unlock prompt.
What it avoids is handing the secret back to your code, so the value cannot reach a terminal or a log by accident.
Treat LiteralRefusedInCi as a state, not an error¶
probe returns LiteralRefusedInCi as an Ok outcome, where
resolve returns it as an Err. That split exists precisely so a
status command can display the condition instead of failing on it.
Showing it is worth doing: it is the case where the credential is configured and readable but will not be used, which is otherwise very hard for an operator to diagnose.
Use () when a tool has no typed config yet¶
CredentialBearing has a blanket implementation for () returning an
empty Vec, so code generic over the trait compiles before a tool has
declared any credentials.
The trait is object-safe, so Box<dyn CredentialBearing> and
&dyn CredentialBearing both work.
What this does not tell you¶
A Resolved outcome means a source produced a value, not that the value
is a working credential. Nothing validates length, format or
authenticity, and a source holding an empty string reports as
Resolved.
A status command built this way answers "where would this come from", not "does this work".