Skip to content

status: per volume×destination coverage + durability panel (CLI + TUI)#177

Merged
mbertschler merged 6 commits into
claude/issue-157-run-rows-standing-statesfrom
claude/issue-159-status-command
Jul 24, 2026
Merged

status: per volume×destination coverage + durability panel (CLI + TUI)#177
mbertschler merged 6 commits into
claude/issue-157-run-rows-standing-statesfrom
claude/issue-159-status-command

Conversation

@mbertschler

Copy link
Copy Markdown
Owner

Summary

The two standing questions no surface could answer now have one answer,
built once and consumed twice:

  • "Is every configured target caught up?" (F16) — a per-(volume ×
    target) grid replaces the single LAST SYNC cell that hid a week-behind
    target behind a fresh ✓ earned by any other target.
  • "What is durable where, and what could I offload?" (F17/F23) — vector
    coverage, verify method, evidence age, and offloadable bytes, surfaced
    directly instead of only through offload --dry-run side effects.

This is read-only introspection — a question per ux-principle 2. It opens
no run, reads no disk bytes, validates no marker, and mutates nothing.

Changes

  • New status package — the shared query layer. status.Build(ctx, s, cfg) produces, per configured volume and per (volume × target):
    • last successful sync + age, coloured against that pair's own
      sync_every
      (late is relative to the pair's cadence, not a global
      constant);
    • the runs: every failure and refusal becomes a run row or a standing state #157 standing states: alarm (latched verify mismatch, red),
      refused (a gate that regressed from a prior success, red), and
      needs-bootstrap (a first-use --init refusal on a pair that never
      succeeded — amber, an expected human step, not a fault);
    • durability: whether the target's vector covers this node's local
      content origin, the verify method behind it, and evidence age vs
      offload_max_evidence_age (stale flagged);
    • offload readiness: "N GB offloadable now" per volume.
      Each cell carries a Level (neutral/ok/amber/red); the report's worst
      level is the single "am I safe?" answer. Neutral text labels live in the
      package so both surfaces render identical words.
  • offload.Readiness — reuses the real gate (loadGate +
    selectCandidates + gate.check), so the offloadable totals match what
    an offload would move; reads only the index.
  • Step 1 — squirrel status [volume] (cmd/squirrel/status.go):
    renders the grid and exits with the worst level (0 green / 1 amber / 2
    red) so the same command scripts a health check. A small exitCodeError
    carries the code out of cobra without printing.
  • Step 2 — TUI dashboard: the top panel is now this grid, built from the
    same query layer, with the STATE and DURABLE cells coloured per level.
    The old formatLast/latestByVol path (single LAST SYNC cell) is gone.

Schema

None. This is a query/display layer over existing tables — no migration,
no SchemaVersion bump.

Testing

  • status package: end-to-end Build tests (fully-durable green,
    needs-bootstrap amber, latched alarm red, never-indexed amber, relayed
    required target amber) plus table-driven unit tests for the pure logic
    (cadence colouring, standing classification, durability level, evidence
    staleness, exit codes, byte formatting, target ordering, state labels).
  • CLI: never-synced → amber + exit 1 with cobra's error line silenced,
    post-sync → green + exit 0 (skips without rclone), unknown-volume and
    missing-config errors.
  • TUI: dashboard run-partitioning test updated to the new signature.
  • go vet ./..., go test ./... green; golangci-lint run clean on the
    changed packages (linter rebuilt with the module's go1.26.1 toolchain —
    the packaged binary targeted an older Go).

Closes #159

Stacked on #157 (PR #174). Base is #157's branch so the diff shows only this
delta; retargets to main once #157 merges.

🤖 Generated with Claude Code

https://claude.ai/code/session_014Skm2Qm6wWUB7dD4CpPTz7


Generated by Claude Code

…nel (#159)

The two standing questions no surface could answer — "is every configured
target caught up?" (F16) and "what is durable where, and what could I
offload?" (F17/F23) — now have one answer, built once and shown two ways.

New `status` package: a read-only query layer that produces, per configured
volume and per (volume × target), the facts both surfaces render:

- last successful sync + age, coloured against THAT pair's own sync_every
  (late is relative to the pair's cadence, not a global constant);
- the #157 standing states — alarm (latched verify mismatch, red), refused
  (a gate that regressed from a prior success, red), and needs-bootstrap (a
  first-use `--init` refusal on a pair that never succeeded, amber);
- durability: whether the target's vector covers this node's local content
  origin, the verify method behind it, and evidence age vs
  offload_max_evidence_age (stale flagged);
- offload readiness: bytes/files currently passing the gate per volume
  ("N GB offloadable now" — the F17 decision-support without the side
  effects of `offload --dry-run`).

Each cell carries a Level (neutral/ok/amber/red); the report's worst level
is the single "am I safe?" answer.

Step 1 — `squirrel status [volume]`: renders the grid and exits with the
worst level (0 green, 1 amber, 2 red) so the same command scripts a health
check. A read-only *question* per ux-principle 2; it mutates nothing.

Step 2 — the TUI dashboard's top panel is now this grid (replacing the
single LAST SYNC cell that hid a week-behind target behind a fresh check),
built from the SAME query layer, with the STATE and DURABLE cells coloured.

Offload readiness reuses the real gate (offload.Readiness shares loadGate +
selectCandidates + gate.check), so the totals match what an offload would
move; it reads only the index — no run, no disk, no marker check.

No schema change: this is a query/display layer over existing tables.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Skm2Qm6wWUB7dD4CpPTz7
Copilot AI review requested due to automatic review settings July 24, 2026 11:54
claude added 2 commits July 24, 2026 11:56
…ng-states' into claude/issue-159-status-command
Merging #157's latest brought its TOCTOU follow-up, which changed
store.RaiseDestinationAlarm to return (raised bool, err error). The alarm
Build test called the old single-return form; discard the raised bool it
does not assert on. No production code called RaiseDestinationAlarm, so the
merge is the only other change. go vet, go test ./..., and golangci-lint
(2.12.2) all pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Skm2Qm6wWUB7dD4CpPTz7

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a shared, read-only status query layer that answers “am I safe?” per (volume × target) and reuses it across a new squirrel status CLI command and the TUI dashboard, including durability/evidence details and offload-readiness totals.

Changes:

  • Introduces new status package with per-volume/target coverage, standing states, durability/evidence age, and worst-level aggregation.
  • Adds squirrel status [volume] CLI with scriptable exit codes (0/1/2) driven by the report’s worst level.
  • Reworks the TUI dashboard to render the per-(volume × target) grid via the shared status layer (replacing the old single “LAST SYNC” per volume).

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tui/tui.go Wires config into dashboard model construction.
tui/dashboard.go Replaces volumes table with shared coverage/durability grid; loads coverage via status.Build.
tui/dashboard_test.go Updates dashboard data-load test for new coverage behavior when config is nil.
status/status.go Defines report model: levels, standing states, and core data structures.
status/runs.go Helpers for retrieving run ages/outcomes and classifying targets against config.
status/labels.go Shared neutral text labels for CLI/TUI rendering.
status/durability.go Computes per-target durability coverage, evidence age, and staleness.
status/build.go Builds the full report from config + store, including offload readiness.
status/logic_test.go Unit tests for pure logic (cadence coloring, labels, ordering, etc.).
status/build_test.go End-to-end Build tests for key scenarios (durable, bootstrap-needed, alarm, etc.).
offload/readiness.go Adds a read-only offload readiness tally matching the real offload gate logic.
cmd/squirrel/status.go Adds the status command and grid rendering.
cmd/squirrel/status_test.go CLI tests for exit codes, output, and config/volume error handling.
cmd/squirrel/root.go Registers the new status subcommand.
cmd/squirrel/main.go Adds exitCodeError handling to allow non-1 exit codes from cobra commands.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread status/labels.go Outdated
Comment thread status/build.go Outdated
Comment thread tui/dashboard.go
Comment thread status/build.go
Comment thread offload/readiness.go Outdated
claude added 3 commits July 24, 2026 12:07
Five review comments, all in one round:

1. PARTIAL terminal outcome (status/labels.go StateLabel, status/build.go
   syncLevel): a 'partial' latest terminal run is terminal and degraded, but
   both sites only special-cased 'failed' and fell through to freshness
   (ok/late). A latest 'partial' now renders "partial"/amber in both — it is
   more recent than any success, so freshness must not mask it.

2. Never-indexed volume offload policy (status/build.go buildVolume): the
   never-indexed early return left Offload.Applicable=false, so a volume that
   declares offload_requires printed "offload: no policy". Applicable is now
   set from the config policy up front (present offload_requires), independent
   of whether a tally ran.

3. Single-pass readiness (offload/readiness.go): Readiness built and sorted a
   full candidate slice via selectCandidates and then iterated it again;
   ordering is irrelevant to totals. It now walks the index map once,
   applying the same present + not-under-reserved-subtree predicate and the
   same gate.check. tallyReadiness removed.

4. TUI readiness cost (tui/dashboard.go): the 1s dashboard tick called
   status.Build → offload.Readiness (whole-index gate pass) every second,
   risking deadline misses on large volumes. Added status.Options
   {SkipOffloadReadiness} + BuildWithOptions; the CLI keeps the full Build.
   The dashboard rebuilds the coverage/durability grid every tick with
   readiness skipped and refreshes the offload tally on a slower cadence
   (readinessRefreshTicks) into a per-volume cache, overlaid at render, so
   "N offloadable now" stays visible without the per-tick cost.

Tests: partial in StateLabel + a syncLevel terminal-outcome table; never-
indexed-reports-policy and skip-vs-full readiness Build tests; dashboard
test updated for the new loadDashboardData arity. go vet, go test ./...,
golangci-lint (2.12.2) all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Skm2Qm6wWUB7dD4CpPTz7
The status command sets SilenceErrors so its scriptable green/amber/red
exit-code signal (an exitCodeError) doesn't leak a cobra "Error:" line.
But SilenceErrors is unconditional: it also silenced genuine failures —
an unknown volume, a missing config, a store error — which then reached
neither cobra's printer nor main.go's, leaving the user a bare exit 1
with no message. The existing tests missed it because they assert on the
returned error value, not on user-visible output.

Print non-exitCodeError errors from the command's RunE (the exit-code
signal stays silent), and extend the unknown-volume and missing-config
tests to assert the message actually reaches stderr.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Skm2Qm6wWUB7dD4CpPTz7
@mbertschler
mbertschler merged commit 0c4c34f into claude/issue-157-run-rows-standing-states Jul 24, 2026
3 checks passed
@mbertschler
mbertschler deleted the claude/issue-159-status-command branch July 24, 2026 21:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants