diff --git a/Cargo.lock b/Cargo.lock index b6cf5ce..d8ec7aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2649,7 +2649,7 @@ dependencies = [ [[package]] name = "ptask-bot" -version = "3.50.5" +version = "3.50.6" dependencies = [ "anyhow", "jiff", @@ -2665,7 +2665,7 @@ dependencies = [ [[package]] name = "ptask-cli" -version = "3.50.5" +version = "3.50.6" dependencies = [ "anyhow", "axum", @@ -2693,7 +2693,7 @@ dependencies = [ [[package]] name = "ptask-core" -version = "3.50.5" +version = "3.50.6" dependencies = [ "hex", "interim", @@ -2714,7 +2714,7 @@ dependencies = [ [[package]] name = "ptask-distill" -version = "3.50.5" +version = "3.50.6" dependencies = [ "anyhow", "candle-core", @@ -2738,7 +2738,7 @@ dependencies = [ [[package]] name = "ptask-notify" -version = "3.50.5" +version = "3.50.6" dependencies = [ "lettre", "ptask-core", @@ -2751,7 +2751,7 @@ dependencies = [ [[package]] name = "ptask-server" -version = "3.50.5" +version = "3.50.6" dependencies = [ "anyhow", "axum", @@ -2783,7 +2783,7 @@ dependencies = [ [[package]] name = "ptask-tui" -version = "3.50.5" +version = "3.50.6" dependencies = [ "anyhow", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index 113c712..0458a21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ members = [ resolver = "2" [workspace.package] -version = "3.50.5" +version = "3.50.6" edition = "2024" license-file = "LICENSE" authors = ["PureTensor, Inc. "] @@ -52,12 +52,12 @@ rmcp = { version = "3.3.0", features = ["server", "macros", "schemars", "transpo futures-util = { version = "0.3", default-features = false } # Internal crates -ptask-core = { path = "crates/ptask-core", version = "3.50.5" } -ptask-tui = { path = "crates/ptask-tui", version = "3.50.5" } -ptask-server = { path = "crates/ptask-server", version = "3.50.5" } -ptask-bot = { path = "crates/ptask-bot", version = "3.50.5" } -ptask-distill = { path = "crates/ptask-distill", version = "3.50.5" } -ptask-notify = { path = "crates/ptask-notify", version = "3.50.5" } +ptask-core = { path = "crates/ptask-core", version = "3.50.6" } +ptask-tui = { path = "crates/ptask-tui", version = "3.50.6" } +ptask-server = { path = "crates/ptask-server", version = "3.50.6" } +ptask-bot = { path = "crates/ptask-bot", version = "3.50.6" } +ptask-distill = { path = "crates/ptask-distill", version = "3.50.6" } +ptask-notify = { path = "crates/ptask-notify", version = "3.50.6" } # HMAC for outbound webhook signing (v0.3.5). sha2 = "0.10" diff --git a/crates/ptask-server/src/routes/metrics.rs b/crates/ptask-server/src/routes/metrics.rs index f86ea09..f536a5b 100644 --- a/crates/ptask-server/src/routes/metrics.rs +++ b/crates/ptask-server/src/routes/metrics.rs @@ -210,19 +210,15 @@ fn render(db: &Db) -> ptask_core::Result { // must describe now, not history (operator doctrine 2026-08-23) — the old // increase(pt_distill_failed_total[2h]) rule kept a WARNING latched for // two hours after full recovery. 1 = latest outcome ok (or never failed), - // 0 = latest outcome failed. Epoch comparison sidesteps the mixed - // +01:00/UTC offsets these rows carry. + // 0 = latest outcome failed. The journal's monotonic id orders outcomes + // even within one second or when the wall clock moves backwards. let distill_last_ok: i64 = db.with_conn(|c| { let n: i64 = c.query_row( - "SELECT CASE - WHEN f.t IS NULL THEN 1 - WHEN s.t IS NULL THEN 0 - WHEN s.t >= f.t THEN 1 - ELSE 0 END - FROM (SELECT MAX(CAST(strftime('%s', ts) AS INTEGER)) AS t - FROM pt_event_log WHERE event_type = 'distill.run') s, - (SELECT MAX(CAST(strftime('%s', ts) AS INTEGER)) AS t - FROM pt_event_log WHERE event_type = 'distill.failed') f", + "SELECT COALESCE( + (SELECT CASE event_type WHEN 'distill.failed' THEN 0 ELSE 1 END + FROM pt_event_log + WHERE event_type IN ('distill.run', 'distill.failed') + ORDER BY id DESC LIMIT 1), 1)", [], |r| r.get(0), )?; diff --git a/crates/ptask-server/tests/distill_outcome_order.rs b/crates/ptask-server/tests/distill_outcome_order.rs new file mode 100644 index 0000000..db3dc77 --- /dev/null +++ b/crates/ptask-server/tests/distill_outcome_order.rs @@ -0,0 +1,59 @@ +use axum::body::{Body, to_bytes}; +use axum::http::{Request, StatusCode}; +use ptask_core::Db; +use ptask_server::{AppState, router}; +use tower::ServiceExt; + +fn record(db: &Db, event_type: &str, ts: &str) { + db.with_conn(|conn| { + conn.execute( + "INSERT INTO pt_event_log (uuid, ts, event_type, payload) VALUES (?1, ?2, ?3, '{}')", + rusqlite::params![format!("{event_type}:{ts}"), ts, event_type], + )?; + Ok(()) + }) + .unwrap(); +} + +async fn last_run_ok(app: &axum::Router) -> i64 { + let response = app + .clone() + .oneshot( + Request::builder() + .uri("/metrics") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(response.status(), StatusCode::OK); + let bytes = to_bytes(response.into_body(), 1 << 20).await.unwrap(); + String::from_utf8(bytes.to_vec()) + .unwrap() + .lines() + .find_map(|line| line.strip_prefix("pt_distill_last_run_ok ")) + .expect("distill health gauge") + .parse() + .unwrap() +} + +#[tokio::test] +async fn latest_failure_in_the_same_second_is_not_reported_as_success() { + let dir = tempfile::tempdir().unwrap(); + let db = Db::open(dir.path().join("metrics.db")).unwrap(); + let app = router(AppState::new( + db.clone(), + Default::default(), + Default::default(), + )); + assert_eq!(last_run_ok(&app).await, 1, "no recorded failures"); + record(&db, "distill.run", "2026-10-01T12:00:00.100000Z"); + record(&db, "distill.failed", "2026-10-01T12:00:00.200000Z"); + assert_eq!( + last_run_ok(&app).await, + 0, + "a failure after a success in the same second must trip the health gauge" + ); + record(&db, "distill.run", "2026-10-01T12:00:00.300000Z"); + assert_eq!(last_run_ok(&app).await, 1, "a later recovery clears it"); +} diff --git a/docs/gen/pt.1 b/docs/gen/pt.1 index 19d392f..71d0f9b 100644 --- a/docs/gen/pt.1 +++ b/docs/gen/pt.1 @@ -1,6 +1,6 @@ .ie \n(.g .ds Aq \(aq .el .ds Aq ' -.TH pt 1 "pt 3.50.5" +.TH pt 1 "pt 3.50.6" .SH NAME pt \- Sovereign task manager for PureTensor .SH SYNOPSIS @@ -208,4 +208,4 @@ Generate shell completions (bash/zsh/fish) to stdout pt\-help(1) Print this message or the help of the given subcommand(s) .SH VERSION -v3.50.5 +v3.50.6