Skip to content

perf(ledger): scope pruning to the validated checkpoint - #708

Closed
ScriptedAlchemy wants to merge 6 commits into
codex/tracedecay-total-redesign-plan-reopenedfrom
claude/perf-ledger-prune
Closed

ScriptedAlchemy wants to merge 6 commits into
codex/tracedecay-total-redesign-plan-reopenedfrom
claude/perf-ledger-prune

Conversation

@ScriptedAlchemy

Copy link
Copy Markdown
Owner

Reopened against codex/tracedecay-total-redesign-plan-reopened (prior PRs auto-closed when #421 merged and master was reverted).

Base already carries the 256-row bound; this keeps that verbatim and adds only what remained: the delete is scoped to the validated (incarnation, epoch) pair instead of joining every checkpoint row and trusting each one's raw scalar authority_epoch. checkpoint::next validates only the incarnation it advances, so a corrupt neighbour reading 999 while its watermark encodes 7 could retire that incarnation's live receipts — and a lost receipt silently re-admits the duplicate write it exists to stop.

328 tests pass, 0 failures. Superseded-incarnation pruning stays out: StoreIncarnationV1 is non-monotonic.

ScriptedAlchemy and others added 6 commits August 24, 2026 00:41
The writer idempotency ledger was never pruned. Measured across seven
stores it holds 1,149 MB of 3,112 MB total (36%), and 87% of three
freshly indexed stores.

Pruning by superseded incarnation would have reclaimed far more but is
unsound: StoreIncarnationV1 is not monotonic — every non-daemon process
derives it from random process-run bytes while the daemon uses a small
counter, so a CLI attach can outrank a later daemon one and pruning by
it would re-admit duplicate writes. This prunes only rows whose
authority epoch trails the checkpoint epoch for that same incarnation,
which never crosses a shard or incarnation boundary.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
perf(observation) landed an import of
tracedecay_runtime_core::background_cpu, but neither the module file nor
its declaration was added, so the pushed integration branch does not
compile: every branch cut from it fails on an unresolved import.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Retention ran as one shard-wide DELETE inside the user mutation's
savepoint and the process's sole SQLite writer transaction. At the
measured scale - 257,131 rows across seven stores - that monopolises
admission, and an interruption or SQLITE_FULL rolls the checkpoint
advance back with it, so every retry re-attempts the same delete and the
new epoch may never commit.

Each commit now removes at most one bounded batch, so the epoch advance
commits regardless of how much backlog remains, and later commits at the
standing epoch keep draining it until it converges.

The delete also read each incarnation's raw scalar authority_epoch,
though checkpoint::next decodes and validates only the incarnation being
advanced. A neighbouring row whose scalar is corrupt - 999 while its
watermark and receipt still encode 7 - would silently retire that
incarnation's live receipts, re-admitting the duplicate writes they
exist to stop. The candidate set is now scoped to the single incarnation
whose checkpoint this commit validated, at that checkpoint's epoch; a
neighbour's records are retired by its own commits.

Pruning is still keyed on authority supersession, never on age and never
on superseded incarnation: StoreIncarnationV1 is not monotonic, so a
non-daemon attach can outrank a later daemon one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The base landed its own copy of the retention rule, so both sides now
prune superseded idempotency records. The two sides agree on the bounding
scheme - same MAX_PRUNED_ROWS_PER_COMMIT name, same value of 256, same
unconditional one-bounded-pass-per-commit placement after checkpoint
persist - and that agreement is kept verbatim rather than replaced with a
second competing scheme.

What this branch still adds on top of that structure is the candidate
scope. The base selects candidates by joining every checkpoint row and
comparing each incarnation's raw scalar authority_epoch, but
checkpoint::next decodes and validates only the incarnation being
advanced. A neighbouring row whose scalar reads 999 while its watermark
and receipt still encode 7 would therefore retire that incarnation's live
receipts, and a lost receipt silently re-admits the duplicate write it
exists to stop. The delete is now scoped to the single incarnation whose
checkpoint this commit decoded, validated, and persisted, at that
checkpoint's validated epoch; a neighbour's records are retired by its own
commits under its own validated checkpoint.

Resolution per file:

- prune.rs: kept the base's bound and its per-commit placement, replaced
  the checkpoint join with the validated (incarnation, epoch) pair the
  caller already holds, and exposed MAX_PRUNED_ROWS_PER_COMMIT to the
  tests so they assert against the constant instead of a copy of 256.
  Ordering drops the two now-constant leading primary-key columns, which
  leaves the same key order over the same candidate set.
- commit.rs: the call passes the persisted checkpoint instead of the bare
  shard id. Pruning still runs on every commit, so a backlog left by an
  earlier bounded pass still converges.
- tests.rs: the base's two bounding tests are superseded by the same two
  tests deriving their expectations from the constant, so its copies are
  dropped rather than duplicated under both names. Both properties it
  covered are retained - one commit removes at most one bounded batch, and
  later commits at the standing epoch keep draining - alongside the new
  case that an unvalidated neighbouring checkpoint cannot retire its own
  receipts.

Verified on the merge result:
  cargo check -p tracedecay-rusqlite-runtime --all-targets --locked  ok
  cargo test  -p tracedecay-rusqlite-runtime --locked                0 failed

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Aug 24, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: da5d845

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

Closing as an exact no-op against PR 707. GitHub reports zero changed files, and the validated-incarnation pruning is already carried by 84df647 in the restored delivery tree.

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