Skip to content

fix(global-db): restore measured WAL reclaim to registered checkpoint (Plan 38 §6) - #534

Merged
ScriptedAlchemy merged 3 commits into
codex/tracedecay-total-redesign-planfrom
cursor/global-db-wal-checkpoint-ccf4
Aug 19, 2026
Merged

fix(global-db): restore measured WAL reclaim to registered checkpoint (Plan 38 §6)#534
ScriptedAlchemy merged 3 commits into
codex/tracedecay-total-redesign-planfrom
cursor/global-db-wal-checkpoint-ccf4

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Summary

Plan 38 §6 WAL checkpoint/compaction for crates/tracedecay-global-db, targeting the live symptom of a 415 MB WAL that never checkpoints down: RegisteredGlobalDb::checkpoint_result() now runs a measured checkpoint/compaction pass with a typed receipt, surfaces a PASSIVE-busy WAL as a typed failure, and truncates the drained file to zero bytes under exclusive maintenance authority. Rebased onto the current #421 tip d6a273c9 (contains #531; not stacked on #537).

Root cause (verified on this branch)

  • Until commit 194781603 (refactor(runtime): enforce owner-bound database lifetimes, Aug 16), checkpoint_result() issued a real PRAGMA wal_checkpoint(TRUNCATE) through the serialized writer (WalCheckpointExecutor) and failed typed on busy/incomplete frames.
  • That commit rewired it to Database::checkpoint() — the retained runtime's PASSIVE-only lane, which returns a vacuous Ok below its 32 MiB soft budget even while a live reader pins the WAL, and never truncates at any size.
  • Since then, no production path anywhere truncated a registered WAL file (Database::truncate_wal_for_offline_maintenance had zero callers; the rusqlite-runtime writer's trigger_maintenance(MaintenanceCheckpointMode::Truncate) permit lane is implemented but unmounted). PASSIVE backfill bounds WAL content once readers release, but the -wal file keeps its high-water size forever — the 415 MB debris. The crate's own busy-contract test was red on the base tip (called Result::unwrap_err() on an Ok value, checkpoint_tests.rs:50).

Change (this crate only)

  • checkpoint_result() returns RegisteredWalCheckpointReceiptV1 { wal_bytes_before, wal_bytes_after, reclaim } — file-level -wal measurements per Plan 38's size-evidence rule; a missing sidecar is a typed zero.
  • The drain goes through the runtime's authorized checkpoint lane, so a pinned WAL under size pressure propagates as a typed error — a stuck WAL is a visible failure, never a silent no-op.
  • Reclaim decision is a pure function (wal_reclaim_plan) of measured size × write-authority role: below the 32 MiB trigger (aligned with the runtime's soft checkpoint budget; a fresh schema install alone leaves several MiB) the warm WAL is left alone; at/above it, the file is truncated via the runtime's exclusive-maintenance facade when this client holds the Maintenance role (real journeys: embedded/CLI lifecycle under enter_owned_maintenance_database_scope, profile-storage commands, core doctor); daemon-role clients get the typed RequiresExclusiveMaintenance receipt.
  • Best-effort checkpoint() keeps its shutdown-safe signature for existing callers (MCP shutdown, host admission).
  • One dev-dependency pin inside this crate: tracedecay-runtime-core dev-features gain test-transport, exposing the maintenance-authority fixture publisher the truncation proof needs.

Test evidence (isolated temp stores only — no live profile, no daemon attach)

All in crates/tracedecay-global-db (cargo nextest run -p tracedecay-global-db -E 'test(checkpoint_tests) or test(registered_maintenance::tests)' — 6/6 pass on d6a273c9):

  • PASSIVE-busy visibility: a read snapshot pinned before ~40 MiB of incompressible synthetic load makes checkpoint_result fail typed (…remains pending); after release the receipt reports the measured high-water bytes (pressured_checkpoint_reports_pinned_reader_and_reclaims_after_release).
  • Truncate drains PASSIVE-busy: a maintenance-scoped store (exclusive lifecycle lease → enter_maintenance_database_scopeDatabaseAuthority::for_runtime = Maintenance role) with the same pinned >32 MiB WAL still fails typed while pinned — maintenance authority never bypasses reader safety — then, after release, checkpoint_result truncates the file to 0 bytes (Truncated receipt, wal_bytes_after == 0, on-disk sidecar measured 0) and the store keeps serving reads/writes (maintenance_truncate_drains_passive_busy_wal_to_zero_bytes).
  • Below-trigger typing: small live WAL → BelowTrigger receipt with measured nonzero bytes, file untouched (below_trigger_checkpoint_reports_measured_wal_bytes).
  • Reclaim typing under non-maintenance authority: drained high-water WAL → RequiresExclusiveMaintenance, wal_bytes_after == wal_bytes_before, plus drain evidence (next write rewinds instead of appending past high-water). Pure-function coverage of all three plan arms across Daemon/Maintenance/Test roles (registered_maintenance::tests).

Gates: cargo fmt --check, cargo clippy --all-targets --all-features -D warnings clean on the crate; cargo check -p tracedecay --lib clean (root callers compile against the receipt). Full-crate nextest has 12 failing tests unrelated to this change — all 12 reproduce identically with the crate checked out at the clean base tip in this VM (daemon-scope-revocation family; environmental, pre-existing).

Boundary finding (per task rule: say so instead of editing other crates)

The daemon-lane file truncation belongs to tracedecay-runtime-core/tracedecay-rusqlite-runtime, not this crate: the writer's TRUNCATE command verifies the daemon-allowed Query intent, but no public runtime-core surface exposes it to daemon-role registered clients since 194781603, and the ExclusiveMaintenancePermit-gated trigger_maintenance(Truncate) writer lane is unreachable from here. This PR mounts reclaim for every journey whose authority the runtime already accepts (exclusive maintenance) and types the daemon gap in the receipt; a daemon-safe truncate mount is a runtime-core follow-up, deliberately not attempted here.

max_open ownership

max_open: 8 is daemon-only: it lives in src/daemon/store_runtime/session_registry/mounts.rs (GraphDbRegistryConfig), not in this crate. Not changed here, per coordination. (Sizing note for the daemon owner: 4 projects need ~10 by the 2×n+2 formula.)

Open in Web Open in Cursor 

@changeset-bot

changeset-bot Bot commented Aug 19, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 0b25be3

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

@cursor
cursor Bot force-pushed the cursor/global-db-wal-checkpoint-ccf4 branch 3 times, most recently from 198a5c2 to 16c6b88 Compare August 19, 2026 22:54
@ScriptedAlchemy
ScriptedAlchemy marked this pull request as ready for review August 19, 2026 22:59

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 909e581ddb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +74 to +75
WalReclaimPlan::Truncate => {
self.truncate_database_wal().await?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Wire truncation into a production maintenance journey

For the live daemon store, the existing shutdown calls in src/mcp/server/connection.rs run with daemon authority, so an oversized WAL selects RequiresExclusiveMaintenance and checkpoint() discards that receipt. A repo-wide search finds no maintenance-scoped production caller of checkpoint_result(); the truncating branch is reached only by checkpoint_tests.rs, leaving the reported 415 MiB production WAL unable to reclaim its high-water file. Wire this pass into an offline maintenance lifecycle or provide an authorized daemon reclaim path.

AGENTS.md reference: AGENTS.md:L75-L77

Useful? React with 👍 / 👎.

Comment on lines +66 to +70
let wal_bytes_before = wal_file_bytes(&wal_path)?;
self.checkpoint_database().await?;
// A successful checkpoint proves the writable scope, so the role read
// cannot race a mode downgrade.
let reclaim = match wal_reclaim_plan(wal_bytes_before, self.write_authority_role()?) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Serialize the WAL size sample with the checkpoint

Because wal_bytes_before is measured before checkpoint_database() acquires the process-local writer mutex, another task holding a cloneable RegisteredGlobalDbLeaseV1 can append while this call waits. For example, a 31 MiB sample can grow beyond 32 MiB before the checkpoint runs, after which the stale sample selects BelowTrigger, retains the oversized high-water file, and publishes a receipt inconsistent with the post-checkpoint size. Measure and decide within the same serialized checkpoint/maintenance lane.

Useful? React with 👍 / 👎.

cursoragent and others added 3 commits August 19, 2026 23:06
Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
@cursor
cursor Bot force-pushed the cursor/global-db-wal-checkpoint-ccf4 branch from 909e581 to 0b25be3 Compare August 19, 2026 23:06
@ScriptedAlchemy
ScriptedAlchemy merged commit dc3e3f8 into codex/tracedecay-total-redesign-plan Aug 19, 2026
1 check passed
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.

2 participants