Skip to content

feat(sl-daemon): bundle compression and archival - #43

Merged
KooshaPari merged 1 commit into
mainfrom
feat/bundle-archival
Jul 4, 2026
Merged

feat(sl-daemon): bundle compression and archival#43
KooshaPari merged 1 commit into
mainfrom
feat/bundle-archival

Conversation

@KooshaPari

Copy link
Copy Markdown
Owner

Summary

  • Adds crates/sl-daemon/src/archive.rs with archive_bundles(), restore_bundle(), and find_archive_path() backed by flate2 gzip
  • Adds sl archive --before <YYYY-MM-DD> [--dry-run] subcommand — gzips matching bundles into <data_dir>/archive/<year>/<month>/, prints summary of count + MB saved
  • Adds sl restore <bundle-id> subcommand — finds the .json.gz in the archive tree and decompresses back to data_dir
  • Adds flate2 = "1" and chrono = "0.4" to crates/sl-daemon/Cargo.toml
  • 8 unit tests covering: archive moves files, dry-run no-op, restore roundtrip, stats accuracy, date filtering, already-archived skip, find_archive_path success, find_archive_path missing error

Test plan

  • CARGO_NET_OFFLINE=true cargo build -p sl-daemon — clean build
  • cargo test -p sl-daemon — 73 passed, 0 failed
  • cargo clippy -p sl-daemon -- -D warnings — no warnings
  • cargo fmt -p sl-daemon — applied

🤖 Generated with Claude Code

… 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
@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@KooshaPari, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 44 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Free

Run ID: 2d8535cb-cab8-4645-b3e2-b7134ce3a4c9

📥 Commits

Reviewing files that changed from the base of the PR and between 6ca4961 and 01dbec9.

📒 Files selected for processing (3)
  • crates/sl-daemon/Cargo.toml
  • crates/sl-daemon/src/archive.rs
  • crates/sl-daemon/src/main.rs

Note

🎁 Summarized by CodeRabbit Free

The 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 @coderabbitai help to get the list of available commands.

@KooshaPari
KooshaPari merged commit 6a3440f into main Jul 4, 2026
8 of 11 checks passed
@KooshaPari
KooshaPari deleted the feat/bundle-archival branch July 4, 2026 04:44

// 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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Suggested change
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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

@kilo-code-bot

kilo-code-bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 5 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 3
SUGGESTION 2
Issue Details (click to expand)

WARNING

File Line Issue
crates/sl-daemon/src/archive.rs 150 Restored file loses .okf.json extension (strips only .gz), so the file is never rediscovered/re-archived/served — breaks the archive→restore round-trip promised by the PR
crates/sl-daemon/src/archive.rs 111 Dry-run fabricates 60% savings into bytes_saved, then prints it as real saved X MB — misleading for capacity planning
crates/sl-daemon/src/archive.rs 155 restore_bundle uses fs::write without checking existence, silently clobbering a live same-id bundle when restoring into data_dir (data loss)

SUGGESTION

File Line Issue
crates/sl-daemon/src/archive.rs 226 gzip_bytes uses .expect() while gunzip_bytes propagates errors; inconsistent and can panic in a CLI command
crates/sl-daemon/src/archive.rs 22 ArchiveStats should derive Clone per repo guidelines for public data types
Files Reviewed (3 files)
  • crates/sl-daemon/Cargo.toml - 0 issues
  • crates/sl-daemon/src/archive.rs - 5 issues
  • crates/sl-daemon/src/main.rs - 0 issues (issues manifest via archive.rs behavior)

Fix these issues in Kilo Cloud


Reviewed by hy3-20260706:free · Input: 73.9K · Output: 12.9K · Cached: 137.5K

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