pub trait MetricsRecorder: Send + Sync + std::fmt::Debug {
fn record_grpc(&self, _method: &str, _status: &str, _seconds: f64) {}
fn observe_pg_query(&self, _op: &str, _table: &str, _seconds: f64) {}
fn inc_cache_op(&self, _op: &str, _hit: bool) {}
fn inc_vector_op(&self, _collection: &str, _op: &str) {}
fn inc_embedding_work(&self, _status: &str) {}
fn observe_embedding_index_lag(&self, _seconds: f64) {}
fn set_embedding_backlog(&self, _count: i64) {}
fn inc_backend_fanout(&self, _backend: &str, _count: u64) {}
fn inc_object_op(&self, _bucket: &str, _method: &str) {}
fn inc_channel_inflight(&self, _channel: &str) {}
fn dec_channel_inflight(&self, _channel: &str) {}
fn inc_channel_rejected(&self, _channel: &str) {}
fn inc_channel_timeout(&self, _channel: &str) {}
fn observe_channel_latency(&self, _channel: &str, _seconds: f64) {}
fn record_fair_admission(
&self,
_project: &str,
_tenant_hash: &str,
_backend: &str,
_instance: &str,
_op: &str,
_status: &str,
) {
}
fn add_fair_cost(
&self,
_project: &str,
_tenant_hash: &str,
_backend: &str,
_instance: &str,
_op: &str,
_cost: f64,
) {
}
fn inc_runs_total(&self, status: &str);
fn inc_operations_total(&self, kind: &str, schema: &str, safety: &str);
fn observe_file_duration(&self, schema: &str, seconds: f64);
fn set_blocked_operations(&self, schema: &str, kind: &str, count: i64);
fn inc_lint_warnings(&self, kind: &str);
fn observe_run_duration(&self, status: &str, seconds: f64);
fn set_pending_files(&self, count: i64);
fn set_cdc_is_leader(&self, host: &str, is_leader: bool);
fn inc_cdc_wal_messages_received_total(&self);
fn inc_cdc_events_published_total(&self, topic: &str);
fn inc_cdc_errors_total(&self, reason: &str);
fn set_cdc_lag_seconds(&self, seconds: f64);
fn set_cdc_outbox_depth(&self, depth: i64);
fn set_cdc_dlq_depth(&self, depth: i64);
fn observe_cdc_publish_duration_seconds(&self, seconds: f64);
fn inc_cdc_dlq_replayed_total(&self);
fn inc_cdc_duplicate_skipped_total(&self);
fn set_saga_active(&self, count: i64);
fn inc_saga_compensated_total(&self);
fn inc_saga_failed_compensations_total(&self);
fn observe_saga_duration_seconds(&self, seconds: f64);
fn set_projection_tasks_pending(&self, backend: &str, instance: &str, kind: &str, count: i64);
fn inc_projection_tasks_completed_total(&self, backend: &str, instance: &str, kind: &str);
fn inc_projection_tasks_failed_total(&self, backend: &str, instance: &str, kind: &str);
fn observe_projection_lag_seconds(
&self,
backend: &str,
instance: &str,
kind: &str,
seconds: f64,
);
fn inc_projection_reconciliation_repairs_total(&self, backend: &str, instance: &str);
fn set_projection_oldest_pending_age_seconds(
&self,
project: &str,
backend: &str,
instance: &str,
kind: &str,
age_seconds: f64,
);
fn record_auth_login(&self, _success: bool) {}
fn record_auth_lockout(&self) {}
fn record_auth_mfa_failure(&self) {}
fn record_auth_token_validation_failure(&self) {}
fn record_authz_deny(&self) {}
fn record_refresh_reuse_detected(&self) {}
fn observe_policy_reload_seconds(&self, _seconds: f64) {}
fn observe_policy_invalidation_lag_seconds(&self, _seconds: f64) {}
fn observe_revocation_propagation_seconds(&self, _seconds: f64) {}
fn record_idp_refresh_failure(&self, _kind: &str) {}
fn record_scim_failure(&self, _op: &str) {}
fn record_audit_sink_failure(&self, _sink: &str) {}
fn set_auth_outbox_lag_seconds(&self, _seconds: f64) {}
fn inc_method_security_denial(&self, _reason: &str) {}
fn inc_tenant_mismatch(&self) {}
fn inc_revocation_lookup_failure(&self) {}
fn inc_outbox_enqueue_failures_total(&self, _path: &str) {}
fn set_native_service_degraded(&self, _service: &str, _degraded: bool) {}
fn set_tenant_connection_starved(&self, _tenant: &str, _starved: bool) {}
fn inc_compensation_failures_total(&self) {}
fn inc_cdc_journal_failures_total(&self) {}
fn inc_control_reload_applied(&self, _resource_type: &str) {}
fn inc_control_nack(&self, _resource_type: &str) {}
fn set_control_push_queue_depth(&self, _depth: u64) {}
fn inc_control_push_throttled(&self) {}
fn inc_control_debounce_coalesced(&self) {}
fn inc_canary_auto_rollback(&self, _reason: &str) {}
fn record_canary_evaluation(&self, _breached: bool) {}
fn set_canary_active(&self, _active: bool) {}
fn inc_raw_dispatch_total(&self, _backend: &str) {}
}
use prometheus::Encoder;
#[derive(Debug, Clone, Default)]
pub struct NoopMetrics;
impl MetricsRecorder for NoopMetrics {
#[inline]
fn inc_runs_total(&self, _status: &str) {}
#[inline]
fn inc_operations_total(&self, _kind: &str, _schema: &str, _safety: &str) {}
#[inline]
fn observe_file_duration(&self, _schema: &str, _seconds: f64) {}
#[inline]
fn set_blocked_operations(&self, _schema: &str, _kind: &str, _count: i64) {}
#[inline]
fn inc_lint_warnings(&self, _kind: &str) {}
#[inline]
fn observe_run_duration(&self, _status: &str, _seconds: f64) {}
#[inline]
fn set_pending_files(&self, _count: i64) {}
#[inline]
fn set_cdc_is_leader(&self, _host: &str, _is_leader: bool) {}
#[inline]
fn inc_cdc_wal_messages_received_total(&self) {}
#[inline]
fn inc_cdc_events_published_total(&self, _topic: &str) {}
#[inline]
fn inc_cdc_errors_total(&self, _reason: &str) {}
#[inline]
fn set_cdc_lag_seconds(&self, _seconds: f64) {}
#[inline]
fn set_cdc_outbox_depth(&self, _depth: i64) {}
#[inline]
fn set_cdc_dlq_depth(&self, _depth: i64) {}
#[inline]
fn observe_cdc_publish_duration_seconds(&self, _seconds: f64) {}
#[inline]
fn inc_cdc_dlq_replayed_total(&self) {}
#[inline]
fn inc_cdc_duplicate_skipped_total(&self) {}
#[inline]
fn set_saga_active(&self, _count: i64) {}
#[inline]
fn inc_saga_compensated_total(&self) {}
#[inline]
fn inc_saga_failed_compensations_total(&self) {}
#[inline]
fn observe_saga_duration_seconds(&self, _seconds: f64) {}
#[inline]
fn set_projection_tasks_pending(
&self,
_backend: &str,
_instance: &str,
_kind: &str,
_count: i64,
) {
}
#[inline]
fn inc_projection_tasks_completed_total(&self, _backend: &str, _instance: &str, _kind: &str) {}
#[inline]
fn inc_projection_tasks_failed_total(&self, _backend: &str, _instance: &str, _kind: &str) {}
#[inline]
fn observe_projection_lag_seconds(
&self,
_backend: &str,
_instance: &str,
_kind: &str,
_seconds: f64,
) {
}
#[inline]
fn inc_projection_reconciliation_repairs_total(&self, _backend: &str, _instance: &str) {}
fn set_projection_oldest_pending_age_seconds(
&self,
_project: &str,
_backend: &str,
_instance: &str,
_kind: &str,
_age_seconds: f64,
) {
}
}
pub fn metrics_or_noop(recorder: Option<&dyn MetricsRecorder>) -> &dyn MetricsRecorder {
static NOOP: NoopMetrics = NoopMetrics;
recorder.unwrap_or(&NOOP)
}
pub fn safety_label(safety: &str) -> &str {
match safety {
"SafeAuto" => "auto",
"RequiresReview" => "requires_review",
"Blocked" => "blocked",
_ => safety,
}
}
#[derive(Clone)]
pub struct PrometheusMetrics {
registry: prometheus::Registry,
grpc_requests: prometheus::IntCounterVec,
grpc_duration: prometheus::HistogramVec,
pg_duration: prometheus::HistogramVec,
cache_ops: prometheus::IntCounterVec,
vector_ops: prometheus::IntCounterVec,
embedding_work: prometheus::IntCounterVec,
embedding_index_lag: prometheus::Histogram,
embedding_backlog: prometheus::IntGauge,
backend_fanout: prometheus::IntCounterVec,
object_ops: prometheus::IntCounterVec,
cdc_events: prometheus::IntCounterVec,
cdc_errors: prometheus::IntCounterVec,
cdc_wal_messages: prometheus::IntCounter,
cdc_is_leader: prometheus::IntGaugeVec,
cdc_lag: prometheus::Gauge,
cdc_outbox_depth: prometheus::IntGauge,
cdc_dlq_depth: prometheus::IntGauge,
cdc_publish_latency: prometheus::Histogram,
cdc_dlq_replayed: prometheus::IntCounter,
cdc_duplicate_skipped: prometheus::IntCounter,
saga_active: prometheus::IntGauge,
saga_compensated: prometheus::IntCounter,
saga_failed_compensations: prometheus::IntCounter,
migration_runs: prometheus::IntCounterVec,
migration_operations: prometheus::IntCounterVec,
migration_blocked: prometheus::IntGaugeVec,
migration_lint_warnings: prometheus::IntCounterVec,
migration_pending_files: prometheus::IntGauge,
saga_duration: prometheus::Histogram,
channel_inflight: prometheus::IntGaugeVec,
channel_rejected: prometheus::IntCounterVec,
channel_timeout: prometheus::IntCounterVec,
channel_latency: prometheus::HistogramVec,
fair_admission: prometheus::IntCounterVec,
fair_cost: prometheus::CounterVec,
projection_tasks_pending: prometheus::IntGaugeVec,
projection_tasks_completed: prometheus::IntCounterVec,
projection_tasks_failed: prometheus::IntCounterVec,
projection_lag: prometheus::HistogramVec,
projection_reconciliation_repairs: prometheus::IntCounterVec,
projection_oldest_pending_age: prometheus::GaugeVec,
auth_login: prometheus::IntCounterVec,
auth_lockouts: prometheus::IntCounter,
auth_mfa_failures: prometheus::IntCounter,
auth_token_validation_failures: prometheus::IntCounter,
authz_denies: prometheus::IntCounter,
auth_refresh_reuse: prometheus::IntCounter,
authz_policy_reload: prometheus::Histogram,
authz_policy_invalidation_lag: prometheus::Histogram,
auth_revocation_propagation: prometheus::Histogram,
idp_refresh_failures: prometheus::IntCounterVec,
scim_failures: prometheus::IntCounterVec,
audit_sink_failures: prometheus::IntCounterVec,
auth_outbox_lag: prometheus::Gauge,
method_security_denials: prometheus::IntCounterVec,
tenant_mismatch: prometheus::IntCounter,
revocation_lookup_failures: prometheus::IntCounter,
outbox_enqueue_failures: prometheus::IntCounterVec,
native_service_degraded: prometheus::IntGaugeVec,
connection_tenant_budget_starved: prometheus::IntGaugeVec,
compensation_failures: prometheus::IntCounter,
cdc_journal_failures: prometheus::IntCounter,
control_reload_applied: prometheus::IntCounterVec,
control_nack: prometheus::IntCounterVec,
control_push_queue_depth: prometheus::IntGauge,
control_push_throttled: prometheus::IntCounter,
control_debounce_coalesced: prometheus::IntCounter,
canary_auto_rollback: prometheus::IntCounterVec,
canary_evaluations: prometheus::IntCounterVec,
canary_active: prometheus::IntGauge,
raw_dispatch: prometheus::IntCounterVec,
}
impl std::fmt::Debug for PrometheusMetrics {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PrometheusMetrics").finish_non_exhaustive()
}
}
impl PrometheusMetrics {
pub fn new() -> Result<Self, prometheus::Error> {
let registry = prometheus::Registry::new();
let grpc_requests = prometheus::IntCounterVec::new(
prometheus::Opts::new(
"udb_grpc_requests_total",
"UDB gRPC requests by method and status",
),
&["method", "status"],
)?;
let grpc_duration = prometheus::HistogramVec::new(
prometheus::HistogramOpts::new(
"udb_grpc_duration_seconds",
"UDB gRPC request duration",
),
&["method"],
)?;
let pg_duration = prometheus::HistogramVec::new(
prometheus::HistogramOpts::new(
"udb_pg_query_duration_seconds",
"PostgreSQL query duration",
),
&["op", "table"],
)?;
let cache_ops = prometheus::IntCounterVec::new(
prometheus::Opts::new("udb_cache_ops_total", "Redis cache operations"),
&["op", "hit"],
)?;
let vector_ops = prometheus::IntCounterVec::new(
prometheus::Opts::new("udb_vector_ops_total", "Vector backend operations"),
&["collection", "op"],
)?;
let embedding_work = prometheus::IntCounterVec::new(
prometheus::Opts::new(
"udb_embedding_work_total",
"Embedding work items by lifecycle outcome",
),
&["status"],
)?;
let embedding_index_lag =
prometheus::Histogram::with_opts(prometheus::HistogramOpts::new(
"udb_embedding_index_lag_seconds",
"Observed time from vector indexing to retrieval",
))?;
let embedding_backlog = prometheus::IntGauge::new(
"udb_embedding_backlog",
"Pending durable embedding work items",
)?;
let backend_fanout = prometheus::IntCounterVec::new(
prometheus::Opts::new(
"udb_backend_fanout_total",
"Scatter-gather data-plane fan-out: count of backend targets resolved, by backend kind",
),
&["backend"],
)?;
let object_ops = prometheus::IntCounterVec::new(
prometheus::Opts::new("udb_object_ops_total", "Object backend operations"),
&["bucket", "method"],
)?;
let cdc_events = prometheus::IntCounterVec::new(
prometheus::Opts::new("udb_cdc_events_published_total", "CDC events published"),
&["topic"],
)?;
let cdc_errors = prometheus::IntCounterVec::new(
prometheus::Opts::new("udb_cdc_errors_total", "CDC errors"),
&["reason"],
)?;
let cdc_wal_messages = prometheus::IntCounter::new(
"udb_cdc_wal_messages_received_total",
"Total WAL messages decoded from the logical replication slot",
)?;
let cdc_is_leader = prometheus::IntGaugeVec::new(
prometheus::Opts::new("udb_cdc_is_leader", "CDC leader state"),
&["host"],
)?;
let cdc_lag = prometheus::Gauge::new("udb_cdc_lag_seconds", "CDC lag in seconds")?;
let cdc_outbox_depth =
prometheus::IntGauge::new("udb_cdc_outbox_depth", "CDC outbox depth")?;
let cdc_dlq_depth =
prometheus::IntGauge::new("udb_cdc_dlq_depth", "Current number of open DLQ events")?;
let cdc_publish_latency = prometheus::Histogram::with_opts(
prometheus::HistogramOpts::new(
"udb_cdc_publish_latency_seconds",
"End-to-end Kafka publish latency per event (outbox row created → ack received)",
)
.buckets(vec![0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 5.0]),
)?;
let cdc_dlq_replayed = prometheus::IntCounter::new(
"udb_cdc_dlq_replayed_total",
"Total DLQ events replayed successfully",
)?;
let cdc_duplicate_skipped = prometheus::IntCounter::new(
"udb_cdc_duplicate_skipped_total",
"Total duplicate CDC events skipped via Redis idempotency guard",
)?;
let saga_active =
prometheus::IntGauge::new("udb_saga_active_total", "Active (IN_PROGRESS) sagas")?;
let saga_compensated = prometheus::IntCounter::new(
"udb_saga_compensated_total",
"Sagas that reached COMPENSATED terminal state",
)?;
let saga_failed_compensations = prometheus::IntCounter::new(
"udb_saga_failed_compensations_total",
"Saga compensation actions that failed",
)?;
let migration_runs = prometheus::IntCounterVec::new(
prometheus::Opts::new(
"udb_migration_runs_total",
"Startup migration runs by terminal status",
),
&["status"],
)?;
let migration_operations = prometheus::IntCounterVec::new(
prometheus::Opts::new(
"udb_migration_operations_total",
"Migration schema operations by kind, schema, and safety",
),
&["kind", "schema", "safety"],
)?;
let migration_blocked = prometheus::IntGaugeVec::new(
prometheus::Opts::new(
"udb_migration_blocked_operations",
"Blocked / requires-review schema operations by schema and kind",
),
&["schema", "kind"],
)?;
let migration_lint_warnings = prometheus::IntCounterVec::new(
prometheus::Opts::new(
"udb_migration_lint_warnings_total",
"Migration lint warnings by kind",
),
&["kind"],
)?;
let migration_pending_files = prometheus::IntGauge::new(
"udb_migration_pending_files",
"Pending migration files awaiting apply",
)?;
let saga_duration = prometheus::Histogram::with_opts(
prometheus::HistogramOpts::new(
"udb_saga_duration_seconds",
"Wall-clock duration of a complete saga transaction",
)
.buckets(vec![0.01, 0.05, 0.1, 0.5, 1.0, 5.0, 30.0]),
)?;
let channel_inflight = prometheus::IntGaugeVec::new(
prometheus::Opts::new("udb_channel_inflight", "In-flight requests per channel"),
&["channel"],
)?;
let channel_rejected = prometheus::IntCounterVec::new(
prometheus::Opts::new(
"udb_channel_rejected_total",
"Rejected requests per channel",
),
&["channel"],
)?;
let channel_timeout = prometheus::IntCounterVec::new(
prometheus::Opts::new("udb_channel_timeout_total", "Timeout count per channel"),
&["channel"],
)?;
let channel_latency = prometheus::HistogramVec::new(
prometheus::HistogramOpts::new(
"udb_channel_latency_seconds",
"Latency histogram per channel",
),
&["channel"],
)?;
let fair_admission = prometheus::IntCounterVec::new(
prometheus::Opts::new(
"udb_fair_admission_total",
"Fair admission decisions by project, tenant hash, backend, instance, operation, and result",
),
&[
"project",
"tenant_hash",
"backend",
"instance",
"operation",
"result",
],
)?;
let fair_cost = prometheus::CounterVec::new(
prometheus::Opts::new(
"udb_fair_cost_units_total",
"Cost units admitted by project, tenant hash, backend, instance, and operation",
),
&["project", "tenant_hash", "backend", "instance", "operation"],
)?;
let projection_tasks_pending = prometheus::IntGaugeVec::new(
prometheus::Opts::new(
"udb_projection_tasks_pending",
"Current count of PENDING projection tasks",
),
&["backend", "instance", "kind"],
)?;
let projection_tasks_completed = prometheus::IntCounterVec::new(
prometheus::Opts::new(
"udb_projection_tasks_completed_total",
"Total completed projection tasks",
),
&["backend", "instance", "kind"],
)?;
let projection_tasks_failed = prometheus::IntCounterVec::new(
prometheus::Opts::new(
"udb_projection_tasks_failed_total",
"Total failed projection tasks",
),
&["backend", "instance", "kind"],
)?;
let projection_lag = prometheus::HistogramVec::new(
prometheus::HistogramOpts::new(
"udb_projection_lag_seconds",
"Wall-clock lag between task creation and completion",
)
.buckets(vec![0.1, 0.5, 1.0, 5.0, 15.0, 60.0, 300.0]),
&["backend", "instance", "kind"],
)?;
let projection_reconciliation_repairs = prometheus::IntCounterVec::new(
prometheus::Opts::new(
"udb_projection_reconciliation_repairs_total",
"Total projection tasks re-enqueued by the reconciliation worker",
),
&["backend", "instance"],
)?;
let projection_oldest_pending_age = prometheus::GaugeVec::new(
prometheus::Opts::new(
"udb_projection_oldest_pending_age_seconds",
"Wall-clock age of the oldest PENDING/FAILED projection task per \
(project, backend, instance, kind). Unlike the completion-time \
lag histogram, this rises even when the worker is stalled.",
),
&["project", "backend", "instance", "kind"],
)?;
let auth_login = prometheus::IntCounterVec::new(
prometheus::Opts::new(
"udb_auth_login_total",
"Authentication login attempts by outcome",
),
&["outcome"],
)?;
let auth_lockouts = prometheus::IntCounter::new(
"udb_auth_lockouts_total",
"Total account lockouts triggered by failed-attempt thresholds",
)?;
let auth_mfa_failures = prometheus::IntCounter::new(
"udb_auth_mfa_failures_total",
"Total MFA second-factor verification failures",
)?;
let auth_token_validation_failures = prometheus::IntCounter::new(
"udb_auth_token_validation_failures_total",
"Total access/refresh token validation failures",
)?;
let authz_denies = prometheus::IntCounter::new(
"udb_authz_denies_total",
"Total authorization decisions that denied access",
)?;
let auth_refresh_reuse = prometheus::IntCounter::new(
"udb_auth_refresh_reuse_detected_total",
"Total refresh-token-family reuse detections (replayed/stolen tokens)",
)?;
let authz_policy_reload = prometheus::Histogram::with_opts(
prometheus::HistogramOpts::new(
"udb_authz_policy_reload_seconds",
"Wall-clock latency of an authz policy snapshot reload",
)
.buckets(vec![0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0, 5.0]),
)?;
let authz_policy_invalidation_lag = prometheus::Histogram::with_opts(
prometheus::HistogramOpts::new(
"udb_authz_policy_invalidation_lag_seconds",
"Lag between a policy-bundle invalidation emit and a node applying it",
)
.buckets(vec![0.01, 0.05, 0.1, 0.5, 1.0, 5.0, 30.0]),
)?;
let auth_revocation_propagation = prometheus::Histogram::with_opts(
prometheus::HistogramOpts::new(
"udb_auth_revocation_propagation_seconds",
"Lag between a token/session/device revocation and cluster-wide effect",
)
.buckets(vec![0.01, 0.05, 0.1, 0.5, 1.0, 5.0, 30.0]),
)?;
let idp_refresh_failures = prometheus::IntCounterVec::new(
prometheus::Opts::new(
"udb_idp_refresh_failures_total",
"IdP discovery/JWKS refresh failures by kind",
),
&["kind"],
)?;
let scim_failures = prometheus::IntCounterVec::new(
prometheus::Opts::new(
"udb_scim_failures_total",
"SCIM provisioning failures by operation",
),
&["op"],
)?;
let audit_sink_failures = prometheus::IntCounterVec::new(
prometheus::Opts::new(
"udb_audit_sink_failures_total",
"Immutable audit-export sink failures by sink",
),
&["sink"],
)?;
let auth_outbox_lag = prometheus::Gauge::new(
"udb_auth_outbox_lag_seconds",
"Age of the oldest un-relayed auth event in the outbox",
)?;
let method_security_denials = prometheus::IntCounterVec::new(
prometheus::Opts::new(
"udb_method_security_denials_total",
"Per-RPC method-security denials by reason",
),
&["reason"],
)?;
let tenant_mismatch = prometheus::IntCounter::new(
"udb_tenant_mismatch_total",
"Requests rejected because tenant metadata did not match the bearer token",
)?;
let revocation_lookup_failures = prometheus::IntCounter::new(
"udb_revocation_lookup_failures_total",
"Token/session/device revocation checks that could not complete (fail-closed)",
)?;
let outbox_enqueue_failures = prometheus::IntCounterVec::new(
prometheus::Opts::new(
"udb_outbox_enqueue_failures_total",
"Outbox enqueue failures by path (native / auth / native_compliance)",
),
&["path"],
)?;
let native_service_degraded = prometheus::IntGaugeVec::new(
prometheus::Opts::new(
"udb_native_service_degraded",
"Native service degraded/fail-closed state (1 = degraded) by service",
),
&["service"],
)?;
let connection_tenant_budget_starved = prometheus::IntGaugeVec::new(
prometheus::Opts::new(
"udb_connection_tenant_budget_starved",
"Per-tenant DB-connection budget starvation (1 = tenant at budget and queueing for a slot) by tenant",
),
&["tenant"],
)?;
let compensation_failures = prometheus::IntCounter::new(
"udb_compensation_failures_total",
"Saga/native compensation steps that failed to undo their forward action",
)?;
let cdc_journal_failures = prometheus::IntCounter::new(
"udb_cdc_journal_failures_total",
"CDC delivery-journal write failures (lost publish evidence)",
)?;
let control_reload_applied = prometheus::IntCounterVec::new(
prometheus::Opts::new(
"udb_control_reload_applied_total",
"Control-plane in-process reloads applied by resource_type",
),
&["resource_type"],
)?;
let control_nack = prometheus::IntCounterVec::new(
prometheus::Opts::new(
"udb_control_nack_total",
"Control-plane node NACKs (rejected pushed versions) by resource_type",
),
&["resource_type"],
)?;
let control_push_queue_depth = prometheus::IntGauge::new(
"udb_control_push_queue_depth",
"Control-plane node-push queue depth (in-flight pushes)",
)?;
let control_push_throttled = prometheus::IntCounter::new(
"udb_control_push_throttled_total",
"Control-plane pushes throttled by the concurrent-push semaphore",
)?;
let control_debounce_coalesced = prometheus::IntCounter::new(
"udb_control_debounce_coalesced_total",
"Control-plane registry version-bump bursts coalesced into one push",
)?;
let canary_auto_rollback = prometheus::IntCounterVec::new(
prometheus::Opts::new(
"udb_canary_auto_rollback_total",
"Canary policy versions auto-rolled-back by reason",
),
&["reason"],
)?;
let canary_evaluations = prometheus::IntCounterVec::new(
prometheus::Opts::new(
"udb_canary_evaluations_total",
"Canary evaluation cycles by outcome (breached / ok)",
),
&["outcome"],
)?;
let canary_active = prometheus::IntGauge::new(
"udb_canary_active",
"Whether any canary policy version is currently ACTIVE (1) or none (0)",
)?;
let raw_dispatch = prometheus::IntCounterVec::new(
prometheus::Opts::new(
"udb_raw_dispatch_total",
"Generic-dispatch requests that bypassed the neutral-IR compiler \
(raw spec) for a mediated backend, by backend kind",
),
&["backend"],
)?;
for collector in [
Box::new(grpc_requests.clone()) as Box<dyn prometheus::core::Collector>,
Box::new(grpc_duration.clone()),
Box::new(pg_duration.clone()),
Box::new(cache_ops.clone()),
Box::new(vector_ops.clone()),
Box::new(embedding_work.clone()),
Box::new(embedding_index_lag.clone()),
Box::new(embedding_backlog.clone()),
Box::new(backend_fanout.clone()),
Box::new(object_ops.clone()),
Box::new(cdc_events.clone()),
Box::new(cdc_errors.clone()),
Box::new(cdc_wal_messages.clone()),
Box::new(cdc_is_leader.clone()),
Box::new(cdc_lag.clone()),
Box::new(cdc_outbox_depth.clone()),
Box::new(cdc_dlq_depth.clone()),
Box::new(cdc_publish_latency.clone()),
Box::new(cdc_dlq_replayed.clone()),
Box::new(cdc_duplicate_skipped.clone()),
Box::new(saga_active.clone()),
Box::new(saga_compensated.clone()),
Box::new(saga_failed_compensations.clone()),
Box::new(saga_duration.clone()),
Box::new(migration_runs.clone()),
Box::new(migration_operations.clone()),
Box::new(migration_blocked.clone()),
Box::new(migration_lint_warnings.clone()),
Box::new(migration_pending_files.clone()),
Box::new(channel_inflight.clone()),
Box::new(channel_rejected.clone()),
Box::new(channel_timeout.clone()),
Box::new(channel_latency.clone()),
Box::new(fair_admission.clone()),
Box::new(fair_cost.clone()),
Box::new(projection_tasks_pending.clone()),
Box::new(projection_tasks_completed.clone()),
Box::new(projection_tasks_failed.clone()),
Box::new(projection_lag.clone()),
Box::new(projection_reconciliation_repairs.clone()),
Box::new(projection_oldest_pending_age.clone()),
Box::new(auth_login.clone()),
Box::new(auth_lockouts.clone()),
Box::new(auth_mfa_failures.clone()),
Box::new(auth_token_validation_failures.clone()),
Box::new(authz_denies.clone()),
Box::new(auth_refresh_reuse.clone()),
Box::new(authz_policy_reload.clone()),
Box::new(authz_policy_invalidation_lag.clone()),
Box::new(auth_revocation_propagation.clone()),
Box::new(idp_refresh_failures.clone()),
Box::new(scim_failures.clone()),
Box::new(audit_sink_failures.clone()),
Box::new(auth_outbox_lag.clone()),
Box::new(method_security_denials.clone()),
Box::new(tenant_mismatch.clone()),
Box::new(revocation_lookup_failures.clone()),
Box::new(outbox_enqueue_failures.clone()),
Box::new(native_service_degraded.clone()),
Box::new(connection_tenant_budget_starved.clone()),
Box::new(compensation_failures.clone()),
Box::new(cdc_journal_failures.clone()),
Box::new(control_reload_applied.clone()),
Box::new(control_nack.clone()),
Box::new(control_push_queue_depth.clone()),
Box::new(control_push_throttled.clone()),
Box::new(control_debounce_coalesced.clone()),
Box::new(canary_auto_rollback.clone()),
Box::new(canary_evaluations.clone()),
Box::new(canary_active.clone()),
Box::new(raw_dispatch.clone()),
] {
registry.register(collector)?;
}
Ok(Self {
registry,
grpc_requests,
grpc_duration,
pg_duration,
cache_ops,
vector_ops,
embedding_work,
embedding_index_lag,
embedding_backlog,
backend_fanout,
object_ops,
cdc_events,
cdc_errors,
cdc_wal_messages,
cdc_is_leader,
cdc_lag,
cdc_outbox_depth,
cdc_dlq_depth,
cdc_publish_latency,
cdc_dlq_replayed,
cdc_duplicate_skipped,
saga_active,
saga_compensated,
saga_failed_compensations,
saga_duration,
migration_runs,
migration_operations,
migration_blocked,
migration_lint_warnings,
migration_pending_files,
channel_inflight,
channel_rejected,
channel_timeout,
channel_latency,
fair_admission,
fair_cost,
projection_tasks_pending,
projection_tasks_completed,
projection_tasks_failed,
projection_lag,
projection_reconciliation_repairs,
projection_oldest_pending_age,
auth_login,
auth_lockouts,
auth_mfa_failures,
auth_token_validation_failures,
authz_denies,
auth_refresh_reuse,
authz_policy_reload,
authz_policy_invalidation_lag,
auth_revocation_propagation,
idp_refresh_failures,
scim_failures,
audit_sink_failures,
auth_outbox_lag,
method_security_denials,
tenant_mismatch,
revocation_lookup_failures,
outbox_enqueue_failures,
native_service_degraded,
connection_tenant_budget_starved,
compensation_failures,
cdc_journal_failures,
control_reload_applied,
control_nack,
control_push_queue_depth,
control_push_throttled,
control_debounce_coalesced,
canary_auto_rollback,
canary_evaluations,
canary_active,
raw_dispatch,
})
}
pub fn record_grpc(&self, method: &str, status: &str, seconds: f64) {
self.grpc_requests
.with_label_values(&[method, status])
.inc();
self.grpc_duration
.with_label_values(&[method])
.observe(seconds);
}
pub fn observe_pg_query(&self, op: &str, table: &str, seconds: f64) {
self.pg_duration
.with_label_values(&[op, bounded_label("pg_table", table).as_ref()])
.observe(seconds);
}
pub fn inc_cache_op(&self, op: &str, hit: bool) {
self.cache_ops
.with_label_values(&[op, if hit { "true" } else { "false" }])
.inc();
}
pub fn inc_vector_op(&self, collection: &str, op: &str) {
self.vector_ops
.with_label_values(&[bounded_label("vector_collection", collection).as_ref(), op])
.inc();
}
pub fn inc_embedding_work(&self, status: &str) {
self.embedding_work.with_label_values(&[status]).inc();
}
pub fn observe_embedding_index_lag(&self, seconds: f64) {
self.embedding_index_lag.observe(seconds.max(0.0));
}
pub fn set_embedding_backlog(&self, count: i64) {
self.embedding_backlog.set(count.max(0));
}
pub fn inc_backend_fanout(&self, backend: &str, count: u64) {
self.backend_fanout
.with_label_values(&[bounded_label("backend", backend).as_ref()])
.inc_by(count);
}
pub fn inc_object_op(&self, bucket: &str, method: &str) {
self.object_ops
.with_label_values(&[bounded_label("object_bucket", bucket).as_ref(), method])
.inc();
}
pub fn gather_text(&self, extra: &str) -> String {
let encoder = prometheus::TextEncoder::new();
let mut bytes = Vec::new();
if encoder.encode(&self.registry.gather(), &mut bytes).is_err() {
return extra.to_string();
}
let mut text = String::from_utf8(bytes).unwrap_or_default();
text.push_str(extra);
text
}
pub fn inc_channel_inflight(&self, channel: &str) {
self.channel_inflight.with_label_values(&[channel]).inc();
}
pub fn dec_channel_inflight(&self, channel: &str) {
self.channel_inflight.with_label_values(&[channel]).dec();
}
pub fn inc_channel_rejected(&self, channel: &str) {
self.channel_rejected.with_label_values(&[channel]).inc();
}
pub fn inc_channel_timeout(&self, channel: &str) {
self.channel_timeout.with_label_values(&[channel]).inc();
}
pub fn observe_channel_latency(&self, channel: &str, seconds: f64) {
self.channel_latency
.with_label_values(&[channel])
.observe(seconds);
}
pub fn record_fair_admission(
&self,
project: &str,
tenant_hash: &str,
backend: &str,
instance: &str,
operation: &str,
result: &str,
) {
self.fair_admission
.with_label_values(&[
bounded_label("fair_project", project).as_ref(),
bounded_label("fair_tenant_hash", tenant_hash).as_ref(),
backend,
bounded_label("fair_instance", instance).as_ref(),
operation,
result,
])
.inc();
}
pub fn add_fair_cost(
&self,
project: &str,
tenant_hash: &str,
backend: &str,
instance: &str,
operation: &str,
cost: f64,
) {
self.fair_cost
.with_label_values(&[
bounded_label("fair_project", project).as_ref(),
bounded_label("fair_tenant_hash", tenant_hash).as_ref(),
backend,
bounded_label("fair_instance", instance).as_ref(),
operation,
])
.inc_by(cost.max(0.0));
}
}
fn cdc_topic_label(topic: &str) -> &'static str {
let topic = topic.trim();
if topic == "workflow.dead_letter.v1" {
"workflow.dead_letter"
} else if topic.starts_with("workflow.retry") {
"workflow.retry"
} else if topic.starts_with("udb.cdc.") {
"udb.cdc"
} else if topic.starts_with("udb.") {
"udb.other"
} else if topic.is_empty() {
"empty"
} else {
"external"
}
}
fn cdc_error_reason_label(reason: &str) -> &'static str {
match reason.trim() {
"validation" | "schema_registry_rejected" | "source_identity_missing" => "validation",
"dlq_routed" => "dlq_routed",
"transient" | "source_stream_error" | "broadcast_lagged" => "transient",
"duplicate" | "duplicate_skipped" => "duplicate",
"" => "unknown",
_ => "other",
}
}
fn max_label_len() -> usize {
static V: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
*V.get_or_init(crate::runtime::config::metric_label_max_len)
}
fn max_distinct_labels() -> usize {
static V: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
*V.get_or_init(crate::runtime::config::metric_max_distinct_labels)
}
fn sanitize_label(raw: &str) -> std::borrow::Cow<'static, str> {
let trimmed = raw.trim();
if trimmed.is_empty() {
return std::borrow::Cow::Borrowed("empty");
}
std::borrow::Cow::Owned(
trimmed
.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || matches!(c, '_' | '-' | '.' | ':') {
c.to_ascii_lowercase()
} else {
'_'
}
})
.take(max_label_len())
.collect(),
)
}
#[cfg(test)]
fn intern_bounded(
label: std::borrow::Cow<'static, str>,
seen: &std::sync::Mutex<std::collections::HashSet<String>>,
cap: usize,
) -> std::borrow::Cow<'static, str> {
let mut guard = seen.lock().unwrap_or_else(|e| e.into_inner());
intern_bounded_set(label, &mut guard, cap)
}
fn intern_bounded_set(
label: std::borrow::Cow<'static, str>,
seen: &mut std::collections::HashSet<String>,
cap: usize,
) -> std::borrow::Cow<'static, str> {
if seen.contains(label.as_ref()) {
return label;
}
if seen.len() < cap {
seen.insert(label.clone().into_owned());
return label;
}
std::borrow::Cow::Borrowed("overflow")
}
fn bounded_label(scope: &'static str, raw: &str) -> std::borrow::Cow<'static, str> {
static SEEN: std::sync::OnceLock<
std::sync::Mutex<
std::collections::HashMap<&'static str, std::collections::HashSet<String>>,
>,
> = std::sync::OnceLock::new();
let seen = SEEN.get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()));
let mut guard = seen.lock().unwrap_or_else(|e| e.into_inner());
let scoped = guard.entry(scope).or_default();
intern_bounded_set(sanitize_label(raw), scoped, max_distinct_labels())
}
impl MetricsRecorder for PrometheusMetrics {
fn record_grpc(&self, method: &str, status: &str, seconds: f64) {
PrometheusMetrics::record_grpc(self, method, status, seconds);
}
fn observe_pg_query(&self, op: &str, table: &str, seconds: f64) {
PrometheusMetrics::observe_pg_query(self, op, table, seconds);
}
fn inc_cache_op(&self, op: &str, hit: bool) {
PrometheusMetrics::inc_cache_op(self, op, hit);
}
fn inc_vector_op(&self, collection: &str, op: &str) {
PrometheusMetrics::inc_vector_op(self, collection, op);
}
fn inc_embedding_work(&self, status: &str) {
PrometheusMetrics::inc_embedding_work(self, status);
}
fn observe_embedding_index_lag(&self, seconds: f64) {
PrometheusMetrics::observe_embedding_index_lag(self, seconds);
}
fn set_embedding_backlog(&self, count: i64) {
PrometheusMetrics::set_embedding_backlog(self, count);
}
fn inc_backend_fanout(&self, backend: &str, count: u64) {
PrometheusMetrics::inc_backend_fanout(self, backend, count);
}
fn inc_object_op(&self, bucket: &str, method: &str) {
PrometheusMetrics::inc_object_op(self, bucket, method);
}
fn inc_channel_inflight(&self, channel: &str) {
PrometheusMetrics::inc_channel_inflight(self, channel);
}
fn dec_channel_inflight(&self, channel: &str) {
PrometheusMetrics::dec_channel_inflight(self, channel);
}
fn inc_channel_rejected(&self, channel: &str) {
PrometheusMetrics::inc_channel_rejected(self, channel);
}
fn inc_channel_timeout(&self, channel: &str) {
PrometheusMetrics::inc_channel_timeout(self, channel);
}
fn observe_channel_latency(&self, channel: &str, seconds: f64) {
PrometheusMetrics::observe_channel_latency(self, channel, seconds);
}
fn record_fair_admission(
&self,
project: &str,
tenant_hash: &str,
backend: &str,
instance: &str,
op: &str,
status: &str,
) {
PrometheusMetrics::record_fair_admission(
self,
project,
tenant_hash,
backend,
instance,
op,
status,
);
}
fn add_fair_cost(
&self,
project: &str,
tenant_hash: &str,
backend: &str,
instance: &str,
op: &str,
cost: f64,
) {
PrometheusMetrics::add_fair_cost(self, project, tenant_hash, backend, instance, op, cost);
}
fn inc_runs_total(&self, status: &str) {
self.migration_runs.with_label_values(&[status]).inc();
}
fn inc_operations_total(&self, kind: &str, schema: &str, safety: &str) {
self.migration_operations
.with_label_values(&[kind, schema, safety])
.inc();
}
fn observe_file_duration(&self, schema: &str, seconds: f64) {
self.observe_pg_query("migration", schema, seconds);
}
fn set_blocked_operations(&self, schema: &str, kind: &str, count: i64) {
self.migration_blocked
.with_label_values(&[schema, kind])
.set(count);
}
fn inc_lint_warnings(&self, kind: &str) {
self.migration_lint_warnings
.with_label_values(&[kind])
.inc();
}
fn observe_run_duration(&self, status: &str, seconds: f64) {
self.record_grpc("startup_lifecycle", status, seconds);
}
fn set_pending_files(&self, count: i64) {
self.migration_pending_files.set(count);
}
fn set_cdc_is_leader(&self, host: &str, is_leader: bool) {
self.cdc_is_leader
.with_label_values(&[host])
.set(i64::from(is_leader));
}
fn inc_cdc_wal_messages_received_total(&self) {
self.cdc_wal_messages.inc();
}
fn inc_cdc_events_published_total(&self, topic: &str) {
self.cdc_events
.with_label_values(&[cdc_topic_label(topic)])
.inc();
}
fn inc_cdc_errors_total(&self, reason: &str) {
self.cdc_errors
.with_label_values(&[cdc_error_reason_label(reason)])
.inc();
}
fn set_cdc_lag_seconds(&self, seconds: f64) {
self.cdc_lag.set(seconds);
}
fn set_cdc_outbox_depth(&self, depth: i64) {
self.cdc_outbox_depth.set(depth);
}
fn set_cdc_dlq_depth(&self, depth: i64) {
self.cdc_dlq_depth.set(depth);
}
fn observe_cdc_publish_duration_seconds(&self, seconds: f64) {
self.cdc_publish_latency.observe(seconds);
}
fn inc_cdc_dlq_replayed_total(&self) {
self.cdc_dlq_replayed.inc();
}
fn inc_cdc_duplicate_skipped_total(&self) {
self.cdc_duplicate_skipped.inc();
}
fn set_saga_active(&self, count: i64) {
self.saga_active.set(count);
}
fn inc_saga_compensated_total(&self) {
self.saga_compensated.inc();
}
fn inc_saga_failed_compensations_total(&self) {
self.saga_failed_compensations.inc();
}
fn observe_saga_duration_seconds(&self, seconds: f64) {
self.saga_duration.observe(seconds);
}
fn set_projection_tasks_pending(&self, backend: &str, instance: &str, kind: &str, count: i64) {
self.projection_tasks_pending
.with_label_values(&[backend, instance, kind])
.set(count);
}
fn inc_projection_tasks_completed_total(&self, backend: &str, instance: &str, kind: &str) {
self.projection_tasks_completed
.with_label_values(&[backend, instance, kind])
.inc();
}
fn inc_projection_tasks_failed_total(&self, backend: &str, instance: &str, kind: &str) {
self.projection_tasks_failed
.with_label_values(&[backend, instance, kind])
.inc();
}
fn observe_projection_lag_seconds(
&self,
backend: &str,
instance: &str,
kind: &str,
seconds: f64,
) {
self.projection_lag
.with_label_values(&[backend, instance, kind])
.observe(seconds);
}
fn inc_projection_reconciliation_repairs_total(&self, backend: &str, instance: &str) {
self.projection_reconciliation_repairs
.with_label_values(&[backend, instance])
.inc();
}
fn set_projection_oldest_pending_age_seconds(
&self,
project: &str,
backend: &str,
instance: &str,
kind: &str,
age_seconds: f64,
) {
self.projection_oldest_pending_age
.with_label_values(&[project, backend, instance, kind])
.set(age_seconds);
}
fn record_auth_login(&self, success: bool) {
self.auth_login
.with_label_values(&[if success { "success" } else { "failure" }])
.inc();
}
fn record_auth_lockout(&self) {
self.auth_lockouts.inc();
}
fn record_auth_mfa_failure(&self) {
self.auth_mfa_failures.inc();
}
fn record_auth_token_validation_failure(&self) {
self.auth_token_validation_failures.inc();
}
fn record_authz_deny(&self) {
self.authz_denies.inc();
}
fn record_refresh_reuse_detected(&self) {
self.auth_refresh_reuse.inc();
}
fn observe_policy_reload_seconds(&self, seconds: f64) {
self.authz_policy_reload.observe(seconds.max(0.0));
}
fn observe_policy_invalidation_lag_seconds(&self, seconds: f64) {
self.authz_policy_invalidation_lag.observe(seconds.max(0.0));
}
fn observe_revocation_propagation_seconds(&self, seconds: f64) {
self.auth_revocation_propagation.observe(seconds.max(0.0));
}
fn record_idp_refresh_failure(&self, kind: &str) {
self.idp_refresh_failures
.with_label_values(&[bounded_label("idp_refresh_kind", kind).as_ref()])
.inc();
}
fn record_scim_failure(&self, op: &str) {
self.scim_failures
.with_label_values(&[bounded_label("scim_op", op).as_ref()])
.inc();
}
fn record_audit_sink_failure(&self, sink: &str) {
self.audit_sink_failures
.with_label_values(&[bounded_label("audit_sink", sink).as_ref()])
.inc();
}
fn set_auth_outbox_lag_seconds(&self, seconds: f64) {
self.auth_outbox_lag.set(seconds);
}
fn inc_method_security_denial(&self, reason: &str) {
self.method_security_denials
.with_label_values(&[reason])
.inc();
}
fn inc_tenant_mismatch(&self) {
self.tenant_mismatch.inc();
}
fn inc_revocation_lookup_failure(&self) {
self.revocation_lookup_failures.inc();
}
fn inc_outbox_enqueue_failures_total(&self, path: &str) {
self.outbox_enqueue_failures
.with_label_values(&[bounded_label("outbox_path", path).as_ref()])
.inc();
}
fn set_native_service_degraded(&self, service: &str, degraded: bool) {
self.native_service_degraded
.with_label_values(&[bounded_label("native_service", service).as_ref()])
.set(i64::from(degraded));
}
fn set_tenant_connection_starved(&self, tenant: &str, starved: bool) {
self.connection_tenant_budget_starved
.with_label_values(&[bounded_label("connection_budget_tenant", tenant).as_ref()])
.set(i64::from(starved));
}
fn inc_compensation_failures_total(&self) {
self.compensation_failures.inc();
}
fn inc_cdc_journal_failures_total(&self) {
self.cdc_journal_failures.inc();
}
fn inc_control_reload_applied(&self, resource_type: &str) {
self.control_reload_applied
.with_label_values(&[resource_type])
.inc();
}
fn inc_control_nack(&self, resource_type: &str) {
self.control_nack
.with_label_values(&[
bounded_label("control_nack_resource_type", resource_type).as_ref()
])
.inc();
}
fn set_control_push_queue_depth(&self, depth: u64) {
self.control_push_queue_depth.set(depth as i64);
}
fn inc_control_push_throttled(&self) {
self.control_push_throttled.inc();
}
fn inc_control_debounce_coalesced(&self) {
self.control_debounce_coalesced.inc();
}
fn inc_canary_auto_rollback(&self, reason: &str) {
self.canary_auto_rollback.with_label_values(&[reason]).inc();
}
fn record_canary_evaluation(&self, breached: bool) {
self.canary_evaluations
.with_label_values(&[if breached { "breached" } else { "ok" }])
.inc();
}
fn set_canary_active(&self, active: bool) {
self.canary_active.set(if active { 1 } else { 0 });
}
fn inc_raw_dispatch_total(&self, backend: &str) {
self.raw_dispatch
.with_label_values(&[bounded_label("raw_dispatch_backend", backend).as_ref()])
.inc();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanitize_label_bounds_charset_and_length() {
assert_eq!(sanitize_label("My Table!"), "my_table_");
assert_eq!(sanitize_label(" "), "empty");
assert_eq!(sanitize_label(""), "empty");
assert_eq!(sanitize_label("udb.core.v1:users-2"), "udb.core.v1:users-2");
assert!(sanitize_label(&"x".repeat(500)).len() <= max_label_len());
}
#[test]
fn intern_bounded_caps_cardinality_under_arbitrary_names() {
use std::collections::HashSet;
use std::sync::Mutex;
let seen = Mutex::new(HashSet::new());
let cap = 8;
let mut distinct = HashSet::new();
for i in 0..(cap * 10) {
let label = intern_bounded(sanitize_label(&format!("tbl_{i}")), &seen, cap);
distinct.insert(label.into_owned());
}
assert!(
distinct.len() <= cap + 1,
"cardinality must stay bounded, got {} distinct labels",
distinct.len()
);
assert!(
distinct.contains("overflow"),
"overflow bucket must be used past the cap"
);
assert_eq!(intern_bounded(sanitize_label("tbl_0"), &seen, cap), "tbl_0");
}
#[test]
fn fair_admission_labels_are_cardinality_bounded() {
let m = PrometheusMetrics::new().expect("build PrometheusMetrics");
let cap = max_distinct_labels();
for i in 0..(cap * 2) {
m.record_fair_admission(
&format!("attacker-project-{i}"),
"tenant-hash",
"postgres",
"primary",
"query",
"admit",
);
m.add_fair_cost(
&format!("attacker-project-{i}"),
"tenant-hash",
"postgres",
"primary",
"query",
1.0,
);
}
let text = m.gather_text("");
let admission_series = text
.lines()
.filter(|line| line.starts_with("udb_fair_admission_total{"))
.count();
let cost_series = text
.lines()
.filter(|line| line.starts_with("udb_fair_cost_units_total{"))
.count();
assert!(
admission_series <= cap + 1,
"fair_admission series must cap at {} (+overflow), got {admission_series}",
cap
);
assert!(
cost_series <= cap + 1,
"fair_cost series must cap at {} (+overflow), got {cost_series}",
cap
);
assert!(
text.lines().any(|line| {
line.starts_with("udb_fair_admission_total{")
&& line.contains("project=\"overflow\"")
}),
"past the cap, projects must collapse into the overflow bucket:\n{}",
text.lines()
.filter(|line| line.starts_with("udb_fair_admission_total"))
.take(5)
.collect::<Vec<_>>()
.join("\n")
);
}
#[test]
fn bounded_labels_are_scoped_by_metric_namespace() {
let cap = max_distinct_labels();
for i in 0..(cap * 2) {
let _ = bounded_label("test_noisy_namespace", &format!("attacker-value-{i}"));
}
assert_eq!(bounded_label("idp_refresh_kind", "jwks"), "jwks");
assert_eq!(bounded_label("outbox_path", "native"), "native");
assert_eq!(bounded_label("native_service", "authn"), "authn");
}
#[test]
fn noop_metrics_is_callable() {
let m = NoopMetrics;
m.inc_runs_total("completed");
m.inc_operations_total("add_column", "app_processing", "auto");
m.observe_file_duration("app_processing", 0.042);
m.set_blocked_operations("app_processing", "drop_column", 0);
m.inc_lint_warnings("missing_primary_key");
m.observe_run_duration("completed", 1.23);
m.set_pending_files(0);
}
#[test]
fn metrics_or_noop_with_none() {
let m = metrics_or_noop(None);
m.inc_runs_total("completed"); }
#[test]
fn safety_label_known_values() {
assert_eq!(safety_label("SafeAuto"), "auto");
assert_eq!(safety_label("RequiresReview"), "requires_review");
assert_eq!(safety_label("Blocked"), "blocked");
}
#[test]
fn auth_metrics_register_and_increment() {
let m = PrometheusMetrics::new().expect("build PrometheusMetrics");
m.record_auth_login(true);
m.record_auth_login(false);
m.record_auth_lockout();
m.record_auth_mfa_failure();
m.record_auth_token_validation_failure();
m.record_authz_deny();
let text = m.gather_text("");
assert!(
text.contains("udb_auth_login_total{outcome=\"success\"} 1"),
"missing success login counter:\n{text}"
);
assert!(
text.contains("udb_auth_login_total{outcome=\"failure\"} 1"),
"missing failure login counter:\n{text}"
);
assert!(
text.contains("udb_auth_lockouts_total 1"),
"missing lockout counter:\n{text}"
);
assert!(
text.contains("udb_auth_mfa_failures_total 1"),
"missing mfa-failure counter:\n{text}"
);
assert!(
text.contains("udb_auth_token_validation_failures_total 1"),
"missing token-validation-failure counter:\n{text}"
);
assert!(
text.contains("udb_authz_denies_total 1"),
"missing authz-deny counter:\n{text}"
);
}
#[test]
fn phase_l_metrics_register_and_increment() {
let m = PrometheusMetrics::new().expect("build PrometheusMetrics");
m.record_refresh_reuse_detected();
m.observe_policy_reload_seconds(0.01);
m.observe_policy_invalidation_lag_seconds(0.2);
m.observe_revocation_propagation_seconds(0.3);
m.record_idp_refresh_failure("jwks");
m.record_scim_failure("provision");
m.record_audit_sink_failure("siem_webhook");
m.set_auth_outbox_lag_seconds(4.0);
let text = m.gather_text("");
assert!(
text.contains("udb_auth_refresh_reuse_detected_total 1"),
"missing refresh-reuse counter:\n{text}"
);
assert!(
text.contains("udb_idp_refresh_failures_total{kind=\"jwks\"} 1"),
"missing idp-refresh-failure counter:\n{text}"
);
assert!(
text.contains("udb_scim_failures_total{op=\"provision\"} 1"),
"missing scim-failure counter:\n{text}"
);
assert!(
text.contains("udb_audit_sink_failures_total{sink=\"siem_webhook\"} 1"),
"missing audit-sink-failure counter:\n{text}"
);
assert!(
text.contains("udb_auth_outbox_lag_seconds 4"),
"missing outbox-lag gauge:\n{text}"
);
assert!(
text.contains("udb_authz_policy_reload_seconds_count 1"),
"missing policy-reload histogram:\n{text}"
);
}
#[test]
fn phase_10_metrics_register_and_increment() {
let m = PrometheusMetrics::new().expect("build PrometheusMetrics");
m.inc_method_security_denial("scope");
m.inc_tenant_mismatch();
m.inc_revocation_lookup_failure();
m.inc_outbox_enqueue_failures_total("native");
m.set_native_service_degraded("storage", true);
m.inc_compensation_failures_total();
let text = m.gather_text("");
assert!(
text.contains("udb_method_security_denials_total{reason=\"scope\"} 1"),
"missing method-security-denial counter:\n{text}"
);
assert!(
text.contains("udb_tenant_mismatch_total 1"),
"missing tenant-mismatch counter:\n{text}"
);
assert!(
text.contains("udb_revocation_lookup_failures_total 1"),
"missing revocation-lookup-failure counter:\n{text}"
);
assert!(
text.contains("udb_outbox_enqueue_failures_total{path=\"native\"} 1"),
"missing outbox-enqueue-failure counter:\n{text}"
);
assert!(
text.contains("udb_native_service_degraded{service=\"storage\"} 1"),
"missing native-service-degraded gauge:\n{text}"
);
assert!(
text.contains("udb_compensation_failures_total 1"),
"missing compensation-failures counter:\n{text}"
);
}
#[test]
fn control_plane_metrics_register_and_increment() {
let m = PrometheusMetrics::new().expect("build PrometheusMetrics");
m.inc_control_reload_applied("RESOURCE_TYPE_ROUTING_POLICY");
m.inc_control_nack("RESOURCE_TYPE_RLS_TENANT_POLICY");
let text = m.gather_text("");
assert!(
text.contains(
"udb_control_reload_applied_total{resource_type=\"RESOURCE_TYPE_ROUTING_POLICY\"} 1"
),
"missing control-reload-applied counter:\n{text}"
);
assert!(
text.lines()
.any(|l| l.starts_with("udb_control_nack_total{") && l.ends_with(" 1")),
"control-nack counter did not register/increment/surface:\n{text}"
);
}
#[test]
fn control_nack_label_is_cardinality_bounded() {
let m = PrometheusMetrics::new().expect("build PrometheusMetrics");
let cap = max_distinct_labels();
for i in 0..(cap * 2) {
m.inc_control_nack(&format!("bogus-type-{i}"));
}
let text = m.gather_text("");
let series = text
.lines()
.filter(|line| line.starts_with("udb_control_nack_total{"))
.count();
assert!(
series <= cap + 1,
"control_nack series must cap at {} (+overflow), got {series}",
cap
);
}
#[test]
fn raw_dispatch_metric_registers_and_increments() {
let m = PrometheusMetrics::new().expect("build PrometheusMetrics");
m.inc_raw_dispatch_total("postgres");
let text = m.gather_text("");
assert!(
text.contains("udb_raw_dispatch_total{backend=\"postgres\"} 1"),
"missing raw-dispatch counter:\n{text}"
);
}
}