From d13ed94309b4563deedc93296d48d0c16c2fe01f Mon Sep 17 00:00:00 2001 From: damocles Date: Thu, 2 Jul 2026 21:37:47 +0200 Subject: [PATCH] docs: rfc3339 wire timestamps convention + audit-log field notes --- docs/conventions.md | 20 ++++++++++++++++++-- docs/web-ui/dashboard.md | 4 ++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/docs/conventions.md b/docs/conventions.md index 050358c6..9bd03501 100644 --- a/docs/conventions.md +++ b/docs/conventions.md @@ -237,8 +237,9 @@ Per-variant fields: `target = None` = operator-routed (dashboard); `Some(agent)` = peer-to-peer thread. - `Reminder { id, owner, message, due_at, age_seconds }` — - `due_at` is the absolute unix timestamp the scheduler is - targeting; clients compute time-until-fire as `due_at - now`. + `due_at` is the absolute time the scheduler is targeting (RFC + 3339 on the wire, see *Timestamps on the wire* below); clients + compute time-until-fire against it. - `PendingMessages { count }` — undelivered inbox messages the agent still owes itself a `recv` for. Informational + not cancellable (drain with `recv`); only emitted when `count > 0`, @@ -296,6 +297,21 @@ status_text, status_set_at, hive_name, swarm_name }`: `services.hyperhive.hiveName` / `services.hyperhive.swarmName`). Both `None` when the options aren't configured. +### Timestamps on the wire + +Timestamp fields that cross a JSON boundary (dashboard API + SSE, +the wire structs in hive-sh4re) serialize as **RFC 3339 UTC strings** +(`2026-07-02T18:30:00Z`) via `hive_sh4re::wire_time` — Rust keeps the +fields as `i64` unix seconds internally, only the JSON representation +changes, and deserialization leniently accepts both the string form +and the legacy bare integer (rolling-deploy skew, persisted blobs). +**Input-direction** fields agents compute as epoch (`first_fire_at_unix`, +schedule-edit `next_fire_at_unix`, `Wakeup::At`) stay integers. The +`*_unix` field *names* are kept for now — renaming is the wire-types +refactor's concern. The dashboard frontend parses via +`util.js::epochSec` wherever it needs arithmetic and feeds the string +straight to `new Date(s)` for display. + ## Tool groups The MCP tool surface an agent receives is derived from a set of named diff --git a/docs/web-ui/dashboard.md b/docs/web-ui/dashboard.md index 61fe767a..041235fa 100644 --- a/docs/web-ui/dashboard.md +++ b/docs/web-ui/dashboard.md @@ -676,7 +676,7 @@ target / outcome / detail); the filter box is a client-side substring match over the cached rows. The outcome badge colours `ok` green and `err` red, with an `err` whose `detail` starts `denied:` (a capability refusal) shown amber and labelled `denied` so it reads apart from an -execution failure. `ts_unix` is unix seconds; a 30 s ticker keeps the +execution failure. `ts_unix` is an RFC 3339 string; a 30 s ticker keeps the relative "ago" column honest while the tab is in view. The backing `audit_log` store records every privileged-action attempt (ok / err / denied). New entries live-append without a refresh: an `audit_entry_added` @@ -1076,7 +1076,7 @@ that's a browser-level decision, not ours. a managed container; rendered in the side panel. - `GET /api/audit-log` — agent-initiated privileged-action audit trail. Returns `{ entries, total }`: `entries` is a `Vec` - (`id`, `ts_unix` in seconds, `agent`, `action`, `target`, `outcome` + (`id`, `ts_unix` as RFC 3339, `agent`, `action`, `target`, `outcome` `"ok"`/`"err"`, `detail` nullable), newest first, server-clamped to 500; `total` is the full row count for a "latest 500 of N" header. Backs the LOGS page AUDIT sub-tab.