Reject drifted claw-analog bootstrap phases

The analog RAG/bootstrap formatter already rejected missing and literal unknown phases, but still accepted arbitrary phase strings. Keep the parser aligned with the runtime/service contract by allowlisting the known emitted phases and returning the existing typed error shape with received value and field context when drift appears.

Constraint: Scope is limited to claw-analog bootstrap/RAG phase parsing for ROADMAP #693.
Rejected: Preserve arbitrary non-empty phases | would continue hiding producer/parser phase drift.
Confidence: high
Scope-risk: narrow
Directive: Update KNOWN_RAG_BOOTSTRAP_PHASES when claw-rag-service deliberately adds a new response phase.
Tested: cargo fmt --manifest-path rust/Cargo.toml --all -- --check; cargo test --manifest-path rust/Cargo.toml -p claw-analog rag_response -- --nocapture; cargo test --manifest-path rust/Cargo.toml -p claw-analog -- --nocapture; cargo check --manifest-path rust/Cargo.toml -p claw-analog; cargo check --manifest-path rust/Cargo.toml --workspace; git diff --check
Not-tested: Full workspace cargo test; remote CI
This commit is contained in:
Yeachan-Heo
2026-05-25 19:33:37 +00:00
parent 590b5b614c
commit 789ea9aac8

View File

@@ -1109,25 +1109,33 @@ enum BlockKind {
},
}
const KNOWN_RAG_BOOTSTRAP_PHASES: &[&str] =
&["1-sqlite-no-db", "1-sqlite-empty", "1-sqlite", "2-qdrant"];
fn unknown_bootstrap_phase_error(received_value: Value, message: &str) -> String {
json!({
"kind": "unknown_bootstrap_phase",
"field": "phase",
"received_value": received_value,
"allowed_values": KNOWN_RAG_BOOTSTRAP_PHASES,
"message": message,
})
.to_string()
}
pub(crate) fn format_rag_query_json_for_model(body: &str) -> Result<String, String> {
let v: Value = serde_json::from_str(body).map_err(|e| format!("invalid JSON: {e}"))?;
let phase = v.get("phase").and_then(|x| x.as_str()).ok_or_else(|| {
json!({
"kind": "unknown_bootstrap_phase",
"field": "phase",
"received_value": v.get("phase").cloned().unwrap_or(Value::Null),
"message": "RAG response is missing a string phase; refusing to silently render phase as unknown"
})
.to_string()
unknown_bootstrap_phase_error(
v.get("phase").cloned().unwrap_or(Value::Null),
"RAG response is missing a string phase; refusing to silently render phase as unknown",
)
})?;
if phase.trim().is_empty() || phase == "unknown" {
return Err(json!({
"kind": "unknown_bootstrap_phase",
"field": "phase",
"received_value": phase,
"message": "RAG response phase must be a concrete phase name"
})
.to_string());
if !KNOWN_RAG_BOOTSTRAP_PHASES.contains(&phase) {
return Err(unknown_bootstrap_phase_error(
Value::String(phase.to_string()),
"RAG response phase is not a recognized bootstrap phase",
));
}
let hits = v
.get("hits")
@@ -2586,6 +2594,16 @@ mod tests {
let err = format_rag_query_json_for_model(r#"{"hits":[],"phase":"unknown"}"#).unwrap_err();
assert!(err.contains(r#""kind":"unknown_bootstrap_phase""#));
assert!(err.contains(r#""received_value":"unknown""#));
assert!(err.contains(r#""field":"phase""#));
}
#[test]
fn rag_response_unrecognized_phase_returns_typed_error() {
let err =
format_rag_query_json_for_model(r#"{"hits":[],"phase":"3-drifted"}"#).unwrap_err();
assert!(err.contains(r#""kind":"unknown_bootstrap_phase""#));
assert!(err.contains(r#""received_value":"3-drifted""#));
assert!(err.contains(r#""allowed_values""#));
}
#[test]