feat(sl-daemon): bundle compression and archival - #43
Conversation
… subcommands) - Add crates/sl-daemon/src/archive.rs with archive_bundles(), restore_bundle(), find_archive_path() using flate2 gzip - Add 'sl archive --before <YYYY-MM-DD> [--dry-run]' subcommand - Add 'sl restore <bundle-id>' subcommand - Add flate2 + chrono deps to Cargo.toml - 8 unit tests: archive moves files, dry-run no-op, restore roundtrip, bytes_saved, date filtering, already-archived skip, find path, missing error
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
|
Warning Review limit reached
Next review available in: 44 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Free Run ID: 📒 Files selected for processing (3)
Note 🎁 Summarized by CodeRabbit FreeThe PR author is not assigned a seat. To perform a comprehensive line-by-line review, please assign a seat to the pull request author through the subscription management page by visiting https://app.coderabbit.ai/login. Comment |
|
|
||
| // Derive output filename: strip .gz from the archive filename. | ||
| let stem = archive_path.file_name().and_then(|n| n.to_str()).unwrap_or("bundle.json.gz"); | ||
| let out_name = stem.strip_suffix(".gz").unwrap_or(stem); |
There was a problem hiding this comment.
WARNING: Restored file loses the .okf.json extension, breaking the archive/restore round-trip
restore_bundle strips only .gz from bundle-003.json.gz, producing bundle-003.json. The rest of the system only recognizes bundles via is_okf_json (matches *.okf.json), so the restored file is never rediscovered, re-archived, or served by sl search/etl. The doc comment on line 140 even promises a .okf.json output, which the code does not deliver. The round-trip test passes only because it asserts on JSON content, not the filename.
| let out_name = stem.strip_suffix(".gz").unwrap_or(stem); | |
| let out_name = format!("{}.okf.json", stem.strip_suffix(".json.gz").unwrap_or(stem)); |
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
| println!(" [dry-run] would archive: {} -> {}", path.display(), dest_file.display()); | ||
| stats.archived_count += 1; | ||
| // Estimate savings optimistically at 60%. | ||
| stats.bytes_saved += original_size * 60 / 100; |
There was a problem hiding this comment.
WARNING: Dry-run reports fabricated 60% savings as if real
In dry-run, bytes_saved is set to original_size * 60 / 100 with a hard-coded 60% estimate. run_archive then prints Archived N bundle(s), saved {mb} MB, so operators are shown a made-up number presented as the actual space that would be reclaimed. This is misleading for capacity planning. Consider printing 0 (unknown) in dry-run, or compressing a sample to estimate, and label the summary clearly as an estimate.
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
|
|
||
| fs::create_dir_all(output_dir) | ||
| .map_err(|e| ArchiveError::Io { path: output_dir.to_path_buf(), source: e })?; | ||
| fs::write(&out_path, &decompressed) |
There was a problem hiding this comment.
WARNING: restore_bundle overwrites an existing destination file without checking
fs::write(&out_path, ...) silently clobbers any file already present at the destination. When restoring into data_dir (the default for sl restore), a live, non-archived bundle with the same id will be overwritten and lost. Prefer failing or warning when out_path already exists, or require an explicit --force flag.
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
|
|
||
| fn gzip_bytes(data: &[u8]) -> Vec<u8> { | ||
| let mut encoder = GzEncoder::new(Vec::new(), Compression::best()); | ||
| encoder.write_all(data).expect("gzip write failed"); |
There was a problem hiding this comment.
SUGGESTION: Use proper error propagation instead of .expect()
gzip_bytes calls .expect("gzip write failed") / .expect("gzip finish failed"), which can panic, while gunzip_bytes correctly maps I/O errors into ArchiveError. For consistency and to avoid an unexpected panic in a CLI command, propagate the error (e.g. write_all(...).map_err(|e| ArchiveError::Io { ... })? and have gzip_bytes return Result<Vec<u8>, ArchiveError>).
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
|
|
||
| /// Summary statistics returned by [`archive_bundles`]. | ||
| #[derive(Debug, Default)] | ||
| pub struct ArchiveStats { |
There was a problem hiding this comment.
SUGGESTION: Derive Clone on ArchiveStats
The repository guidelines ask public types to implement Debug and Clone where practical. ArchiveStats is a plain data struct that would benefit from Clone (e.g. for callers that want to retain stats after printing), but currently only derives Debug, Default.
Reply with @kilocode-bot fix it to have Kilo Code address this issue.
Code Review SummaryStatus: 5 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (3 files)
Fix these issues in Kilo Cloud Reviewed by hy3-20260706:free · Input: 73.9K · Output: 12.9K · Cached: 137.5K |
Summary
crates/sl-daemon/src/archive.rswitharchive_bundles(),restore_bundle(), andfind_archive_path()backed byflate2gzipsl archive --before <YYYY-MM-DD> [--dry-run]subcommand — gzips matching bundles into<data_dir>/archive/<year>/<month>/, prints summary of count + MB savedsl restore <bundle-id>subcommand — finds the.json.gzin the archive tree and decompresses back todata_dirflate2 = "1"andchrono = "0.4"tocrates/sl-daemon/Cargo.tomlTest plan
CARGO_NET_OFFLINE=true cargo build -p sl-daemon— clean buildcargo test -p sl-daemon— 73 passed, 0 failedcargo clippy -p sl-daemon -- -D warnings— no warningscargo fmt -p sl-daemon— applied🤖 Generated with Claude Code