From e5920788685adf7e23281cb00e454ab1a5ae7ed7 Mon Sep 17 00:00:00 2001 From: atlas Date: Sun, 25 Oct 2026 18:11:05 +0100 Subject: [PATCH] move client-cert gauges behind an otel feature of swarm-secret-client, drop the crate --- AGENTS.md | 4 +--- Cargo.lock | 16 ++-------------- Cargo.toml | 2 -- docs/scheduler/observability.md | 2 +- hive-agent/Cargo.toml | 4 +--- hive-agent/src/otel_turn_metrics.rs | 2 +- hive-c0re/Cargo.toml | 4 +--- hive-c0re/src/stats/otel_metrics.rs | 2 +- nix/host-modules/swarm-grafana.nix | 2 +- swarm-cert-metrics/Cargo.toml | 17 ----------------- swarm-controller/Cargo.toml | 4 +--- swarm-controller/src/main.rs | 5 ++++- swarm-review-classifier/Cargo.toml | 4 +--- swarm-review-classifier/src/cost.rs | 2 +- swarm-secret-client/Cargo.toml | 8 ++++++++ swarm-secret-client/src/cert.rs | 16 ++-------------- .../src/cert_metrics.rs | 13 +++++-------- swarm-secret-client/src/lib.rs | 2 ++ .../test-cert.pem | 0 19 files changed, 33 insertions(+), 76 deletions(-) delete mode 100644 swarm-cert-metrics/Cargo.toml rename swarm-cert-metrics/src/lib.rs => swarm-secret-client/src/cert_metrics.rs (94%) rename {swarm-cert-metrics => swarm-secret-client}/test-cert.pem (100%) diff --git a/AGENTS.md b/AGENTS.md index d9b46399..68683535 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -138,9 +138,7 @@ Swarm-level: the **`BAO_`** env spellings explicitly — `vaultrs`'s `VAULT_*` defaults are set by no unit here, and falling through to them yields a client with **no identity** and a TLS failure that names no cause. -- **`swarm-cert-metrics/`** — the `bao.client_cert.not_before` / `not_after` - observable gauges a service registers on its existing OTLP exporter; the - cert file is read at each collection. + Feature `otel` adds `cert_metrics`, the `bao.client_cert.*` gauges. - **`swarm-queue-client/`** — the one authenticated connect to the swarm NATS queue. - **`swarm-wire/`** — serde-only request and reply contracts services diff --git a/Cargo.lock b/Cargo.lock index 316a3015..3c5a9fad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1691,7 +1691,6 @@ dependencies = [ "serde_json", "sha1", "swarm-agent-verify", - "swarm-cert-metrics", "swarm-queue-client", "swarm-secret-client", "swarm-wire", @@ -1802,7 +1801,6 @@ dependencies = [ "serde_json", "sha2 0.11.0", "strum", - "swarm-cert-metrics", "swarm-matrix-client", "swarm-queue-client", "swarm-secret-client", @@ -4839,16 +4837,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "swarm-cert-metrics" -version = "0.1.0" -dependencies = [ - "opentelemetry", - "opentelemetry_sdk", - "swarm-secret-client", - "tracing", -] - [[package]] name = "swarm-ci-metrics" version = "0.1.0" @@ -4921,7 +4909,6 @@ dependencies = [ "similar", "strum", "swarm-authelia-bridge-sock", - "swarm-cert-metrics", "swarm-controller-api", "swarm-matrix-client", "swarm-queue-client", @@ -5108,7 +5095,6 @@ dependencies = [ "opentelemetry_sdk", "serde", "serde_json", - "swarm-cert-metrics", "swarm-queue-client", "swarm-secret-client", "swarm-wire", @@ -5123,6 +5109,8 @@ version = "0.1.0" dependencies = [ "backon", "futures-util", + "opentelemetry", + "opentelemetry_sdk", "reqwest", "rustify", "rustify_derive", diff --git a/Cargo.toml b/Cargo.toml index a758c8a4..cbea8d23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,6 @@ members = [ "swarm-agent-verify", "swarm-authelia-bridge", "swarm-builder", - "swarm-cert-metrics", "swarm-ci-metrics", "swarm-claude-login", "swarm-authelia-bridge-sock", @@ -119,7 +118,6 @@ hive-sock-client = { path = "hive-sock-client" } hive-types = { path = "hive-types" } swarm-agent-verify = { path = "swarm-agent-verify" } swarm-authelia-bridge-sock = { path = "swarm-authelia-bridge-sock" } -swarm-cert-metrics = { path = "swarm-cert-metrics" } swarm-controller-api = { path = "swarm-controller-api" } swarm-matrix-client = { path = "swarm-matrix-client" } swarm-queue-client = { path = "swarm-queue-client" } diff --git a/docs/scheduler/observability.md b/docs/scheduler/observability.md index 21ed0f70..1fde60f1 100644 --- a/docs/scheduler/observability.md +++ b/docs/scheduler/observability.md @@ -572,7 +572,7 @@ data point each, unix seconds: `cert` names the identity (`client`, `controller`, `agent`, `review-classifier`, `ci-metrics`); the resource labels (`service.name`, `hive`, `agent`) say who -holds it. The `swarm-cert-metrics` crate registers both gauges on the service's +holds it. `swarm_secret_client::cert_metrics` (cargo feature `otel`) registers both gauges on the service's existing exporter and reads `BAO_CLIENT_CERT` at every collection, so a renewed leaf shows up on the next export. A file that's missing, or that isn't a certificate yields no data point and a debug log line. The parser is diff --git a/hive-agent/Cargo.toml b/hive-agent/Cargo.toml index 5849defc..aa9bd09c 100644 --- a/hive-agent/Cargo.toml +++ b/hive-agent/Cargo.toml @@ -58,11 +58,9 @@ swarm-queue-client = { workspace = true, features = [ ] } # The spelling of the token the queue connection presents. swarm-agent-verify.workspace = true -# The `bao.client_cert.*` gauges on this exporter. -swarm-cert-metrics.workspace = true swarm-wire.workspace = true # This agent's own queue secret, read from the store under its own certificate. -swarm-secret-client.workspace = true +swarm-secret-client = { workspace = true, features = ["otel"] } # The directory an AGENTS.md edit is applied in, and the file it is written # through. tempfile = "3" diff --git a/hive-agent/src/otel_turn_metrics.rs b/hive-agent/src/otel_turn_metrics.rs index 34185454..592c6c27 100644 --- a/hive-agent/src/otel_turn_metrics.rs +++ b/hive-agent/src/otel_turn_metrics.rs @@ -181,7 +181,7 @@ fn build() -> Option { Ok(provider) => { tracing::info!(?interval, "otel turn-metrics: exporter enabled"); let meter = provider.meter("hyperhive.turn_stats"); - swarm_cert_metrics::register(&meter, "agent"); + swarm_secret_client::cert_metrics::register(&meter, "agent"); Some(Instruments { turn_duration_ms: meter .u64_histogram("hyperhive.agent.turn.duration") diff --git a/hive-c0re/Cargo.toml b/hive-c0re/Cargo.toml index fa46080b..54d8572e 100644 --- a/hive-c0re/Cargo.toml +++ b/hive-c0re/Cargo.toml @@ -62,10 +62,8 @@ swarm-queue-client = { workspace = true, features = [ "agent-permissions", "agent-archetype", ] } -# The `bao.client_cert.*` gauges on this exporter. -swarm-cert-metrics.workspace = true swarm-matrix-client.workspace = true -swarm-secret-client.workspace = true +swarm-secret-client = { workspace = true, features = ["otel"] } swarm-wire.workspace = true tokio.workspace = true tokio-stream.workspace = true diff --git a/hive-c0re/src/stats/otel_metrics.rs b/hive-c0re/src/stats/otel_metrics.rs index 5f9149c1..1b099f71 100644 --- a/hive-c0re/src/stats/otel_metrics.rs +++ b/hive-c0re/src/stats/otel_metrics.rs @@ -262,7 +262,7 @@ fn register_instruments(provider: &SdkMeterProvider, snapshot: Arc Result<()> { fn register_cert_gauges(provider: Option<&opentelemetry_sdk::metrics::SdkMeterProvider>) { use opentelemetry::metrics::MeterProvider as _; if let Some(provider) = provider { - swarm_cert_metrics::register(&provider.meter("hyperhive.bao_client_cert"), "controller"); + swarm_secret_client::cert_metrics::register( + &provider.meter("hyperhive.bao_client_cert"), + "controller", + ); } } diff --git a/swarm-review-classifier/Cargo.toml b/swarm-review-classifier/Cargo.toml index acf91af0..5aa5e6dc 100644 --- a/swarm-review-classifier/Cargo.toml +++ b/swarm-review-classifier/Cargo.toml @@ -25,12 +25,10 @@ opentelemetry-otlp.workspace = true serde.workspace = true serde_json.workspace = true # The try-out subject and the shapes the controller sends and reads back. -# The `bao.client_cert.*` gauges on this exporter. -swarm-cert-metrics.workspace = true swarm-queue-client.workspace = true swarm-wire.workspace = true # The API key and queue client secret paths, and the store login. -swarm-secret-client.workspace = true +swarm-secret-client = { workspace = true, features = ["otel"] } tokio.workspace = true tracing.workspace = true diff --git a/swarm-review-classifier/src/cost.rs b/swarm-review-classifier/src/cost.rs index a0cc8203..5a5018c8 100644 --- a/swarm-review-classifier/src/cost.rs +++ b/swarm-review-classifier/src/cost.rs @@ -171,7 +171,7 @@ fn build() -> Option<(SdkMeterProvider, Counters)> { .build(); let provider = SdkMeterProvider::builder().with_reader(reader).build(); let counters = counters(&provider); - swarm_cert_metrics::register( + swarm_secret_client::cert_metrics::register( &provider.meter("hyperhive.review_classifier"), "review-classifier", ); diff --git a/swarm-secret-client/Cargo.toml b/swarm-secret-client/Cargo.toml index ed694402..ab36b333 100644 --- a/swarm-secret-client/Cargo.toml +++ b/swarm-secret-client/Cargo.toml @@ -13,6 +13,9 @@ futures-util.workspace = true # the `BAO_*` files) rather than by its env defaults, so the dependency is # direct rather than incidental. reqwest.workspace = true +# `cert_metrics`: the `bao.client_cert.*` gauges, for the services that export +# OTLP. Off by default so the other dependents link no OTel. +opentelemetry = { workspace = true, optional = true } # vaultrs's own endpoint machinery, for the one endpoint it does not # implement — see `client::WriteAclPolicy`. rustify.workspace = true @@ -40,6 +43,8 @@ workspace = true # provider compiled in rustls picks it without an install, so the provider # test would pass with `install_crypto_provider` doing nothing. [dev-dependencies] +# `InMemoryMetricExporter`, to read back what `cert_metrics` exports. +opentelemetry_sdk = { workspace = true, features = ["testing"] } rustls = { workspace = true, features = ["ring"] } # `test-util` for `#[tokio::test(start_paused = true)]`: the re-read backoff # tests run on paused time. @@ -47,3 +52,6 @@ tokio = { workspace = true, features = ["test-util"] } # Capturing what a re-read logs, to check it names the client and never the # secret. tracing-subscriber.workspace = true + +[features] +otel = ["dep:opentelemetry"] diff --git a/swarm-secret-client/src/cert.rs b/swarm-secret-client/src/cert.rs index e27b5490..6389a597 100644 --- a/swarm-secret-client/src/cert.rs +++ b/swarm-secret-client/src/cert.rs @@ -46,23 +46,11 @@ pub fn file_validity(path: &Path) -> Result<(i64, i64), Error> { } #[cfg(test)] -mod tests { +pub(crate) mod tests { use super::*; /// Self-signed, CN `hive-agent-atlas`, valid 2026-01-01T00:00:00Z to - /// 2026-04-01T00:00:00Z. Its key was discarded. - pub(crate) const CERT_PEM: &str = "-----BEGIN CERTIFICATE----- -MIIBizCCATGgAwIBAgIUSFpuYmy1UoHvq/MYiyrAudRaWXcwCgYIKoZIzj0EAwIw -GzEZMBcGA1UEAwwQaGl2ZS1hZ2VudC1hdGxhczAeFw0yNjAxMDEwMDAwMDBaFw0y -NjA0MDEwMDAwMDBaMBsxGTAXBgNVBAMMEGhpdmUtYWdlbnQtYXRsYXMwWTATBgcq -hkjOPQIBBggqhkjOPQMBBwNCAAR7p2eZAxjiE1RdsuSHE3CcTjbnd4swzfnmqUPW -9NJN7mvOtqEzxCKfOSFjaIhhyShIQ41/V6vGi80EPDB+uR56o1MwUTAdBgNVHQ4E -FgQUxgZFsN1VY3ODrPS1NUfY/x3EnmswHwYDVR0jBBgwFoAUxgZFsN1VY3ODrPS1 -NUfY/x3EnmswDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiEAnU9e -WOioNTUNK9b6FignejpK5FpyrODUuH/iL4TqCh0CIAz6GzJeLebhRPvYlfVUofdV -hWx3sOwmUgjkRQXoxY+p ------END CERTIFICATE----- -"; + pub(crate) const CERT_PEM: &str = include_str!("../test-cert.pem"); pub(crate) const NOT_BEFORE: i64 = 1_767_225_600; pub(crate) const NOT_AFTER: i64 = 1_775_001_600; diff --git a/swarm-cert-metrics/src/lib.rs b/swarm-secret-client/src/cert_metrics.rs similarity index 94% rename from swarm-cert-metrics/src/lib.rs rename to swarm-secret-client/src/cert_metrics.rs index 6e91ffd1..8396044f 100644 --- a/swarm-cert-metrics/src/lib.rs +++ b/swarm-secret-client/src/cert_metrics.rs @@ -1,4 +1,4 @@ -//! The two gauges every service holding a store client certificate exports: +//! (feature `otel`) The two gauges every service holding a store client certificate exports: //! when that certificate became valid and when it stops being valid. //! //! `bao.client_cert.not_before` and `bao.client_cert.not_after`, unix seconds, @@ -17,9 +17,9 @@ use std::path::Path; +use crate::{cert, client::ENV_CLIENT_CERT}; use opentelemetry::KeyValue; use opentelemetry::metrics::Meter; -use swarm_secret_client::{cert, client::ENV_CLIENT_CERT}; /// Register both gauges on `meter` for the certificate [`ENV_CLIENT_CERT`] /// names, reported under `cert`. @@ -97,10 +97,7 @@ mod tests { use opentelemetry_sdk::metrics::{InMemoryMetricExporter, PeriodicReader, SdkMeterProvider}; use std::path::PathBuf; - /// Self-signed, valid 2026-01-01T00:00:00Z to 2026-04-01T00:00:00Z. - const CERT_PEM: &str = include_str!("../test-cert.pem"); - const NOT_BEFORE_SECS: i64 = 1_767_225_600; - const NOT_AFTER_SECS: i64 = 1_775_001_600; + use crate::cert::tests::{CERT_PEM, NOT_AFTER, NOT_BEFORE}; /// A provider with both gauges registered for `path`, and the exporter it /// flushes into. @@ -163,12 +160,12 @@ mod tests { ( cert::NOT_AFTER_METRIC.to_owned(), "controller".to_owned(), - NOT_AFTER_SECS + NOT_AFTER ), ( cert::NOT_BEFORE_METRIC.to_owned(), "controller".to_owned(), - NOT_BEFORE_SECS + NOT_BEFORE ), ] ); diff --git a/swarm-secret-client/src/lib.rs b/swarm-secret-client/src/lib.rs index 754683bf..38e27a64 100644 --- a/swarm-secret-client/src/lib.rs +++ b/swarm-secret-client/src/lib.rs @@ -32,6 +32,8 @@ pub mod acp; pub mod builder; pub mod cert; +#[cfg(feature = "otel")] +pub mod cert_metrics; pub mod ci; pub mod ci_metrics; pub mod claude_login; diff --git a/swarm-cert-metrics/test-cert.pem b/swarm-secret-client/test-cert.pem similarity index 100% rename from swarm-cert-metrics/test-cert.pem rename to swarm-secret-client/test-cert.pem