v0.2 slice 5: status reporting #5

Merged
erik merged 1 commit from v02-slice5-status into main 2026-05-27 00:39:12 +00:00
Owner

Summary

Extend dropbear status from identity-only validation to a five-section health report. With v0.2 slices 1–4 in place, this slice is the observational surface that lets you ask "what is the state of this root right now?" without rerunning sync or grepping state.sqlite.

What the command now reports

  • identity — the existing root-toml + path validation
  • file_counts — counts per classification (clean / new / modified / deleted / conflict / ignored)
  • conflicts — every StatusConflict row, with the .conflict-* sibling copies discovered on disk and the peer device id + timestamp parsed from the basename
  • peers — each peer's head, manifest id, sha256, and updated_at; plus a skipped count
  • drift — enumerate the bucket's blob set once, then flag any manifest entry (own or peer) whose sha256 is absent. This catches phantom-manifest entries from the pre-slice-3 legacy. Also reports peer-head and peer-manifest fetch failures.

Drift on its own does not change the exit code — status is observational. Exit code matches the identity check (0 when online, the existing config exit codes otherwise).

New supporting helpers

  • state.CountByStatus(ctx, status) (int, error) — single-SQL COUNT helper, used by file_counts
  • upload.ParseConflictBasename(name) — inverse of ConflictFilename, used by conflict listing to recover peer + timestamp
  • download.FetchPeerHead — head-only fetch, factored out of FetchPeer; no behavior change for sync
  • objectstore.BlobPrefix — convenience for List-based blob enumeration
  • cmd/dropbear/db.goopenOrCreateStateDB moved here from sync.go so both commands share it

JSON envelope

Follows the same schema_version=1, snake_case, optional-pointer convention as the existing sync --json envelope. Sections that don't apply (offline root, no peers, no conflicts) are omitted entirely.

Docs

New page at doc/use-cases/status.md covers the human output, the five drift issue kinds, and when to run the command. Wired into doc/use-cases/index.md and surfaced in wiki/PROJECT.md recent changes.

Test plan

  • just validate green (format / vet / lint / tests / secrets / vulns)
  • Unit tests: state.CountByStatus (table-driven, 6 statuses); ParseConflictBasename (round-trip across 6 names + 3 reject cases)
  • Integration tests in cmd/dropbear/status_test.go:
    • missing path arg → exit 2
    • offline root → exit 3 with JSON envelope still well-formed
    • online clean root → JSON shape (root_id, device_id, mode, file_counts.total=0)
    • online after sync → file_counts.clean=1, drift.ok=true
    • drift blob-missing → blob deleted from fake bucket, status flags it with the expected kind + path
    • human output → root, device, identity, files, drift lines present
  • Manual smoke test against MinIO/R2 with two real peers (deferred — not yet feasible in this session)

🤖 Generated with Claude Code

## Summary Extend `dropbear status` from identity-only validation to a five-section health report. With v0.2 slices 1–4 in place, this slice is the observational surface that lets you ask "what is the state of this root right now?" without rerunning `sync` or grepping `state.sqlite`. ### What the command now reports - **identity** — the existing root-toml + path validation - **file_counts** — counts per classification (clean / new / modified / deleted / conflict / ignored) - **conflicts** — every `StatusConflict` row, with the `.conflict-*` sibling copies discovered on disk and the peer device id + timestamp parsed from the basename - **peers** — each peer's head, manifest id, sha256, and `updated_at`; plus a skipped count - **drift** — enumerate the bucket's blob set once, then flag any manifest entry (own or peer) whose sha256 is absent. This catches phantom-manifest entries from the pre-slice-3 legacy. Also reports peer-head and peer-manifest fetch failures. Drift on its own does not change the exit code — `status` is observational. Exit code matches the identity check (0 when online, the existing config exit codes otherwise). ### New supporting helpers - `state.CountByStatus(ctx, status) (int, error)` — single-SQL COUNT helper, used by file_counts - `upload.ParseConflictBasename(name)` — inverse of `ConflictFilename`, used by conflict listing to recover peer + timestamp - `download.FetchPeerHead` — head-only fetch, factored out of `FetchPeer`; no behavior change for sync - `objectstore.BlobPrefix` — convenience for `List`-based blob enumeration - `cmd/dropbear/db.go` — `openOrCreateStateDB` moved here from `sync.go` so both commands share it ### JSON envelope Follows the same `schema_version=1`, snake_case, optional-pointer convention as the existing `sync --json` envelope. Sections that don't apply (offline root, no peers, no conflicts) are omitted entirely. ### Docs New page at `doc/use-cases/status.md` covers the human output, the five drift issue kinds, and when to run the command. Wired into `doc/use-cases/index.md` and surfaced in `wiki/PROJECT.md` recent changes. ## Test plan - [x] `just validate` green (format / vet / lint / tests / secrets / vulns) - [x] Unit tests: `state.CountByStatus` (table-driven, 6 statuses); `ParseConflictBasename` (round-trip across 6 names + 3 reject cases) - [x] Integration tests in `cmd/dropbear/status_test.go`: - missing path arg → exit 2 - offline root → exit 3 with JSON envelope still well-formed - online clean root → JSON shape (root_id, device_id, mode, file_counts.total=0) - online after sync → file_counts.clean=1, drift.ok=true - drift blob-missing → blob deleted from fake bucket, status flags it with the expected kind + path - human output → root, device, identity, files, drift lines present - [ ] Manual smoke test against MinIO/R2 with two real peers (deferred — not yet feasible in this session) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
v0.2 slice 5: status reporting
All checks were successful
Validate / validate (push) Successful in 40s
Validate / validate (pull_request) Successful in 39s
0697b79fb6
Extend `dropbear status` from identity-only validation to a five-section health report:

  - identity (existing)
  - file_counts by classification (clean/new/modified/deleted/conflict/ignored)
  - conflicts: every StatusConflict row, with the .conflict-* sibling copies discovered on disk and the peer device id + timestamp parsed from the basename
  - peers: each peer's head, manifest id, manifest sha256, and updated_at; plus a skipped count
  - drift: enumerate the bucket's blob set once, then flag any manifest entry (own or peer) whose sha256 is absent. Surfaces phantom-manifest entries from the pre-slice-3 legacy. Also reports peer-head and peer-manifest fetch failures.

Drift on its own does not exit non-zero; status is observational.

New helpers:
  - state.CountByStatus(ctx, status) (int, error)
  - upload.ParseConflictBasename(name) returns (stem, peer, ts, ext, ok)
  - download.FetchPeerHead, factored out of FetchPeer
  - objectstore.BlobPrefix, for List-based blob enumeration
  - cmd/dropbear/db.go, shared openOrCreateStateDB

The JSON envelope follows the schema_version=1, snake_case, optional-pointer convention used by sync. Sections that do not apply (offline root, no peers, etc.) are omitted entirely.

New doc page at doc/use-cases/status.md covers the human output, the five drift issue kinds, and when to run the command.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
erik merged commit 246fea08f0 into main 2026-05-27 00:39:12 +00:00
erik deleted branch v02-slice5-status 2026-05-27 00:39:12 +00:00
Sign in to join this conversation.
No description provided.