use crate::runtime::core::RuntimeInitReport;
use crate::runtime::service::native_registry::NativeServiceStatus;
pub const METRIC_GRPC_DURATION: &str = "udb_grpc_duration_seconds";
pub const METRIC_GRPC_REQUESTS: &str = "udb_grpc_requests_total";
pub const METRIC_AUTH_LOGIN: &str = "udb_auth_login_total";
pub const METRIC_AUTH_TOKEN_VALIDATION_FAILURES: &str = "udb_auth_token_validation_failures_total";
pub const METRIC_AUTHZ_DENIES: &str = "udb_authz_denies_total";
pub const METRIC_AUTHZ_POLICY_INVALIDATION_LAG: &str = "udb_authz_policy_invalidation_lag_seconds";
pub const METRIC_AUTHZ_POLICY_RELOAD: &str = "udb_authz_policy_reload_seconds";
pub const METRIC_OBJECT_OPS: &str = "udb_object_ops_total";
pub const METRIC_VECTOR_OPS: &str = "udb_vector_ops_total";
pub const METRIC_CDC_PUBLISH_LATENCY: &str = "udb_cdc_publish_latency_seconds";
pub const METRIC_CDC_DLQ_DEPTH: &str = "udb_cdc_dlq_depth";
pub const METRIC_CDC_ERRORS: &str = "udb_cdc_errors_total";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LatencyStat {
P50,
P95,
P99,
}
impl LatencyStat {
pub fn quantile(self) -> f64 {
match self {
Self::P50 => 0.50,
Self::P95 => 0.95,
Self::P99 => 0.99,
}
}
pub fn as_str(self) -> &'static str {
match self {
Self::P50 => "p50",
Self::P95 => "p95",
Self::P99 => "p99",
}
}
}
#[derive(Debug, Clone)]
pub struct Slo {
pub objective: &'static str,
pub operation: &'static str,
pub grpc_method: &'static str,
pub latency: Option<(LatencyStat, f64)>,
pub availability: f64,
pub latency_metric: &'static str,
pub availability_metric: &'static str,
pub rolling_window_days: u32,
}
impl Slo {
pub fn referenced_metrics(&self) -> Vec<&'static str> {
let mut out = vec![self.latency_metric, self.availability_metric];
out.retain(|m| !m.is_empty());
out.sort_unstable();
out.dedup();
out
}
}
pub fn slo_catalog() -> Vec<Slo> {
fn rpc(
objective: &'static str,
operation: &'static str,
method: &'static str,
stat: LatencyStat,
p_ms: f64,
availability: f64,
) -> Slo {
Slo {
objective,
operation,
grpc_method: method,
latency: Some((stat, p_ms)),
availability,
latency_metric: METRIC_GRPC_DURATION,
availability_metric: METRIC_GRPC_REQUESTS,
rolling_window_days: 28,
}
}
vec![
rpc(
"authn.login",
"Interactive credential login (password/MFA exchange → tokens)",
"Login",
LatencyStat::P99,
400.0,
0.999,
),
rpc(
"authn.refresh",
"Refresh-token rotation → new access token",
"RefreshToken",
LatencyStat::P99,
150.0,
0.9995,
),
rpc(
"authn.validate",
"Access-token validation (bearer introspection)",
"ValidateToken",
LatencyStat::P99,
50.0,
0.9995,
),
rpc(
"authz.check",
"Single authorization decision (Casbin enforce)",
"Check",
LatencyStat::P99,
25.0,
0.9999,
),
rpc(
"authz.batch_check",
"Batched authorization decisions",
"BatchCheck",
LatencyStat::P99,
75.0,
0.9995,
),
Slo {
objective: "storage.presign",
operation: "Presigned-URL mint for upload/download",
grpc_method: "PresignObject",
latency: Some((LatencyStat::P99, 100.0)),
availability: 0.999,
latency_metric: METRIC_GRPC_DURATION,
availability_metric: METRIC_OBJECT_OPS,
rolling_window_days: 28,
},
Slo {
objective: "storage.finalize",
operation: "Finalize a completed upload (commit object metadata)",
grpc_method: "FinalizeUpload",
latency: Some((LatencyStat::P99, 250.0)),
availability: 0.999,
latency_metric: METRIC_GRPC_DURATION,
availability_metric: METRIC_OBJECT_OPS,
rolling_window_days: 28,
},
Slo {
objective: "storage.list",
operation: "List objects under a prefix",
grpc_method: "ListObjects",
latency: Some((LatencyStat::P99, 300.0)),
availability: 0.999,
latency_metric: METRIC_GRPC_DURATION,
availability_metric: METRIC_OBJECT_OPS,
rolling_window_days: 28,
},
rpc(
"asset.pipeline_start",
"Start an asset-processing pipeline run",
"StartPipeline",
LatencyStat::P99,
200.0,
0.999,
),
Slo {
objective: "asset.pipeline_step",
operation: "Execute one asset pipeline step (EMBED → vector upsert)",
grpc_method: "ExecuteStep",
latency: Some((LatencyStat::P95, 2000.0)),
availability: 0.995,
latency_metric: METRIC_GRPC_DURATION,
availability_metric: METRIC_VECTOR_OPS,
rolling_window_days: 28,
},
rpc(
"webrtc.signaling",
"WebRTC signaling relay (offer/answer/ICE)",
"Signal",
LatencyStat::P99,
100.0,
0.999,
),
Slo {
objective: "cdc.publish",
operation: "End-to-end CDC publish (outbox row created → Kafka ack)",
grpc_method: "",
latency: Some((LatencyStat::P99, 1000.0)),
availability: 0.999,
latency_metric: METRIC_CDC_PUBLISH_LATENCY,
availability_metric: METRIC_CDC_ERRORS,
rolling_window_days: 28,
},
Slo {
objective: "cdc.dlq",
operation: "CDC dead-letter backlog stays drained",
grpc_method: "",
latency: None,
availability: 0.999,
latency_metric: METRIC_CDC_DLQ_DEPTH,
availability_metric: METRIC_CDC_DLQ_DEPTH,
rolling_window_days: 28,
},
Slo {
objective: "policy.distribution_ack",
operation: "Control-plane policy ACK latency (invalidation emit → node apply)",
grpc_method: "",
latency: Some((LatencyStat::P99, 5000.0)),
availability: 0.999,
latency_metric: METRIC_AUTHZ_POLICY_INVALIDATION_LAG,
availability_metric: METRIC_AUTHZ_POLICY_RELOAD,
rolling_window_days: 28,
},
]
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ErrorBudget {
pub objective: f64,
pub total: u64,
pub failed: u64,
}
impl ErrorBudget {
pub fn from_success_total(objective: f64, success: u64, total: u64) -> Self {
let success = success.min(total);
Self {
objective,
total,
failed: total - success,
}
}
pub fn allowed_failures(&self) -> u64 {
((1.0 - self.objective) * self.total as f64).floor() as u64
}
pub fn failure_ratio(&self) -> f64 {
if self.total == 0 {
0.0
} else {
self.failed as f64 / self.total as f64
}
}
pub fn consumed_fraction(&self) -> f64 {
let allowed = (1.0 - self.objective).max(0.0);
if allowed <= 0.0 {
return if self.failed == 0 { 0.0 } else { f64::INFINITY };
}
self.failure_ratio() / allowed
}
pub fn remaining_fraction(&self) -> f64 {
1.0 - self.consumed_fraction()
}
pub fn is_exhausted(&self) -> bool {
self.consumed_fraction() >= 1.0 - 1e-9
}
}
pub fn burn_rate(objective: f64, observed_failure_ratio: f64) -> f64 {
let allowed = (1.0 - objective).max(0.0);
if allowed <= 0.0 {
return if observed_failure_ratio > 0.0 {
f64::INFINITY
} else {
0.0
};
}
observed_failure_ratio / allowed
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReadinessFact {
pub name: String,
pub ok: bool,
pub required: bool,
pub detail: String,
}
impl ReadinessFact {
fn new(name: impl Into<String>, ok: bool, required: bool, detail: impl Into<String>) -> Self {
Self {
name: name.into(),
ok,
required,
detail: detail.into(),
}
}
pub fn required(name: impl Into<String>, ok: bool, detail: impl Into<String>) -> Self {
Self::new(name, ok, true, detail)
}
pub fn optional(name: impl Into<String>, ok: bool, detail: impl Into<String>) -> Self {
Self::new(name, ok, false, detail)
}
}
#[derive(Debug, Clone, Default)]
pub struct ReadinessFacts {
pub facts: Vec<ReadinessFact>,
}
impl ReadinessFacts {
pub fn passed(&self) -> bool {
self.facts.iter().all(|f| f.ok || !f.required)
}
pub fn errors(&self) -> Vec<String> {
self.facts
.iter()
.filter(|f| !f.ok && f.required)
.map(|f| format!("{}: {}", f.name, f.detail))
.collect()
}
pub fn warnings(&self) -> Vec<String> {
self.facts
.iter()
.filter(|f| !f.ok && !f.required)
.map(|f| format!("{}: {}", f.name, f.detail))
.collect()
}
pub fn get(&self, name: &str) -> Option<&ReadinessFact> {
self.facts.iter().find(|f| f.name == name)
}
}
fn object_store_configured(init: &RuntimeInitReport) -> bool {
init.s3_configured || init.azureblob_configured || init.gcs_configured
}
pub fn build_readiness_facts(
init: &RuntimeInitReport,
native: &[NativeServiceStatus],
auth_checks: &[(String, bool, String)],
) -> ReadinessFacts {
let mut facts = Vec::new();
facts.push(ReadinessFact::required(
"postgres",
init.postgres_configured,
if init.postgres_configured {
"PostgreSQL configured".to_string()
} else {
"PostgreSQL is required: set UDB_PG_DSN or DATABASE_URL".to_string()
},
));
facts.push(ReadinessFact::optional(
"redis",
init.redis_configured,
if init.redis_configured {
"Redis configured"
} else {
"Redis not configured; read-through cache and CDC idempotency degrade"
},
));
facts.push(ReadinessFact::optional(
"object_store",
object_store_configured(init),
if object_store_configured(init) {
"object store (S3/MinIO/Azure/GCS) configured"
} else {
"no object store configured; object/storage RPCs unavailable"
},
));
facts.push(ReadinessFact::optional(
"qdrant",
init.qdrant_configured,
if init.qdrant_configured {
"Qdrant configured"
} else {
"Qdrant not configured; vector RPCs unavailable"
},
));
let relational_store_expected = init.postgres_configured
|| init.mysql_configured
|| init.sqlite_configured
|| init.mssql_configured;
let system_store_ok = !relational_store_expected || init.full_system_store_registered;
facts.push(ReadinessFact::required(
"system_store",
system_store_ok,
if !relational_store_expected {
"no relational backend configured; canonical system store not required".to_string()
} else if init.full_system_store_registered {
"canonical system store registered".to_string()
} else {
"canonical system store NOT registered — udb_system not provisioned; \
saga/audit/admin RPCs return FAILED_PRECONDITION"
.to_string()
},
));
for (name, ok, detail) in auth_checks {
facts.push(ReadinessFact::required(
format!("auth.{name}"),
*ok,
detail.clone(),
));
}
for status in native {
if !status.enabled {
continue;
}
let detail = if status.mounted {
format!("native service '{}' mounted and serving", status.service_id)
} else if status.disabled_reason.is_empty() {
format!(
"native service '{}' enabled but not mounted",
status.service_id
)
} else {
format!(
"native service '{}' enabled but not mounted: {}",
status.service_id, status.disabled_reason
)
};
facts.push(ReadinessFact::optional(
format!("native.{}", status.service_id),
status.mounted,
detail,
));
}
ReadinessFacts { facts }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn slo_metrics_exist() {
use crate::runtime::metrics::MetricsRecorder;
let metrics =
crate::runtime::metrics::PrometheusMetrics::new().expect("metrics registry builds");
metrics.record_grpc("Login", "ok", 0.01); metrics.inc_object_op("bucket", "PutObject"); metrics.inc_vector_op("collection", "upsert"); metrics.inc_cdc_errors_total("warmup");
let exposition = metrics.gather_text("");
for slo in slo_catalog() {
for metric in slo.referenced_metrics() {
let type_line = format!("# TYPE {metric} ");
let help_line = format!("# HELP {metric} ");
assert!(
exposition.contains(&type_line) || exposition.contains(&help_line),
"SLO '{}' references metric '{metric}' which is not registered in metrics.rs",
slo.objective,
);
}
}
}
#[test]
fn slo_catalog_covers_every_required_lane() {
let ids: std::collections::HashSet<&str> =
slo_catalog().iter().map(|s| s.objective).collect();
for required in [
"authn.login",
"authn.refresh",
"authn.validate",
"authz.check",
"authz.batch_check",
"storage.presign",
"storage.finalize",
"storage.list",
"asset.pipeline_start",
"asset.pipeline_step",
"webrtc.signaling",
"cdc.publish",
"cdc.dlq",
"policy.distribution_ack",
] {
assert!(
ids.contains(required),
"SLO catalog missing lane '{required}'"
);
}
}
#[test]
fn slo_objectives_are_unique() {
let mut seen = std::collections::HashSet::new();
for slo in slo_catalog() {
assert!(
seen.insert(slo.objective),
"duplicate SLO objective id '{}'",
slo.objective
);
assert!(
slo.availability > 0.0 && slo.availability <= 1.0,
"availability objective for '{}' out of (0,1]",
slo.objective
);
}
}
#[test]
fn error_budget_allowed_failures() {
let b = ErrorBudget::from_success_total(0.999, 100_000, 100_000);
assert_eq!(b.allowed_failures(), 100);
assert!(!b.is_exhausted());
assert_eq!(b.failed, 0);
assert_eq!(b.consumed_fraction(), 0.0);
}
#[test]
fn error_budget_exhaustion() {
let b = ErrorBudget::from_success_total(0.99, 9_900, 10_000);
assert_eq!(b.allowed_failures(), 100);
assert_eq!(b.failed, 100);
assert!((b.consumed_fraction() - 1.0).abs() < 1e-9);
assert!(b.is_exhausted());
assert!((b.remaining_fraction()).abs() < 1e-9);
}
#[test]
fn error_budget_overspend_is_negative_remaining() {
let b = ErrorBudget::from_success_total(0.99, 9_800, 10_000);
assert!((b.consumed_fraction() - 2.0).abs() < 1e-9);
assert!((b.remaining_fraction() + 1.0).abs() < 1e-9);
assert!(b.is_exhausted());
}
#[test]
fn error_budget_empty_window_is_healthy() {
let b = ErrorBudget::from_success_total(0.999, 0, 0);
assert_eq!(b.failure_ratio(), 0.0);
assert_eq!(b.consumed_fraction(), 0.0);
assert!(!b.is_exhausted());
}
#[test]
fn error_budget_clamps_success_to_total() {
let b = ErrorBudget::from_success_total(0.999, 200, 100);
assert_eq!(b.failed, 0);
}
#[test]
fn burn_rate_canonical_fast_burn() {
let r = burn_rate(0.999, 0.0144);
assert!((r - 14.4).abs() < 1e-6, "expected ~14.4x burn, got {r}");
}
#[test]
fn burn_rate_steady_state_is_one() {
let r = burn_rate(0.99, 0.01);
assert!((r - 1.0).abs() < 1e-9);
}
#[test]
fn burn_rate_perfect_objective_overspends_on_any_error() {
assert_eq!(burn_rate(1.0, 0.0), 0.0);
assert!(burn_rate(1.0, 0.0001).is_infinite());
}
fn pg_only_init() -> RuntimeInitReport {
RuntimeInitReport {
postgres_configured: true,
full_system_store_registered: true,
..RuntimeInitReport::default()
}
}
fn native_status(
service_id: &str,
enabled: bool,
mounted: bool,
reason: &str,
) -> NativeServiceStatus {
NativeServiceStatus {
service_id: service_id.to_string(),
display_name: service_id.to_string(),
proto_services: Vec::new(),
enabled,
configured: enabled,
mounted,
healthy: mounted,
degraded: enabled && !mounted,
surface: String::new(),
listener_kind: String::new(),
supported_rpcs: Vec::new(),
selected_capabilities: Vec::new(),
required_backends: Vec::new(),
missing_dependencies: Vec::new(),
disabled_reason: reason.to_string(),
migration_enabled: false,
migration_status: "disabled".to_string(),
owns_background_workers: false,
background_worker_enabled: false,
background_workers: Vec::new(),
descriptor_version: String::new(),
enablement_key: String::new(),
service_enablement_key: String::new(),
}
}
#[test]
fn readiness_passes_with_pg_and_no_failing_auth_checks() {
let init = pg_only_init();
let auth = vec![
("casbin_model".to_string(), true, "model parsed".to_string()),
(
"token_issuance".to_string(),
true,
"sessions-only dev".to_string(),
),
];
let facts = build_readiness_facts(&init, &[], &auth);
assert!(facts.passed(), "errors: {:?}", facts.errors());
assert!(facts.warnings().iter().any(|w| w.starts_with("redis")));
assert!(facts.errors().is_empty());
assert!(facts.get("auth.casbin_model").is_some());
}
#[test]
fn readiness_fails_without_postgres() {
let init = RuntimeInitReport::default();
let facts = build_readiness_facts(&init, &[], &[]);
assert!(!facts.passed());
assert!(facts.errors().iter().any(|e| e.starts_with("postgres")));
}
#[test]
fn readiness_fails_on_failing_auth_check() {
let init = pg_only_init();
let auth = vec![(
"jwt_signing_key".to_string(),
false,
"invalid RSA PEM".to_string(),
)];
let facts = build_readiness_facts(&init, &[], &auth);
assert!(!facts.passed());
assert!(
facts
.errors()
.iter()
.any(|e| e.starts_with("auth.jwt_signing_key"))
);
}
#[test]
fn unmounted_enabled_native_service_is_a_warning_not_error() {
let init = pg_only_init();
let status = native_status(
"storage",
true,
false,
"missing dependencies: object_storage",
);
let facts = build_readiness_facts(&init, std::slice::from_ref(&status), &[]);
assert!(facts.passed());
assert!(
facts
.warnings()
.iter()
.any(|w| w.starts_with("native.storage"))
);
}
#[test]
fn disabled_native_service_emits_no_fact() {
let init = pg_only_init();
let status = native_status("asset", false, false, "");
let facts = build_readiness_facts(&init, std::slice::from_ref(&status), &[]);
assert!(facts.get("native.asset").is_none());
}
#[test]
fn readiness_fails_when_pg_configured_but_system_store_unregistered() {
let init = RuntimeInitReport {
postgres_configured: true,
full_system_store_registered: false,
..RuntimeInitReport::default()
};
let facts = build_readiness_facts(&init, &[], &[]);
assert!(!facts.passed(), "expected readiness to fail");
assert!(
facts.errors().iter().any(|e| e.starts_with("system_store")),
"errors: {:?}",
facts.errors()
);
}
#[test]
fn system_store_not_required_without_relational_backend() {
let init = RuntimeInitReport {
redis_configured: true,
full_system_store_registered: false,
..RuntimeInitReport::default()
};
let facts = build_readiness_facts(&init, &[], &[]);
let store_fact = facts
.get("system_store")
.expect("system_store fact present");
assert!(
store_fact.ok,
"system_store should be ok without a relational backend"
);
}
fn render_slo_table() -> String {
fn pct(availability: f64) -> String {
let s = format!("{:.4}", availability * 100.0);
let s = s.trim_end_matches('0').trim_end_matches('.');
format!("{s}%")
}
fn fmt_ms(ms: f64) -> String {
if ms.fract().abs() < f64::EPSILON {
format!("{}", ms as i64)
} else {
let s = format!("{ms:.3}");
s.trim_end_matches('0').trim_end_matches('.').to_string()
}
}
let mut out = String::new();
out.push_str(
"| Objective | Operation | gRPC method | Latency target | Availability | Latency metric | Availability metric |\n",
);
out.push_str("|---|---|---|---|---|---|---|\n");
for slo in slo_catalog() {
let method = if slo.grpc_method.is_empty() {
"—".to_string()
} else {
format!("`{}`", slo.grpc_method)
};
let latency = match slo.latency {
Some((stat, ms)) => format!("{} ≤ {} ms", stat.as_str(), fmt_ms(ms)),
None => "—".to_string(),
};
out.push_str(&format!(
"| `{}` | {} | {} | {} | {} | `{}` | `{}` |\n",
slo.objective,
slo.operation,
method,
latency,
pct(slo.availability),
slo.latency_metric,
slo.availability_metric,
));
}
out
}
#[test]
fn slo_doc_table_matches_catalog() {
let rendered = render_slo_table();
let root = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let doc = std::fs::read_to_string(root.join("docs/slo.md"))
.expect("docs/slo.md should be readable")
.replace("\r\n", "\n");
const BEGIN: &str = "<!-- BEGIN GENERATED:slo -->";
const END: &str = "<!-- END GENERATED:slo -->";
let begin = doc
.find(BEGIN)
.expect("docs/slo.md must contain the <!-- BEGIN GENERATED:slo --> marker");
let end = doc
.find(END)
.expect("docs/slo.md must contain the <!-- END GENERATED:slo --> marker");
assert!(
begin < end,
"docs/slo.md BEGIN GENERATED:slo marker must precede END"
);
let block = doc[begin + BEGIN.len()..end].trim_matches('\n').to_string();
assert_eq!(
block,
rendered.trim_end_matches('\n'),
"docs/slo.md `SLO catalog` generated block is stale; regenerate it from slo_catalog() \
(run `cargo test -p udb slo_doc_table_matches_catalog` to see the expected table) and \
replace the text between the <!-- BEGIN/END GENERATED:slo --> markers"
);
}
}