Skip to content

Admin CLI: check / rebuild-rollups / inspect-segment / verify-period / export-parquet#18

Merged
pbudzik merged 1 commit into
mainfrom
feat/admin-cli
May 16, 2026
Merged

Admin CLI: check / rebuild-rollups / inspect-segment / verify-period / export-parquet#18
pbudzik merged 1 commit into
mainfrom
feat/admin-cli

Conversation

@pbudzik

@pbudzik pbudzik commented May 16, 2026

Copy link
Copy Markdown
Owner

Summary

The usagedb binary doubles as an admin tool. The HTTP server behavior moves under usagedb serve (still the default when no subcommand is given, so prior invocations work unchanged).

Subcommands

usagedb check [--deep]
  Manifest summary (generation, segment counts, watermark).
  --deep additionally opens every segment file and verifies
  checksum + structural validity. Fails non-zero on any corruption.

usagedb rebuild-rollups --from <RFC3339> --to <RFC3339>
  Drops rollup segments overlapping the range + rewinds the
  watermark to `from`. Next server tick refills from raw segments.

usagedb inspect-segment <segment_id>
  SegmentMeta fields (bucket, time range, ID sets, checksum,
  input_segment_ids for rollups) + first 5 rows as a sample.

usagedb verify-period --account <id> --from <RFC3339> --to <RFC3339>
  Same logic as the /verify HTTP endpoint. Prints raw_total,
  rollup_total, drift, and `period_sealed`.

usagedb export-parquet <output.parquet>
  Calls export_raw_segments() — every raw segment to one Parquet
  file, zstd-compressed.

All commands accept --db-root <path> (global, default ./data) and operate on the on-disk state without needing a running server. Assumes the server is not running concurrently — file locking to prevent that is on the backlog.

Implementation

  • src/admin.rscmd_* async functions take AppState + args and return Result<String>. Each command formats its own output; main.rs just prints.
  • open_state_for_admin() — loads the manifest directly via Manifest::load() (no full Recovery, no segment-scan dedupe rebuild, no WAL replay). One-shot commands don't need the full startup cost.
  • main.rs uses clap derive macros. The old server logic moves into run_server() — same code path, just behind a Command::Serve match arm.

Tests

10 new tests in tests/admin.rs, calling cmd_* directly (no subprocess):

  • check_reports_manifest_summary
  • check_deep_passes_for_valid_segments
  • check_deep_fails_for_corrupt_segment — flips a byte mid-segment-file
  • check_on_fresh_db_errors_with_clear_message
  • inspect_segment_prints_meta_and_sample_rows
  • inspect_segment_errors_for_unknown_id
  • rebuild_rollups_drops_and_rewinds — tick → rebuild → verify state
  • rebuild_rollups_rejects_invalid_dates
  • verify_period_reports_match_for_sealed_rollup
  • export_parquet_writes_file

Smoke test

$ cargo run --release -- --help
Embedded append-only usage database for AI billing

Usage: usagedb [OPTIONS] [COMMAND]

Commands:
  serve            Run the HTTP server (default)
  check            Print manifest summary
  rebuild-rollups  Drop rollup segments overlapping [from, to)
  inspect-segment  Read a specific segment
  verify-period    Compute raw vs rollup totals + drift
  export-parquet   Export every raw segment to a Parquet file
  ...

Test plan

  • cargo build --all-targets clean with -D warnings
  • cargo test --all-targets — 106 tests pass (was 96; +10)
  • CLI smoke test (--help, all subcommands resolve)
  • CI green

Dependency

clap = "4" with derive feature, ~1 MB compiled.

🤖 Generated with Claude Code

…/ export-parquet

The usagedb binary doubles as an admin tool. The existing HTTP server
behavior moves under `usagedb serve` (still the default when no
subcommand is given, so prior invocations work unchanged).

Subcommands:
  check [--deep]
    Print manifest summary (generation, segment counts, watermark).
    --deep additionally opens every segment file and verifies checksum
    + structural validity. Fails non-zero if any segment is corrupt.

  rebuild-rollups --from <RFC3339> --to <RFC3339>
    Drops rollup segments overlapping the range + rewinds the
    watermark to `from`. Next server tick refills from raw segments.

  inspect-segment <segment_id>
    Prints SegmentMeta fields (bucket, time range, account/product/
    meter/model sets, checksum, input_segment_ids for rollups) + first
    5 rows as a sample.

  verify-period --account <id> --from <RFC3339> --to <RFC3339>
    Same logic as the /verify HTTP endpoint. Prints raw_total,
    rollup_total, drift, and whether the period is sealed.

  export-parquet <output.parquet>
    Calls export_raw_segments() to write every raw segment to one
    Parquet file with zstd compression.

All commands accept --db-root <path> (global, default ./data). They
operate on the on-disk state without requiring a running server.
Assumes the server is NOT running concurrently — file locking is on
the backlog.

Implementation:
  - New module src/admin.rs with cmd_* async functions taking
    AppState + args and returning Result<String>. Each command
    formats its own output; the main binary just prints.
  - open_state_for_admin() loads the manifest directly via
    Manifest::load() — cheaper than full Recovery (no segment-scan
    dedupe rebuild, no WAL replay) for one-shot commands.
  - main.rs uses clap (derive macros) for arg parsing. The old
    server logic moves into run_server() — same code path, just
    behind a Command::Serve match arm.

Tests (tests/admin.rs, 10 tests):
  - check_reports_manifest_summary
  - check_deep_passes_for_valid_segments
  - check_deep_fails_for_corrupt_segment (flips a byte mid-file)
  - check_on_fresh_db_errors_with_clear_message
  - inspect_segment_prints_meta_and_sample_rows
  - inspect_segment_errors_for_unknown_id
  - rebuild_rollups_drops_and_rewinds (tick → rebuild → verify state)
  - rebuild_rollups_rejects_invalid_dates
  - verify_period_reports_match_for_sealed_rollup
  - export_parquet_writes_file

clap added as a regular dep (~1 MB).

Total tests: 106 (was 96; +10). Clean under RUSTFLAGS=-D warnings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pbudzik pbudzik merged commit e0b87ee into main May 16, 2026
1 check passed
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.

1 participant