Watch
0
0
Fork
You've already forked hyperhive
0

Compare commits

..
35 changed files with 161 additions and 186 deletions

2
Cargo.lock generated
View file

@ -1562,7 +1562,6 @@ version = "0.1.0"
dependencies = [
"anyhow",
"axum",
"chrono",
"clap",
"futures-util",
"hive-agent-sock",
@ -1623,7 +1622,6 @@ version = "0.1.0"
dependencies = [
"anyhow",
"axum",
"chrono",
"clap",
"hive-agent-sock",
"hive-sh4re",

View file

@ -45,7 +45,6 @@ axum = { version = "0.8", features = ["ws"] }
base64 = "0.22"
bcrypt = "0.19"
chrono = { version = "0.4", default-features = false, features = [
"clock",
"serde",
"std",
] }

View file

@ -10,7 +10,6 @@ workspace = true
[dependencies]
anyhow.workspace = true
axum.workspace = true
chrono.workspace = true
reqwest.workspace = true
hyper.workspace = true
hyper-util.workspace = true

View file

@ -172,7 +172,7 @@ mod tests {
let pending = migrated.list_pending().unwrap();
assert_eq!(pending.len(), 1);
assert_eq!(pending[0].id, orig_id, "id preserved across the copy");
assert_eq!(pending[0].due_at.timestamp(), 1000);
assert_eq!(pending[0].due_at, 1000);
}
#[test]

View file

@ -12,8 +12,8 @@ use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use chrono::Utc;
use hive_claude::TokenUsage;
use hive_sh4re::wire_time::now_unix;
use rusqlite::{Connection, params};
use serde::{Deserialize, Serialize};
use tokio::sync::broadcast;
@ -390,7 +390,7 @@ impl Bus {
tx: Arc::new(tx),
event_seq: Arc::new(AtomicU64::new(0)),
store,
state: Arc::new(Mutex::new((TurnState::Idle, Utc::now().timestamp()))),
state: Arc::new(Mutex::new((TurnState::Idle, now_unix()))),
model: Arc::new(Mutex::new(initial_model)),
effort: Arc::new(Mutex::new(initial_effort)),
last_ctx_usage: Arc::new(Mutex::new(None)),
@ -575,7 +575,7 @@ impl Bus {
*self.last_ctx_usage.lock().unwrap() = Some(ctx);
*self.last_cost_usage.lock().unwrap() = Some(cost);
self.last_turn_ended_unix
.store(Utc::now().timestamp(), Ordering::Relaxed);
.store(now_unix(), Ordering::Relaxed);
self.emit(LiveEvent::TokenUsageChanged { ctx, cost });
}
@ -778,7 +778,7 @@ impl Bus {
if guard.0 == next {
return;
}
*guard = (next, Utc::now().timestamp());
*guard = (next, now_unix());
since = guard.1;
}
self.emit(LiveEvent::TurnStateChanged {
@ -861,7 +861,7 @@ impl Bus {
}
let envelope = BusEvent {
seq: self.next_seq(),
ts: Utc::now().timestamp(),
ts: now_unix(),
event,
};
// Lagged subscribers drop events — fine; the UI is a tail, not a log.

View file

@ -770,7 +770,7 @@ async fn handle_turn<S: Surface>(
unread,
});
bus.set_state(TurnState::Thinking);
let started_at = chrono::Utc::now().timestamp();
let started_at = serve_common::now_unix();
let started_instant = std::time::Instant::now();
let model_at_start = bus.model();
let prompt = serve_common::format_wake_prompt(msg_id, &from, &body, unread, redelivered);
@ -842,7 +842,7 @@ async fn handle_turn<S: Surface>(
let sid = stats.start_session(started_at, &model_at_start);
bus.set_session_id(sid);
}
let ended_at = chrono::Utc::now().timestamp();
let ended_at = serve_common::now_unix();
let duration_ms = i64::try_from(started_instant.elapsed().as_millis()).unwrap_or(i64::MAX);
let (open_threads, open_reminders) = S::post_turn_counts(socket).await;
let row = serve_common::build_row(serve_common::TurnRowArgs {

View file

@ -32,7 +32,7 @@ use std::path::Path;
use std::sync::Mutex;
use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use hive_sh4re::wire_time::now_unix;
use rusqlite::{Connection, params};
const SCHEMA: &str = r"
@ -78,7 +78,7 @@ pub struct QuestionMirror {
pub role: Role,
pub peer: String,
pub question: String,
pub asked_at: DateTime<Utc>,
pub asked_at: i64,
}
/// The harness-local questions mirror. Same sharing/locking shape as
@ -120,7 +120,7 @@ impl Questions {
conn.execute(
"INSERT OR REPLACE INTO questions (id, role, peer, question, asked_at) \
VALUES (?1, ?2, ?3, ?4, ?5)",
params![id, role.as_str(), peer, question, Utc::now().timestamp()],
params![id, role.as_str(), peer, question, now_unix()],
)?;
Ok(())
}
@ -170,14 +170,12 @@ impl Questions {
tracing::warn!(%id, %role_str, "questions mirror: unknown role, skipping row");
continue;
};
let asked_at_secs: i64 = row.get(4)?;
out.push(QuestionMirror {
id,
role,
peer: row.get(2)?,
question: row.get(3)?,
asked_at: chrono::DateTime::from_timestamp(asked_at_secs, 0)
.unwrap_or_else(Utc::now),
asked_at: row.get(4)?,
});
}
Ok(out)

View file

@ -74,7 +74,7 @@ pub async fn run(
}
fn tick(store: &Reminders, tx: &mpsc::UnboundedSender<hive_sh4re::DeliveredMessage>) {
let now = chrono::Utc::now().timestamp();
let now = hive_sh4re::wire_time::now_unix();
let due = match store.due(now, REMINDER_BATCH_LIMIT) {
Ok(rows) => rows,
Err(e) => {
@ -269,7 +269,7 @@ mod tests {
#[test]
fn resolve_due_at_in_seconds_is_close_to_now_plus_n() {
let due = resolve_due_at(&hive_sh4re::ReminderTiming::InSeconds { seconds: 60 }).unwrap();
let now = chrono::Utc::now().timestamp();
let now = hive_sh4re::wire_time::now_unix();
assert!((due - now - 60).abs() <= 2, "due={due} now={now}");
}

View file

@ -17,9 +17,8 @@ use std::path::Path;
use std::sync::Mutex;
use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use hive_sh4re::ReminderStats;
use hive_sh4re::wire_time;
use hive_sh4re::wire_time::now_unix;
use rusqlite::{Connection, params};
const SCHEMA: &str = r"
@ -43,8 +42,8 @@ pub struct Reminder {
pub id: i64,
pub message: String,
pub file_path: Option<String>,
pub due_at: DateTime<Utc>,
pub created_at: DateTime<Utc>,
pub due_at: i64,
pub created_at: i64,
}
/// The harness-local reminder store. Cheap to share behind an `Arc`; the
@ -82,7 +81,7 @@ impl Reminders {
/// Panics if the connection mutex is poisoned.
pub fn store(&self, message: &str, file_path: Option<&str>, due_at: i64) -> Result<i64> {
let conn = self.conn.lock().unwrap();
let now = Utc::now().timestamp();
let now = now_unix();
conn.execute(
"INSERT INTO reminders (message, file_path, due_at, created_at, sent_at) \
VALUES (?1, ?2, ?3, ?4, NULL)",
@ -171,7 +170,7 @@ impl Reminders {
let conn = self.conn.lock().unwrap();
conn.execute(
"UPDATE reminders SET sent_at = ?1 WHERE id = ?2",
params![Utc::now().timestamp(), id],
params![now_unix(), id],
)?;
Ok(())
}
@ -211,7 +210,7 @@ impl Reminders {
pub fn rollup(&self, since_secs: i64) -> Result<ReminderStats> {
let conn = self.conn.lock().unwrap();
let cutoff = if since_secs > 0 {
Utc::now().timestamp().saturating_sub(since_secs)
now_unix().saturating_sub(since_secs)
} else {
i64::MIN
};
@ -259,14 +258,12 @@ impl Reminders {
}
fn row_to_reminder(row: &rusqlite::Row) -> rusqlite::Result<Reminder> {
let due_at_secs: i64 = row.get(3)?;
let created_at_secs: i64 = row.get(4)?;
Ok(Reminder {
id: row.get(0)?,
message: row.get(1)?,
file_path: row.get(2)?,
due_at: wire_time::from_secs(due_at_secs),
created_at: wire_time::from_secs(created_at_secs),
due_at: row.get(3)?,
created_at: row.get(4)?,
})
}
@ -290,7 +287,7 @@ mod tests {
let pending = s.list_pending().unwrap();
assert_eq!(pending.len(), 1);
assert_eq!(pending[0].id, id);
assert_eq!(pending[0].due_at.timestamp(), 1000);
assert_eq!(pending[0].due_at, 1000);
assert_eq!(s.count_pending().unwrap(), 1);
}
@ -351,7 +348,7 @@ mod tests {
)
.unwrap();
}
let cutoff = Utc::now().timestamp() - 10;
let cutoff = now_unix() - 10;
let n = s.prune_delivered_older_than(cutoff).unwrap();
assert_eq!(n, 1, "only the backdated row is older than cutoff");
let remaining_ids: Vec<i64> = {

View file

@ -7,6 +7,7 @@
use crate::events::Bus;
use crate::turn::{TurnError, TurnOutcome};
use crate::turn_stats::TurnStatRow;
pub use hive_sh4re::wire_time::now_unix;
/// Assemble the per-turn wake prompt string. The role/tools/etc. live in the
/// system prompt; this is just the wake signal body. `id` is the broker row

View file

@ -11,11 +11,11 @@ use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use chrono::Utc;
use rusqlite::{Connection, OpenFlags};
use serde::Serialize;
use hive_sh4re::ReminderStats;
use hive_sh4re::wire_time::now_unix;
/// Window param accepted by `/api/stats?window=`. Each maps to a
/// total span + the bucket width used to roll up trend series.
@ -225,7 +225,7 @@ fn default_path() -> PathBuf {
}
fn empty_snapshot(window: Window) -> Snapshot {
let now = Utc::now().timestamp();
let now = now_unix();
let from = now - window.span_secs();
let buckets = fill_buckets(from, now, window.bucket_secs(), &HashMap::new());
Snapshot {
@ -305,7 +305,7 @@ fn snapshot(path: &Path, window: Window) -> Result<Snapshot> {
// and blanks the whole stats page. Wait out the brief write instead.
conn.busy_timeout(std::time::Duration::from_millis(500))
.with_context(|| format!("set busy_timeout on {}", path.display()))?;
let now = Utc::now().timestamp();
let now = now_unix();
// Fixed windows look back a constant span; `all` starts at the earliest
// recorded turn and sizes its buckets adaptively from that span.
let (from, bucket_secs) = match window {
@ -677,7 +677,7 @@ mod tests {
fn snapshot_aggregates_rows() {
let db = tmp_db();
let _ = std::fs::remove_file(&db);
let now = Utc::now().timestamp();
let now = now_unix();
seed_db(
&db,
&[
@ -766,17 +766,7 @@ mod tests {
fn bash_breakdown_empty_without_table() {
let db = tmp_db();
let _ = std::fs::remove_file(&db);
seed_db(
&db,
&[(
Utc::now().timestamp() - 100,
1000,
"opus",
"recv",
"ok",
"{}",
)],
);
seed_db(&db, &[(now_unix() - 100, 1000, "opus", "recv", "ok", "{}")]);
let s = snapshot(&db, Window::Day).unwrap();
assert!(s.bash_breakdown.is_empty());
}
@ -788,7 +778,7 @@ mod tests {
let db = tmp_db();
let _ = std::fs::remove_file(&db);
seed_db(&db, &[]);
let now = Utc::now().timestamp();
let now = now_unix();
let conn = Connection::open(&db).unwrap();
conn.execute_batch("CREATE TABLE bash_commands (ts INTEGER NOT NULL, head TEXT NOT NULL);")
.unwrap();

View file

@ -479,8 +479,8 @@ fn no_questions_store() -> Response {
/// `target` are derived from `role` — this agent's own label fills whichever
/// side `role` says is us, `peer` fills the other.
fn question_to_loose_end(q: QuestionMirror) -> LooseEnd {
let now = chrono::Utc::now().timestamp();
let age = u64::try_from(now.saturating_sub(q.asked_at.timestamp())).unwrap_or(0);
let now = hive_sh4re::wire_time::now_unix();
let age = u64::try_from(now.saturating_sub(q.asked_at)).unwrap_or(0);
let me = crate::identity::label();
let (asker, target) = match q.role {
Role::Asked => (me, Some(q.peer)),
@ -500,13 +500,13 @@ fn question_to_loose_end(q: QuestionMirror) -> LooseEnd {
/// "age" is how long the reminder has been *scheduled*, not how soon
/// it's due).
fn reminder_to_loose_end(r: Reminder) -> LooseEnd {
let now = chrono::Utc::now().timestamp();
let age = u64::try_from(now.saturating_sub(r.created_at.timestamp())).unwrap_or(0);
let now = hive_sh4re::wire_time::now_unix();
let age = u64::try_from(now.saturating_sub(r.created_at)).unwrap_or(0);
LooseEnd::Reminder {
id: r.id,
owner: crate::identity::label(),
message: r.message,
due_at: r.due_at,
due_at: hive_sh4re::wire_time::from_secs(r.due_at),
age_seconds: age,
}
}
@ -521,8 +521,8 @@ fn err(e: &anyhow::Error) -> Response {
/// Map a stored [`Todo`] to a [`LooseEnd::Todo`], deriving `age_seconds`
/// from `updated_at` (saturating so a backwards clock step reads 0).
fn to_loose_end(t: Todo) -> LooseEnd {
let now = chrono::Utc::now().timestamp();
let age = u64::try_from(now.saturating_sub(t.updated_at.timestamp())).unwrap_or(0);
let now = hive_sh4re::wire_time::now_unix();
let age = u64::try_from(now.saturating_sub(t.updated_at)).unwrap_or(0);
LooseEnd::Todo {
id: t.id,
subsystem: t.subsystem,

View file

@ -32,8 +32,7 @@ use std::path::Path;
use std::sync::Mutex;
use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use hive_sh4re::wire_time;
use hive_sh4re::wire_time::now_unix;
use rusqlite::{Connection, params};
/// SQL bootstrap. `CREATE TABLE IF NOT EXISTS` so first-boot agents and
@ -67,8 +66,8 @@ const MIGRATIONS: &[&str] = &[
"ALTER TABLE todos ADD COLUMN acked_at INTEGER",
];
/// One dynamic, subsystem-pushed todo. The consumer derives `age_seconds`
/// from `updated_at`.
/// One dynamic, subsystem-pushed todo. Timestamps are unix seconds; the
/// consumer derives `age_seconds` from `updated_at`.
#[derive(Debug, Clone)]
pub struct Todo {
pub id: i64,
@ -81,7 +80,7 @@ pub struct Todo {
pub summary: String,
/// Optional free-text provenance (e.g. the room name / task label).
pub source: Option<String>,
pub updated_at: DateTime<Utc>,
pub updated_at: i64,
}
/// The harness-local todo store. Cheap to share behind an `Arc`; the inner
@ -145,7 +144,7 @@ impl Todos {
source: Option<&str>,
) -> Result<(i64, bool)> {
let conn = self.conn.lock().unwrap();
let now = Utc::now().timestamp();
let now = now_unix();
let existing: Option<(i64, String, Option<String>)> = if key.is_some() {
conn.query_row(
"SELECT id, summary, source FROM todos \
@ -240,7 +239,7 @@ impl Todos {
let conn = self.conn.lock().unwrap();
let n = conn.execute(
"UPDATE todos SET acked = 1, acked_at = ?1 WHERE id = ?2 AND acked = 0",
params![Utc::now().timestamp(), id],
params![now_unix(), id],
)?;
Ok(n)
}
@ -267,14 +266,13 @@ impl Todos {
)?;
let rows = stmt
.query_map(params![subsystem], |row| {
let updated_at_secs: i64 = row.get(5)?;
Ok(Todo {
id: row.get(0)?,
subsystem: row.get(1)?,
subsystem_key: row.get(2)?,
summary: row.get(3)?,
source: row.get(4)?,
updated_at: wire_time::from_secs(updated_at_secs),
updated_at: row.get(5)?,
})
})?
.collect::<rusqlite::Result<Vec<_>>>()?;
@ -493,12 +491,12 @@ mod tests {
let conn = s.conn.lock().unwrap();
conn.execute(
"UPDATE todos SET acked_at = ?1 WHERE id = ?2",
params![Utc::now().timestamp() - 1000, old_id],
params![now_unix() - 1000, old_id],
)
.unwrap();
}
let removed = s.reap_acked(Utc::now().timestamp() - 500).unwrap();
let removed = s.reap_acked(now_unix() - 500).unwrap();
assert_eq!(removed, 1, "only the backdated row is past the cutoff");
let conn = Connection::open(dir.path().join("todos.sqlite")).unwrap();

View file

@ -416,8 +416,7 @@ fn maybe_auto_reset(bus: &Bus) {
if last_ended == 0 {
return; // no completed turn yet
}
// Compute idle seconds using the same clock semantics as elsewhere
// in this crate (unix epoch, i64 seconds via `Utc::now().timestamp()`).
// Compute idle seconds using the same clock as now_unix (unix epoch, i64).
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_secs());

View file

@ -17,6 +17,7 @@
use std::path::Path;
use std::time::Duration;
use hive_sh4re::wire_time::now_unix;
use rusqlite::{Connection, Result, params};
/// How often the sweep runs.
@ -55,8 +56,7 @@ fn sweep_once() {
let tasks_dir = harness.join("bash-tasks");
if tasks_dir.is_dir() {
let removed =
vacuum_bash_tasks(&tasks_dir, chrono::Utc::now().timestamp() - BASH_KEEP_SECS);
let removed = vacuum_bash_tasks(&tasks_dir, now_unix() - BASH_KEEP_SECS);
if removed > 0 {
tracing::info!(removed, "bash-tasks vacuum");
}
@ -92,14 +92,14 @@ fn sweep_once() {
/// `Reminders` handle).
fn vacuum_reminders(path: &Path) -> anyhow::Result<usize> {
let store = crate::reminders::Reminders::open(path)?;
store.prune_delivered_older_than(chrono::Utc::now().timestamp() - REMINDER_KEEP_SECS)
store.prune_delivered_older_than(now_unix() - REMINDER_KEEP_SECS)
}
/// Reap acked todo rows older than [`TODO_ACKED_KEEP_SECS`] via the typed
/// store API — same own-short-lived-connection shape as `vacuum_reminders`.
fn vacuum_todos(path: &Path) -> anyhow::Result<usize> {
let store = crate::todos::Todos::open(path)?;
store.reap_acked(chrono::Utc::now().timestamp() - TODO_ACKED_KEEP_SECS)
store.reap_acked(now_unix() - TODO_ACKED_KEEP_SECS)
}
/// Delete eligible bash-task trios in `dir`. Returns the count of `.json`
@ -140,15 +140,10 @@ fn should_delete(json_path: &Path, cutoff: i64) -> bool {
if !TERMINAL_STATUSES.contains(&status) {
return false;
}
// `completed_at` serializes as an RFC3339 string (`TaskFile.completed_at`
// is `Option<DateTime<Utc>>`), not a bare epoch-seconds integer — parse
// it the same way, falling back to "never expired" on anything
// unparseable so a corrupt/legacy field never causes a premature delete.
let completed_at = v
.get("completed_at")
.and_then(serde_json::Value::as_str)
.and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
.map_or(i64::MAX, |dt| dt.timestamp());
.and_then(serde_json::Value::as_i64)
.unwrap_or(i64::MAX);
completed_at < cutoff
}
@ -169,7 +164,7 @@ fn delete_trio(dir: &Path, stem: &str) {
/// agent's `events.sqlite`. Returns the number of rows deleted.
fn vacuum_events(path: &Path) -> Result<u64> {
let conn = Connection::open(path)?;
let cutoff = chrono::Utc::now().timestamp() - STREAM_KEEP_SECS;
let cutoff = now_unix() - STREAM_KEEP_SECS;
let removed = conn.execute(
"DELETE FROM events WHERE kind = 'stream' AND ts < ?1",
params![cutoff],

View file

@ -10,7 +10,6 @@ workspace = true
[dependencies]
anyhow.workspace = true
axum.workspace = true
chrono.workspace = true
clap.workspace = true
hive-agent-sock.workspace = true
hive-sh4re.workspace = true

View file

@ -38,12 +38,11 @@ fn format_task(task: &TaskFile) -> String {
let _ = write!(out, ", exit={code}");
}
if let (Some(started), None) = (task.started_at, task.completed_at) {
let elapsed = (chrono::Utc::now() - started).num_seconds();
let _ = write!(out, ", running for {elapsed}s");
let now = hive_sh4re::wire_time::now_unix();
let _ = write!(out, ", running for {}s", now - started);
}
if let (Some(completed), Some(started)) = (task.completed_at, task.started_at) {
let took = (completed - started).num_seconds();
let _ = write!(out, ", took {took}s");
let _ = write!(out, ", took {}s", completed - started);
}
let out_file = crate::paths::task_out(&task.id);
@ -296,8 +295,8 @@ mod status_hint_tests {
cmd: "echo hi".to_owned(),
timeout_secs: None,
status,
created_at: chrono::DateTime::from_timestamp(1, 0).unwrap_or_default(),
started_at: Some(chrono::DateTime::from_timestamp(1, 0).unwrap_or_default()),
created_at: 1,
started_at: Some(1),
completed_at: None,
exit_code: None,
stdout_tail: None,

View file

@ -133,6 +133,8 @@ fn signal_group(pgid: Option<i32>, sig: i32) {
// Helpers
// ---------------------------------------------------------------------------
use hive_sh4re::wire_time::now_unix;
/// Generate a task ID: `<timestamp_hex><seq_hex>`.
#[must_use]
pub fn new_task_id() -> String {
@ -269,7 +271,7 @@ pub fn submit_task(cmd: String, timeout_secs: Option<u64>, name: Option<String>)
cmd,
timeout_secs,
status: TaskStatus::Pending,
created_at: chrono::Utc::now(),
created_at: now_unix(),
started_at: None,
completed_at: None,
exit_code: None,
@ -375,7 +377,7 @@ pub fn kill_task(id: &str, force: bool) -> (bool, bool) {
&& task.status == TaskStatus::Pending
{
task.status = TaskStatus::Killed;
task.completed_at = Some(chrono::Utc::now());
task.completed_at = Some(now_unix());
let _ = write_task(&task);
// A Pending task never reached Running, so it has no keyed "active"
// todo to clear and (like the old code) fires no completion signal —
@ -433,7 +435,7 @@ async fn mark_interrupted(socket: &Path) {
}
tracing::warn!(id = %id, "bash_runner: marking interrupted task");
task.status = TaskStatus::Interrupted;
task.completed_at = Some(chrono::Utc::now());
task.completed_at = Some(now_unix());
if let Err(e) = write_task(&task) {
tracing::warn!(id = %id, error = ?e, "bash_runner: write interrupted state failed");
}
@ -488,7 +490,7 @@ async fn run_task(mut task: TaskFile, socket: &Path) {
tracing::info!(id = %id, cmd = %task.cmd, "bash_runner: starting task");
task.status = TaskStatus::Running;
task.started_at = Some(chrono::Utc::now());
task.started_at = Some(now_unix());
if let Err(e) = write_task(&task) {
tracing::warn!(id = %id, error = ?e, "bash_runner: write running state failed");
}
@ -557,7 +559,7 @@ async fn run_task(mut task: TaskFile, socket: &Path) {
};
task.status = status;
task.completed_at = Some(chrono::Utc::now());
task.completed_at = Some(now_unix());
task.exit_code = exit_code;
task.stdout_tail = stdout_tail.clone().filter(|s| !s.is_empty());
task.stderr_tail = stderr_tail.clone().filter(|s| !s.is_empty());

View file

@ -714,7 +714,7 @@ fn finish_approval(
// Phase 5b: also fire on the dashboard event channel so the
// browser moves the row out of pending into history without a
// snapshot refetch. `approved` rows that succeed get the
// approval's logged resolved_at indirectly via `Utc::now()`;
// approval's logged resolved_at indirectly via `now_unix()`;
// failures already wrote it via mark_failed above.
let approval_kind = approval.kind.as_str();
let sha_short = approval

View file

@ -538,7 +538,7 @@ fn build_transient_views(
// Clamped at 0: `since` is wall-clock now (the node's own
// `started_at`), so a backwards clock adjustment could otherwise
// render a negative age.
secs: (chrono::Utc::now() - st.since)
secs: (hive_sh4re::wire_time::from_secs(hive_sh4re::wire_time::now_unix()) - st.since)
.num_seconds()
.max(0)
.cast_unsigned(),

Some files were not shown because too many files have changed in this diff Show more