Skip to content

feat: bound artifact version history and audit log growth - #289

Merged
getappz merged 8 commits into
masterfrom
feat/storage-guards
Jul 21, 2026
Merged

feat: bound artifact version history and audit log growth#289
getappz merged 8 commits into
masterfrom
feat/storage-guards

Conversation

@getappz

@getappz getappz commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Adds two storage guards, modeled directly on lean-ctx's own core/archive.rs::cleanup_with — whose comment cites its own past incident: "without an enforcer the archive grew unbounded on disk and starved the host of RAM via the page cache (#417)". agentflare had the same two open-ended growth paths with nothing bounding them.

1. Artifact version-snapshot history (agentflare-artifacts)

ArtifactStore::publish wrote a full, uncompressed content snapshot per version into versions/ forever — no cap. prune_old_versions now keeps the last 50 version bodies plus v1 (the origin anchor), called on every publish. Deliberately conservative vs. lean-ctx's cache-eviction model: this is user-published data, not disposable cache, so:

  • The cap only bounds runaway loops (e.g. a stuck /loop session republishing one artifact hundreds of times), never normal editing.
  • Only version-snapshot bodies are pruned. versions() (the history/version list) is untouched, so what happened is still visible. diff/get_version on a pruned version returns a plain NotFound rather than silently returning wrong data.

2. Audit log rotation (flare-git-core)

audit::log_event appended one JSONL line per git-shim invocation with zero rotation — confirmed live on this machine: git.jsonl is already at 7,538 lines. maybe_rotate now trims a log back to its last 5,000 lines once it exceeds 5MB. Checked via a single metadata() stat per append, so the common under-budget case costs one cheap syscall, not a read of the whole file — no hot-path regression for the git shim.

Verification

  • cargo test -p agentflare-artifacts -p flare-git-core: 24 + 71 passed, 0 failed (both crates previously had 0/71 respectively for this new coverage — agentflare-artifacts had no test module at all before this PR)
  • cargo fmt --check clean
  • cargo clippy --workspace --all-features -- -D warnings: only the two pre-existing Windows-local dead-code errors in daemon_autostart.rs (unix-only helpers, present on master, untouched here)
  • cargo test --workspace: 692 passed / 0 failed, all suites green

Summary by CodeRabbit

  • New Features
    • agentflare init now installs command shims and helps add them to your PATH.
    • Stored artifacts and blobs are gzip-compressed automatically while remaining compatible with existing uncompressed data.
    • Version snapshots are automatically pruned while preserving the initial version.
    • Audit logs now trim older entries when they exceed the configured size limit.
  • Bug Fixes
    • Improved handling of stale or missing shims and legacy stored data.

getappz added 2 commits July 21, 2026 16:02
…ish loops

Agentflare-Agent: claude-code_2-1-216_harness
Agentflare-Branch: feat/storage-guards
Agentflare-Agent: claude-code_2-1-216_harness
Agentflare-Branch: feat/storage-guards
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 49 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 67f99152-9e22-408d-bc12-3bd2a752f2b9

📥 Commits

Reviewing files that changed from the base of the PR and between 5721fce and 7fd72d5.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • crates/flare-git-core/Cargo.toml
  • crates/flare-git-core/src/audit.rs
📝 Walkthrough

Walkthrough

The PR adds gzip compression and retention controls for artifact snapshots and disk blobs, trims oversized audit logs, and introduces consent-gated installation of generic and Git PATH shims during initialization.

Changes

Storage retention and compression

Layer / File(s) Summary
Artifact snapshot compression and pruning
crates/agentflare-artifacts/Cargo.toml, crates/agentflare-artifacts/src/store.rs
Artifact snapshots are gzip-compressed, old bodies are pruned while history remains complete, and gzip or legacy plaintext snapshots can be read.
Disk blob compression compatibility
Cargo.toml, crates/agentflare-store/Cargo.toml, crates/agentflare-store/src/blobs.rs
Disk blobs are gzip-compressed on write and conditionally decompressed on read, with tests for compressed and legacy formats.

Audit log retention

Layer / File(s) Summary
Audit log trimming and validation
crates/flare-git-core/src/audit.rs
Audit event logging trims oversized JSONL files to the configured recent-line limit and tests budget, trimming, and missing-file behavior.

PATH shim installation

Layer / File(s) Summary
Shim module and component wiring
src/main.rs, src/components.rs
A new shim installer module is registered and exposed as a consent-gated initialization component.
Shim discovery and filesystem primitives
src/shim_install.rs
Bundled binaries are discovered, generic tool destinations are checked, and hardlink-or-copy installation behavior is implemented and tested.
Shim installation and Git integration
src/shim_install.rs, src/cli/git.rs, src/cli/mod.rs
Initialization installs generic and Git shims, updates PATH, and reuses crate-visible Git shim helpers.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant InitComponent
  participant ShimInstaller
  participant GitShimInstaller
  participant ShimDirectory
  InitComponent->>ShimInstaller: check and install shims
  ShimInstaller->>ShimDirectory: hardlink or copy generic shims
  ShimInstaller->>GitShimInstaller: install bundled Git shim
  GitShimInstaller->>ShimDirectory: copy binary and set permissions
  ShimInstaller->>ShimDirectory: ensure directory is on PATH
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and matches the main changes: artifact history bounds and audit log growth control.
Description check ✅ Passed The description covers the summary, verification, and backwards-compatibility notes, though it doesn't use the exact template headings.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/storage-guards

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

getappz added 3 commits July 21, 2026 16:03
…ency

Agentflare-Agent: claude-code_2-1-216_harness
Agentflare-Branch: feat/storage-guards
Agentflare-Agent: claude-code_2-1-216_harness
Agentflare-Branch: feat/storage-guards
Agentflare-Agent: claude-code_2-1-216_harness
Agentflare-Branch: feat/storage-guards
@getappz

getappz commented Jul 21, 2026

Copy link
Copy Markdown
Owner Author

Added two more pieces to this PR:

Gzip compression (artifact version snapshots + blob storage): both stores wrote raw bytes to disk with no compression. Compressed with flate2, using gzip's self-describing magic header (1f 8b) to detect legacy uncompressed files on read — no version marker or migration step needed, old data reads through unchanged. Tests confirm on-disk size shrinks and legacy plaintext still round-trips.

PATH shim install wired into init: previously agentflare git install-shim required an explicit --binary path and was never called automatically; the 53 generic-tool shims (aws, cargo, npm, python, ...) had no installer anywhere in the codebase at all — the ~50 copies on a dev machine were manually placed, not product-installed. New shim_install module auto-discovers bundled shim binaries next to the running agentflare executable and hardlinks (falling back to copy) them into ~/.agentflare/shims/, wired as a new init component (needs_consent, matching mise/lean-ctx's risk tier).

Note: this only takes effect end-to-end for release/curl-installed builds once two infra pieces are also updated — release.yml's packaging step (only bundles the agentflare binary today, not agentflare-shim/git) and install.sh (only extracts/installs agentflare from the downloaded archive). Flagging separately rather than editing CI/installer scripts inline here — happy to do those as a follow-up PR if wanted.

Agentflare-Agent: claude-code_2-1-216_harness
Agentflare-Branch: feat/storage-guards

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
crates/agentflare-artifacts/src/store.rs (1)

313-322: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Pruning is O(n) per publish → O(n²) over a runaway loop.

prune_old_versions runs on every publish and re-issues remove_file for the whole 2..=cutoff range each time, so the syscall count grows with the version number. A stuck /loop republishing to version N (the very scenario MAX_KEPT_VERSIONS is meant to bound) triggers ~O(N²) filesystem operations overall.

Since the window slides by exactly one per changed publish, only the single version that just fell out of the window needs removing:

♻️ Prune only the newly-evicted version
 fn prune_old_versions(dir: &Path, latest_version: u32, keep: u32) {
     if latest_version <= keep {
         return;
     }
-    let cutoff = latest_version - keep;
-    let versions_dir = dir.join(VERSIONS_DIR);
-    for v in 2..=cutoff {
-        let _ = fs::remove_file(versions_dir.join(v.to_string()));
-    }
+    // The window slides by one per changed publish, so only the version that
+    // just fell out of it needs deleting; earlier ones were pruned before.
+    // v1 is the origin anchor and is never in range (cutoff >= 2 guards it).
+    let cutoff = latest_version - keep;
+    if cutoff >= 2 {
+        let _ = fs::remove_file(dir.join(VERSIONS_DIR).join(cutoff.to_string()));
+    }
 }

Note this trades away catch-up pruning of directories that were already over the cap before this code shipped; if that matters, keep the loop but bound its lower end to a stored high-water mark rather than always restarting at 2.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/agentflare-artifacts/src/store.rs` around lines 313 - 322, Update
prune_old_versions to remove only the single version newly evicted when
latest_version exceeds keep, deriving that version from latest_version and keep
instead of iterating from 2 through the cutoff. Preserve the existing early
return and ignored remove_file errors; do not retain the full-range loop unless
it is bounded by a persisted high-water mark.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/flare-git-core/src/audit.rs`:
- Around line 77-84: Update the audit rotation logic after the meta.len() check
so rotation guarantees the resulting active file is below max_bytes. Prefer
rename-based rotation that replaces the oversized file with a fresh empty file;
if retaining in-place trimming, trim content by byte size rather than only
keep_lines. Ensure subsequent appends can use the size fast path without
repeatedly rereading and rewriting an oversized file.
- Around line 70-86: Update maybe_rotate to perform rotation atomically and
safely under concurrent processes: acquire an exclusive lock covering the
metadata check and entire read-modify-write cycle, write trimmed content to a
temporary file, then rename it over path instead of using fs::write. Preserve
the existing no-op behavior when the file is missing, within budget, or has no
more than keep_lines lines.

---

Nitpick comments:
In `@crates/agentflare-artifacts/src/store.rs`:
- Around line 313-322: Update prune_old_versions to remove only the single
version newly evicted when latest_version exceeds keep, deriving that version
from latest_version and keep instead of iterating from 2 through the cutoff.
Preserve the existing early return and ignored remove_file errors; do not retain
the full-range loop unless it is bounded by a persisted high-water mark.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0d14c9f9-dd5b-435d-83c6-cfefb881130d

📥 Commits

Reviewing files that changed from the base of the PR and between f398c43 and 5721fce.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • Cargo.toml
  • crates/agentflare-artifacts/Cargo.toml
  • crates/agentflare-artifacts/src/store.rs
  • crates/agentflare-store/Cargo.toml
  • crates/agentflare-store/src/blobs.rs
  • crates/flare-git-core/src/audit.rs
  • src/cli/git.rs
  • src/cli/mod.rs
  • src/components.rs
  • src/main.rs
  • src/shim_install.rs

Comment thread crates/flare-git-core/src/audit.rs Outdated
Comment thread crates/flare-git-core/src/audit.rs
maybe_rotate did an unsynchronized read-modify-write (metadata check, read,
truncating write) on every append once over budget. A concurrent git process
appending between another's read and write would have its event silently
dropped, and a rewrite interrupted mid-write (crash, disk full) could corrupt
the log. Fixed by holding an exclusive lock (fs2, already a workspace
dependency, mirroring the pattern in src/daemon.rs) across the whole
log_event call, and writing rotated content to a temp file then renaming it
over the original so a crash can't leave a torn file.

Agentflare-Agent: claude-code_2-1-216_agent
Agentflare-Branch: feat/storage-guards
@getappz
getappz enabled auto-merge (squash) July 21, 2026 12:28
@getappz
getappz merged commit 68f3b8e into master Jul 21, 2026
16 of 17 checks passed
@getappz
getappz deleted the feat/storage-guards branch July 21, 2026 12:37
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