fix(rag): fail loudly when a RAG's vault secret is missing

`interpolate_secrets` does not error on a secret the vault cannot resolve:
it substitutes the empty string and returns the name in its second tuple
element. `load_async` discarded that vec, so a typo'd or deleted vault
secret produced `api_key = ""` and an unexplained 401 from Qdrant.

Bail instead, naming the RAG and the missing secrets, matching what global
config loading already does.
This commit is contained in:
2026-08-11 13:44:00 -06:00
parent ecda258d3a
commit 93a934439b
+14 -2
View File
@@ -377,10 +377,22 @@ impl Rag {
let api_key: Option<String> = match data.driver_config.get("api_key") {
Some(placeholder) => {
let (resolved, _) =
interpolate_secrets(placeholder, vault).with_context(|| {
let (resolved, missing) = interpolate_secrets(placeholder, vault)
.with_context(|| {
format!("Failed to resolve api_key secret for RAG '{name}'")
})?;
// A secret the vault does not hold is NOT an error inside
// `interpolate_secrets`: it substitutes an empty string and
// only reports the name. Accepting that silently attaches with
// `api_key = ""`, and the user sees an unexplained 401 from the
// server instead of the typo they made.
if !missing.is_empty() {
bail!(
"RAG '{name}' references secrets that are missing from the vault: {}. \
Add them with `coyote --add-secret <name>`, then try again.",
missing.join(", ")
);
}
Some(resolved)
}
None => None,