feat(storage): execute declared index fast-forward plans on open#3238
Conversation
Wire an executor into the index-tier open path so a declared clone-safe schema-version gap no longer forces a full archive rebuild. Ref polylogue-t3gk Co-Authored-By: Claude <noreply@anthropic.com>
Move canonical-DDL resolution into lifecycle.py and use literal, in-module mutation SQL for the FTS rebuild path so the new fast-forward executor passes devtools verify layering as a properly inventoried index-tier writer. Ref polylogue-t3gk Co-Authored-By: Claude <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Warning Review limit reached
Next review available in: 52 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (9)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
Coordinator stand-in review (CodeRabbit rate-limited): read the full 778-line diff.
Merging on green quick-gate. Operational note: first open of the live v42 archive by post-merge code will execute the v43 fast-forward (ledger population over 4.77M blocks) — coordinator will sequence that after the in-flight Hermes reprocess releases the writer. |
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QUsH3Rhq6oAZpYPWcsZqnZ
Summary
Wires an executor into the index-tier open path so a declared clone-safe schema-version gap no longer forces a full archive rebuild. Before this change,
IndexDeltaDeclaration/IndexFastForwardPlan(polylogue/storage/sqlite/lifecycle.py) had no consumer:initialize_archive_databasealways raised the rebuild-requiredRuntimeErrorfor anyPRAGMA user_versiongap, benign or not.Problem
Live incident 2026-07-21: a freshly promoted v42
index.dbcould not be opened by v43 code, even though#3235shipped a declared clone-safe fast-forward (IndexDeltaDeclarationv43,FastForwardOperationname=v43-messages-fts-identity, kind=REBUILD_FTS).grepconfirmed nothing outsidelifecycle.pyconsumedIndexFastForwardPlan/eligible_for_sql_fast_forward— the declaration registry existed but no open-path executor applied it, so the declared-benign v43 delta still forced the ~11h full rebuild it exists to avoid.Solution
polylogue/storage/sqlite/archive_tiers/index_fast_forward_executor.py(new):apply_index_fast_forward(conn, plan)applies a plan's declarations one at a time, each inside its ownBEGIN IMMEDIATE/commit transaction, bumpingPRAGMA user_versionin the same transaction. A crash mid-chain rolls back to the previous version; the next open rebuilds the plan from whatever version actually landed and re-applies idempotently.FastForwardOperationKind, not per-version:REBUILD_FTS— drops the declared triggers, callsensure_fts_index_sync, then literalDELETE FROM messages_fts/DELETE FROM messages_fts_identity+insert_all_message_*_rows_sql()for whichever surfaces the declaration names (narrow identity-only refresh for v43, not a full content rescan).REPLACE_TABLE— copy-forwards columns shared between the existing and canonical shape onto a temp table, then renames it in (handles the declared v33/v36/v38/v41 constraint-widening/column-removal deltas without a raw reparse).REPLACE_VIEW/CREATE_INDEX/DROP_TABLE— straightforward canonical-DDL re-run / drop.polylogue/storage/sqlite/lifecycle.py: addedresolve_canonical_index_objects(executesINDEX_DDLagainst a scratch:memory:connection to resolve each declared object's exact canonicalCREATEstatement) — lives here rather than in the executor so it stays a read-only helper, not itself a mutation the writer-module inventory has to account for.polylogue/storage/sqlite/archive_tiers/bootstrap.py:initialize_archive_databasenow callsindex_fast_forward_plan(current_version, expected_version)before raising the rebuild-required error forArchiveTier.INDEX; applies the plan and returns when one is eligible, falls through to the existing error otherwise (e.g. aSEMANTIC_REPARSEdeclaration is in the span, as with v39/v42).polylogue/storage/fts/fts_lifecycle.py: splitrebuild_fts_index_syncinto reusablerebuild_messages_fts_content_sync/rebuild_messages_fts_identity_synchelpers (used internally; the executor inlines the literalDELETEstatements itself sodevtools verify layering's writer-module inventory can see and account for the index-tier mutation).docs/plans/layering.yaml: registered the new module as an index-tier writer (Writer module: indexdocstring marker +entrypoints: [apply_index_fast_forward]).docs/internals.md: documented the open-path executor in the Schema Versioning Model section.docs/plans/topology-target.yaml/docs/topology-status.md: regenerated for the new module.Verification
devtools test tests/unit/storage/test_index_fast_forward_executor.py— 5 passed. New tests build a v43-schemaindex.db, downgrade it to the exact v42-shaped trigger bodies (nomessages_fts_identity, pre-v43 trigger bodies), then open it throughinitialize_archive_databaseand assert: noRuntimeError,PRAGMA user_version == 43, the identity ledger is populated for every indexable block,message_identity_mismatch_sql()returns 0, a second open is idempotent, aSEMANTIC_REPARSEdeclaration still raises the rebuild-required error, and dispatch is exercised generically (not v43-hardcoded) via a syntheticDROP_TABLEdeclaration.devtools test tests/unit/storage/test_index_fast_forward_lifecycle.py tests/unit/storage/test_archive_tiers_ddl.py tests/unit/storage/test_archive_tier_init.py— 46 passed, 4 pre-existing failures intest_index_fast_forward_lifecycle.pyreproduced identically on unmodified master viagit stash(unrelated to this change; xdist-order-dependentschema_policy/monkeypatch leakage, not caused here)./realm/project/polylogue/.venv/bin/python -m mypy --stricton all four touched modules —Success: no issues found./realm/project/polylogue/.venv/bin/python -m devtools verify layering—No layering violations found.devtools verify --quick—exit_code: 0(format, lint, mypy, render-all-check, verify-layering, verify-closure-matrix, schema-roundtrip, manifests, ci-workflows, doc-commands, docs-coverage, test-infra-currency, test-clock-hygiene, pytest-timeout-overrides, degrade-loudly all green).devtools test tests/unit/storage/test_fts_identity_ledger.py tests/unit/storage/test_fts_bloat_invariants.py tests/unit/storage/test_fts_repair_sql.py tests/unit/storage/test_dangling_fts_derived_surfaces.py tests/unit/daemon/test_fts_global_repair.py— 1 pre-existing failure (test_dangling_fts_derived_surfaces.py::test_reset_and_repair_fts_rows_recovers_excess_message_rows,no such table: main.derived_refresh_guard) reproduced identically on unmodified master.AC matrix (Ref polylogue-t3gk)
bootstrap.pycallsapply_index_fast_forwarduser_versionbumped + committed per declarationFastForwardOperationKind, not v43-onlyif/elifon kind; exercised by a synthetic non-v43 declaration in testsSEMANTIC_REPARSEtestBEGIN IMMEDIATEonly within that connection's own transactionuser_version==43, zero identity mismatchResidual risk
REPLACE_TABLE/REPLACE_VIEW/CREATE_INDEXdispatch (v33/v34/v36/v38/v41 declarations) is implemented and unit-exercised only indirectly (through the generic-dispatch synthetic test); it is not exercised end-to-end against a real historical fixture in this PR because those spans are not reachable in practice today —index_fast_forward_planrequires a contiguous eligible chain, and the v39 and v42SEMANTIC_REPARSEdeclarations block any span crossing them, so the only currently-reachable production gap is v42→v43. If an operator ever needs v32-era fast-forward again, add a fixture-based test for that span before relying on it.