Skip to content

fix(stats): persist tool events immediately to close ROI/Home divergence - #1066

Closed
cedric013 wants to merge 4 commits into
yvgude:mainfrom
cedric013:fix/1064-stats-durability
Closed

fix(stats): persist tool events immediately to close ROI/Home divergence#1066
cedric013 wants to merge 4 commits into
yvgude:mainfrom
cedric013:fix/1064-stats-durability

Conversation

@cedric013

Copy link
Copy Markdown
Contributor

What

Persist stats per event and reword the dashboard so Home (estimated) and ROI (verified) are presented as independent accounting scopes.

Closes #1064

Why

core/savings_ledger appends every measured event immediately under a cross-process lock, while core/stats::record buffered events in a process-local STATS_BUFFER and only flushed after FLUSH_INTERVAL_SECS (30s) or on graceful shutdown(). MCP stdio servers are frequently terminated without a graceful shutdown, so ledger events survived while buffered stats were lost — Home then under-reported real savings and could sit far below the ROI ledger total. The existing UI copy ("of which … are signed", "auditable floor", "Why smaller here") wrongly implied verified <= estimated, which is false for two independent accumulators.

Changes

  • core/stats::record and core/stats::adjust_savings now persist each event immediately (merge_and_save), matching the ledger's per-event durability. The 30s cadence no longer gates whether a completed event reaches disk.
  • Dashboard copy (cockpit-overview.js bridge line, cockpit-roi.js methodology + hero banner) now frames Home and ROI as independent surfaces where either total may legitimately be larger, instead of a subset/floor relationship.

Testing

  • cargo test --lib core::stats::tests — 10 passed
  • cargo check --lib, cargo clippy --lib -- -D warnings — clean
  • node --check on both edited components — clean

Notes

Behaviour-preserving for the split-data-dir aggregation (#830), corruption quarantine (#706) and daily-history bounds — only the flush timing changed from deferred to immediate.

stats::record buffered events in a process-local STATS_BUFFER flushed
only every 30s or on graceful shutdown, so abrupt MCP stdio restarts
dropped unflushed stats while the append-only savings ledger kept every
event. This let ROI (verified ledger) exceed Home (estimated stats) by a
large factor.

Persist each stats::record and stats::adjust_savings immediately,
matching the ledger's per-event durability, and reword the dashboard so
Home and ROI read as independent accounting scopes where either total
may legitimately be larger.

Closes yvgude#1064
Only advance the in-memory baseline after acquiring the cross-process lock
and successfully replacing stats.json. This prevents an immediate flush
from silently marking an event persisted when lock contention or I/O
failure actually dropped the write.

Add a regression test covering lock contention and retry.
@cedric013
cedric013 force-pushed the fix/1064-stats-durability branch from 3e231d7 to aba5be9 Compare July 20, 2026 12:07
@cedric013

Copy link
Copy Markdown
Contributor Author

Fixed the PR-specific durability bug and rebased onto current main. merge_and_save previously advanced the in-memory baseline even when the cross-process lock could not be acquired or stats.json could not be replaced; the event was then considered persisted and could be lost permanently. It now returns success only after a durable write, preserving the delta for retry on failure. Added a lock-contention regression test. Local result: 18/18 core::stats tests pass. CI rerun is queued.

@cedric013

Copy link
Copy Markdown
Contributor Author

Follow-up: inspected the actual failed CI logs. Root causes were an unformatted pair of upstream OCLA files and an unused CompressionProvider import promoted to an error by RUSTFLAGS=-Dwarnings; most other jobs failed/cancelled transitively. Fixed both in 93bbf225. New Format check is green; remaining jobs are running.

@yvgude yvgude left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Code Review: fix(stats): persist tool events immediately

Verdict: Changes Requested

The stats durability fix itself is correct, but this PR mixes 3 unrelated concerns:

1. Stats durability (the actual fix) — io.rs, mod.rs — Good:

  • write_to_disk() now returns bool for error propagation
  • merge_file_stats() chains write success via Option
  • maybe_flush() runs on every tool event, not just on timer
  • Tests cover baseline preservation

2. CI import fixes — compression_provider.rs, tool_lifecycle.rs — Already on main:
These are identical to the now-closed PR #1098. R5 integration already applied these fixes (commit 01b630409). This will create merge conflicts.

3. Dashboard text changes — cockpit-overview.js, cockpit-roi.js — Unrelated:
Copy changes ("of which" → removed, "since" → "recording since", ROI divergence note) are not about stats durability.

4. Content port fix — content_port.rs — Unrelated:
Removing unused _jailed binding is a separate clippy fix.

What to do

Please rebase on current main (the import fixes will auto-resolve as they are identical) and remove the unrelated dashboard text changes from this PR. The stats durability changes in io.rs and mod.rs are clean and ready to merge once isolated.

Alternatively: squash the unrelated changes into a separate commit so we can merge only the stats fix.

@yvgude

yvgude commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Merged to main via CLI rebase+merge. Import/CI commits were dropped (already on main). Only the stats durability fix + dashboard copy improvements remain.

@yvgude yvgude closed this Jul 21, 2026
yvgude added a commit that referenced this pull request Jul 21, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 21, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: Home estimated savings and ROI verified ledger diverge after abrupt MCP restarts (buffered stats lost)

2 participants