Views v3 ledger: report contention as busy, not unreadable or schema drift

AF
1h ago
+660-85

Two Windows CI failures today (a branch-switch round trip in #1098's queue group and on main at 3d2e786fcb) refused a call-graph view with reservation_ledger_unreadable. The old error dropped its cause, and the failure didn't reproduce natively, so this PR doesn't claim to fix those incidents. It fixes what the code shows, and it adds the path, the operation and the underlying error to every ledger refusal, so a recurrence names its site.

  • Busy WAL checkpoint: a wal_checkpoint(TRUNCATE) that reports busy now returns a typed SQLITE_BUSY with the checkpoint counters (reservation_ledger_busy: checkpoint WAL <path>: ... busy=1 ...) instead of reservation_ledger_unreadable.
  • Unstable measurement: a namespace that doesn't stabilize after 8 measurements or corrections reports busy.
  • Busy read as schema drift (found by a new test, on Linux and Windows): v3 initialization and format validation turned SQLITE_BUSY into schema_drift, which reads as a damaged store. Contention is kept now; real malformed schemas keep their drift checks.
  • Callgraph: busy carries through plane publication, base references, the registry and the driver to the call-graph view, which already reports busy as "building, retry". The classification across layers matches known message prefixes, which is weaker than a typed error.

Tests, each with real SQLite contention, a recovery check, and a check that it fails without the fix: checkpoint_contention_is_busy_and_preserves_sqlite_cause, busy_validation_is_not_persisted_as_schema_drift, changing_derived_database_reports_busy_then_measurement_recovers, v3_checkpoint_contention_reaches_checkout_reader_as_busy and v3_busy_ledger_during_view_construction_is_retryable. 181 v3, callgraph and migration tests pass, plus the Windows target check.


Summary by cubic

v3 reservation-ledger contention now reports as transient busy instead of reservation_ledger_unreadable or schema_drift, which previously read as a damaged store. Base-plane completion owned by another driver also classifies as busy. Each ledger refusal records the ledger path, the failing operation, and the SQLite cause, so a recurrence names its site. This doesn't claim to fix the two Windows CI incidents that motivated it; neither reproduced natively.

Busy classification

  • A busy wal_checkpoint(TRUNCATE) returns a typed SQLITE_BUSY with WAL counters; namespaces and registries that don't settle after 8 attempts report busy.
  • v3 validation and initialization keep SQLITE_BUSY instead of converting it to schema drift; genuine malformed or incomplete schemas still fail drift checks.
  • Permanent ledger failures still become migration storage_gap refusals; busy is never swallowed.

Contention propagation

  • Busy classification carries through plane publication, base references, the driver, and view construction; the call-graph view access keeps its SQLite busy form, which callers already treat as retryable, instead of an unreadable graph.
  • Base completion fenced by another driver answers busy until the base becomes ready.
  • Tests use real SQLite contention with recovery checks and fail without the fix.