status: per volume×destination coverage + durability panel (CLI + TUI)#177
Merged
mbertschler merged 6 commits intoJul 24, 2026
Conversation
…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
…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
There was a problem hiding this comment.
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
statuspackage 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.
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
…ng-states' into review-177
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
merged commit Jul 24, 2026
0c4c34f
into
claude/issue-157-run-rows-standing-states
3 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The two standing questions no surface could answer now have one answer,
built once and consumed twice:
target) grid replaces the single LAST SYNC cell that hid a week-behind
target behind a fresh ✓ earned by any other target.
coverage, verify method, evidence age, and offloadable bytes, surfaced
directly instead of only through
offload --dry-runside 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
statuspackage — the shared query layer.status.Build(ctx, s, cfg)produces, per configured volume and per (volume × target):sync_every(late is relative to the pair's cadence, not a globalconstant);
refused (a gate that regressed from a prior success, red), and
needs-bootstrap (a first-use
--initrefusal on a pair that neversucceeded — amber, an expected human step, not a fault);
content origin, the verify method behind it, and evidence age vs
offload_max_evidence_age(stale flagged);Each cell carries a
Level(neutral/ok/amber/red); the report's worstlevel 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 whatan offload would move; reads only the index.
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
exitCodeErrorcarries the code out of cobra without printing.
same query layer, with the STATE and DURABLE cells coloured per level.
The old
formatLast/latestByVolpath (single LAST SYNC cell) is gone.Schema
None. This is a query/display layer over existing tables — no migration,
no
SchemaVersionbump.Testing
statuspackage: end-to-endBuildtests (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).
post-sync → green + exit 0 (skips without rclone), unknown-volume and
missing-config errors.
go vet ./...,go test ./...green;golangci-lint runclean on thechanged 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
mainonce #157 merges.🤖 Generated with Claude Code
https://claude.ai/code/session_014Skm2Qm6wWUB7dD4CpPTz7
Generated by Claude Code