queryd: add latency_ms to QueryResponse (iter 9 finding #3, 88% conf)

Scrum iter 9 flagged that gateway's audit row stores null for
`latency_ms` — required for PRD audit-log parity. The field didn't
exist; adding it now with a single Instant captured at handler entry,
populated on both response paths (empty batches + non-empty result).

No behavior change for existing clients — they read the JSON and
ignore unknown fields. Audit-log consumers can now surface p50/p99
latency from the response body instead of inferring from tracing.

Narrow fingerprint on crates/queryd already has this as a known
BoundaryViolation pattern (`latency_ms-row_count` key) — iter 10 on
any queryd file will see the preamble say "this was fixed in iter 10"
when it runs.

Workspace warnings unchanged at 11. 7 policy tests still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
root 2026-04-24 06:18:46 -05:00
parent fd92a9a0d0
commit c47523e5bd

View File

@ -69,6 +69,11 @@ struct QueryResponse {
columns: Vec<ColumnInfo>, columns: Vec<ColumnInfo>,
rows: serde_json::Value, rows: serde_json::Value,
row_count: usize, row_count: usize,
// Elapsed wall time from handler entry to response. Required for
// audit-log parity — gateway's audit row previously stored null here.
// Scrum iter 9 finding, populated from std::time::Instant captured
// at the top of execute_query / paged_query.
latency_ms: u64,
} }
#[derive(Serialize)] #[derive(Serialize)]
@ -115,6 +120,7 @@ async fn execute_query(
State(state): State<QueryState>, State(state): State<QueryState>,
Json(req): Json<QueryRequest>, Json(req): Json<QueryRequest>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let started = std::time::Instant::now();
tracing::info!("executing query: {}", req.sql); tracing::info!("executing query: {}", req.sql);
if let Some(reason) = sql_policy_check(&state.truth, &req.sql) { if let Some(reason) = sql_policy_check(&state.truth, &req.sql) {
@ -129,6 +135,7 @@ async fn execute_query(
columns: vec![], columns: vec![],
rows: serde_json::Value::Array(vec![]), rows: serde_json::Value::Array(vec![]),
row_count: 0, row_count: 0,
latency_ms: started.elapsed().as_millis() as u64,
})); }));
} }
@ -147,6 +154,7 @@ async fn execute_query(
columns, columns,
rows, rows,
row_count, row_count,
latency_ms: started.elapsed().as_millis() as u64,
})) }))
} }
Err(e) => Err((StatusCode::BAD_REQUEST, e)), Err(e) => Err((StatusCode::BAD_REQUEST, e)),