feat(proto): real GetStatus health/readiness surface #376

Merged
KofTwentyTwo merged 1 commits from story/obs-8-health-surface into dev 2026-08-01 00:40:52 +00:00
KofTwentyTwo commented 2026-08-01 00:39:26 +00:00 (Migrated from github.com)

Closes #364 (OBS-8; also references the older WS-F10).

Summary

Turns System.GetStatus from a Debug-ish status+version stub into a real, live health/readiness surface. Every field is backed by live daemon state read when the RPC is served — nothing fabricated; DB read failures degrade honestly rather than inventing numbers.

New proto shape (nuncio.v1, additive; v1 not frozen)

enum SyncState { SYNC_STATE_UNSPECIFIED=0; SYNC_STATE_IDLE=1; SYNC_STATE_SYNCING=2; SYNC_STATE_ERROR=3; }

message AccountSyncState {
  string account_id = 1;
  SyncState state = 2;
  optional google.protobuf.Timestamp last_synced = 3;
  optional string last_error = 4;
}

message GetStatusResponse {
  string engine_status = 1;            // unchanged
  string version = 2;                  // unchanged
  google.protobuf.Duration uptime = 3;
  uint64 accounts_loaded = 4;
  uint64 unread_count = 5;
  optional string last_error = 6;
  uint64 outbox_depth = 7;
  repeated AccountSyncState account_sync_states = 8;
  bool ready = 9;
  bool db_healthy = 10;
}

Descriptor golden regenerated (crates/nuncio-proto/proto/nuncio/v1/descriptor.bin); contract_stability::descriptor_matches_committed_golden passes.

What live state backs each field

  • uptime — Instant captured when the System service is constructed at daemon boot (monotonic; can't jump/go negative); reported via new time::duration_from_std.
  • accounts_loaded / account_sync_states — DatabaseEngine::list_accounts() live; per-account phase from the shared SyncDispatcher's in-flight map + new last-result tracking (sync_status()), which retains each account's last successful-sync time and last error.
  • unread_count — new DatabaseEngine::count_unread_messages() (COUNT(*) WHERE read_flag = 0).
  • outbox_depth — new DatabaseEngine::count_pending_mutations() (pending rows in pending_remote_mutations).
  • last_error — engine event-bus AppState.last_error.
  • db_healthy — new DatabaseEngine::ping() (cheap SELECT 1).
  • ready — db_healthy && engine not ShuttingDown.

Honestly omitted/degraded, never faked: if a store read fails it logs a warning and reports 0/empty (already signalled by db_healthy=false); last_synced/last_error are absent for an account that hasn't synced since boot.

CLI

nuncio-cli system status renders uptime, ready/db_healthy, accounts_loaded/unread/outbox_depth, last_error, and per-account state; --json carries all fields coherently inside the {status:ok,data:{…}} envelope.

Tests

  • nuncio-proto: golden/contract stability + GetStatusResponse field round-trip; 32 passed.
  • sync_dispatcher: new sync_status unit tests (idle default, syncing-in-flight, idle+last-synced after success, error after failure).
  • nunciod grpc: existing accepts-test asserts uptime>0 / db_healthy / ready / zero counts; new get_status_reports_live_accounts_and_outbox_depth proves accounts_loaded=2, outbox_depth=1, and per-account IDLE against a live in-memory daemon.
  • CLI happy-path test asserts the new JSON fields.
  • Full workspace: ~582 passed, 0 failed, 0 ignored.

Gate (all green, run as three separate commands)

  • cargo fmt --all -- --check — clean
  • cargo check-all (clippy --all-targets --workspace -D warnings) — 0 warnings
  • cargo test-all — ~582 passed, 0 failed, 0 ignored

No unsafe/unwrap/expect/panic/todo in non-test code; no issue/story breadcrumbs in .rs/.proto.

Closes #364 (OBS-8; also references the older WS-F10). ## Summary Turns `System.GetStatus` from a Debug-ish status+version stub into a real, live health/readiness surface. Every field is backed by live daemon state read when the RPC is served — nothing fabricated; DB read failures degrade honestly rather than inventing numbers. ## New proto shape (`nuncio.v1`, additive; v1 not frozen) ```proto enum SyncState { SYNC_STATE_UNSPECIFIED=0; SYNC_STATE_IDLE=1; SYNC_STATE_SYNCING=2; SYNC_STATE_ERROR=3; } message AccountSyncState { string account_id = 1; SyncState state = 2; optional google.protobuf.Timestamp last_synced = 3; optional string last_error = 4; } message GetStatusResponse { string engine_status = 1; // unchanged string version = 2; // unchanged google.protobuf.Duration uptime = 3; uint64 accounts_loaded = 4; uint64 unread_count = 5; optional string last_error = 6; uint64 outbox_depth = 7; repeated AccountSyncState account_sync_states = 8; bool ready = 9; bool db_healthy = 10; } ``` Descriptor golden regenerated (`crates/nuncio-proto/proto/nuncio/v1/descriptor.bin`); `contract_stability::descriptor_matches_committed_golden` passes. ## What live state backs each field - **uptime** — `Instant` captured when the System service is constructed at daemon boot (monotonic; can't jump/go negative); reported via new `time::duration_from_std`. - **accounts_loaded / account_sync_states** — `DatabaseEngine::list_accounts()` live; per-account phase from the shared `SyncDispatcher`'s in-flight map + new last-result tracking (`sync_status()`), which retains each account's last successful-sync time and last error. - **unread_count** — new `DatabaseEngine::count_unread_messages()` (`COUNT(*) WHERE read_flag = 0`). - **outbox_depth** — new `DatabaseEngine::count_pending_mutations()` (pending rows in `pending_remote_mutations`). - **last_error** — engine event-bus `AppState.last_error`. - **db_healthy** — new `DatabaseEngine::ping()` (cheap `SELECT 1`). - **ready** — `db_healthy && engine not ShuttingDown`. Honestly omitted/degraded, never faked: if a store read fails it logs a warning and reports 0/empty (already signalled by `db_healthy=false`); `last_synced`/`last_error` are absent for an account that hasn't synced since boot. ## CLI `nuncio-cli system status` renders uptime, ready/db_healthy, accounts_loaded/unread/outbox_depth, last_error, and per-account state; `--json` carries all fields coherently inside the `{status:ok,data:{…}}` envelope. ## Tests - `nuncio-proto`: golden/contract stability + `GetStatusResponse` field round-trip; 32 passed. - `sync_dispatcher`: new `sync_status` unit tests (idle default, syncing-in-flight, idle+last-synced after success, error after failure). - `nunciod` grpc: existing accepts-test asserts uptime>0 / db_healthy / ready / zero counts; new `get_status_reports_live_accounts_and_outbox_depth` proves accounts_loaded=2, outbox_depth=1, and per-account IDLE against a live in-memory daemon. - CLI happy-path test asserts the new JSON fields. - Full workspace: ~582 passed, 0 failed, 0 ignored. ## Gate (all green, run as three separate commands) - `cargo fmt --all -- --check` — clean - `cargo check-all` (clippy `--all-targets --workspace -D warnings`) — 0 warnings - `cargo test-all` — ~582 passed, 0 failed, 0 ignored No `unsafe`/`unwrap`/`expect`/`panic`/`todo` in non-test code; no issue/story breadcrumbs in `.rs`/`.proto`.
Sign in to join this conversation.