fix(storage): declare the v44 index delta and gate schema-versioning by default - #3384
Conversation
…by default Problem An index schema bump merged without its lifecycle.py delta declaration, and nothing caught it. index_fast_forward_plan(43, 44) returned None, so the live archive fell back to full raw replay and every repo-CLI query against it failed with "no such column: s.title_ref" -- diagnosed by hand days later. The check that exists for exactly this (`devtools lab policy schema-versioning`) reported the violation correctly the whole time. It sits inside the `if lab:` branch of devtools/verify.py, which neither default verify, nor `verify --quick` (the pre-push hook), nor the CI lint job runs. What changed - Declare index v44 (sessions.title_ref/title_confidence) as SEMANTIC_REPARSE. That is the truthful classification under the current DerivedDeltaClass vocabulary: the DDL surface is two nullable columns, but the values come from the v44 Codex title resolver, so a shape-only fast-forward would leave all 3,201 codex-session rows NULL where a cold rebuild populates them. Behaviour is unchanged (full rebuild is what already happens); the declaration records why. polylogue-9rw0.1 owns removing the need for that classification. - Move `lab policy schema-versioning` from the --lab branch into the default verify step list and the CI lint job. It is static, archive-independent and measured at 0.63s. - Rewrite the stale comment above INDEX_SCHEMA_VERSION. It claimed index.db has no migration chain and that a stale generation needs `ops reset --index`, written before the declared fast-forward mechanism existed. It is the reason this cost was mistaken for unavoidable. Verification devtools verify --quick exit 0 (58.03s, 18 steps) lab policy schema-versioning exit 0, "Schema evolution policy intact" anti-vacuity: INDEX_SCHEMA_VERSION -> 45 exit 1, "missing: [45]"; reverted -> exit 0 Ref polylogue-9rw0, polylogue-ih67, polylogue-ze5i
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
💤 Files with no reviewable changes (1)
📝 WalkthroughWalkthroughThe change declares SQLite index schema version 44 as requiring semantic reparse, updates its versioning documentation, and runs the schema-versioning policy check in general verification and CI lint. ChangesSchema versioning lifecycle
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@devtools/verify.py`:
- Around line 1775-1781: Move the “lab policy schema-versioning” verification
step out of the general not-commit branch and into the later `if not quick and
not commit` block within `build_verify_steps`, preserving its existing command
and behavior for non-quick, non-commit runs while excluding it from quick
verification.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 374c4be5-b24e-4c72-a3bc-9baf80225de4
📒 Files selected for processing (5)
.beads/issues.jsonl.github/workflows/ci.ymldevtools/verify.pypolylogue/storage/sqlite/archive_tiers/index.pypolylogue/storage/sqlite/lifecycle.py
| # Static, archive-independent, sub-second: an index bump that | ||
| # lands without its lifecycle.py delta declaration silently | ||
| # downgrades every existing generation to a full raw replay | ||
| # (polylogue-9rw0). Gated here, not behind --lab, because the | ||
| # failure surfaces as an unqueryable live archive rather than | ||
| # as a test failure. | ||
| ("lab policy schema-versioning", _devtools_cmd("lab policy schema-versioning")), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Keep this check out of quick verification.
Because the step is added under if not commit, build_verify_steps(quick=True, commit=False) still runs it. The stated scope is non-quick, non-commit; append it inside the later if not quick and not commit block, or explicitly confirm that quick mode should include this policy.
Proposed placement
- ("lab policy schema-versioning", _devtools_cmd("lab policy schema-versioning")),
]
)
if not quick and not commit:
+ steps.append(("lab policy schema-versioning", _devtools_cmd("lab policy schema-versioning")))Also applies to: 1860-1860
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@devtools/verify.py` around lines 1775 - 1781, Move the “lab policy
schema-versioning” verification step out of the general not-commit branch and
into the later `if not quick and not commit` block within `build_verify_steps`,
preserving its existing command and behavior for non-quick, non-commit runs
while excluding it from quick verification.
…ctive index path (#3389) ## Summary Fixes a real bug class discovered while closing out `polylogue-l2cd`: ~60 call sites across daemon status/health/cursor-lag tracking, storage embeddings, schemas, sources/live, and coordination code derived a sibling durable-tier path (`ops.db`/`embeddings.db`/`user.db`/`source.db`) via `<index_path>.with_name("<tier>.db")`, where `<index_path>` was resolved through pointer-aware logic (`resolve_active_index_path()`/ `ArchiveLocation.active_index_path`/`Config.db_path`). ## Problem Once a `.index-active-pointer` file exists (written the first time `IndexGenerationStore` bootstraps against an archive whose `index.db` is already a promoted-generation symlink), `active_index_path` resolves to the pointer's target — e.g. `configured_root/.index-generations/<gen-id>/index.db` — and `.with_name("<tier>.db")` on that path lands inside the generation subdirectory, which never contains the other durable tiers (they only ever live in `configured_root`). Verified not currently live-broken on the real archive (`index.db` is already a symlink but no `.index-active-pointer` file exists yet, so resolution currently falls through to the safe branch) — this activates the next time a rebuild-and-promote cycle bootstraps `IndexGenerationStore` against an archive for the first time. ## Solution Two mechanical patterns applied per-site after tracing each anchor's actual data flow (not blindly wrapping every grep hit): - **Daemon-global code** (bare `archive_root()` already in scope, never unit-tested with an injected path): swap `resolve_active_index_path(archive_root()).with_name(...)` for `archive_root() / "<tier>.db"` directly. - **Small pure functions** tested by passing an arbitrary `tmp_path` file with no ambient config (`cursor_lag_summary_info`, `catchup_status`, `convergence_debt_status`, etc.): added an explicit optional tier-path parameter (defaulting to the old `.with_name()` derivation for backward compatibility) threaded from the caller, which already has `archive_root()` in scope — kept these functions pure/testable rather than reaching into global config internally. - Config-based sites route through the existing `archive_file_set_root()` helper (from the just-merged `archive_file_set_root_for_paths` migration) to honor the `polylogue-yla8.1` split-root override contract. A handful of sites were investigated and deliberately left untouched, documented in the PR: 4 `storage/fts/fts_lifecycle.py` call sites (bounded-risk best-effort telemetry) and 2 `sources/live/batch.py` `source.db` derivations pending further research into `CursorStore`'s tier semantics. ## Verification - `mypy --strict` clean (1091 files); `ruff check`/`format --check` clean. - `devtools verify --quick` exit 0. - Broad sweep (`tests/unit/{daemon,storage,cli,coordination,core,maintenance,sources,schemas}`, ~11k tests): 50 failures on the branch. Compared against a clean `origin/master` worktree (not the shared checkout, which had drifted to another session's branch) by exact test name: the 3 branch-only entries are all false positives — 2 are pre-existing on clean master (the still-unmerged `ih67`/v44 schema-declaration gap, tracked by #3384, unrelated to this diff) and 1 passes in isolation on both branches (order-dependent asyncio-cancellation flake under parallel test load). Zero real regressions. Ref polylogue-aaj9
…er-accuracy-and-derived-transition-gate # Conflicts: # .beads/issues.jsonl
The rebase onto master (after #3384 merged) left literal conflict markers in .beads/issues.jsonl and two intermediate commits with the file emptied. This restores the union: 1141 beads, every line parsing, no markers. Content is unchanged from the union of both sides — for each id the row with the later updated_at wins, which is the documented recovery for this file. Co-Authored-By: Claude <noreply@anthropic.com>
Summary
An index schema bump merged without its delta declaration, the live archive silently fell back to full raw replay, and the repo CLI could not read it at all. The check that catches this already existed and ran nowhere. This declares the missing delta, moves the check into the gating path, and corrects the comment that made the cost look unavoidable. It also lands a tracker pass correcting stale bead state found while diagnosing it.
Problem
index_fast_forward_plan(43, 44)returnedNone, soarchive_tiers/bootstrap.pyfell through to full rebuild and every repo-CLI query against the live v43 archive failed:devtools lab policy schema-versioningreported the violation correctly and continuously:It is appended inside
if lab:atdevtools/verify.py. Defaultdevtools verifydoes not run it;verify --quick(pre-push) does not run it; the CIlintjob runsrender all --check,verify public-claimsand ruff, not this. Three other policies behind the same gate are also currently red (demo-tour-freshness,backlog-hygiene,bead-graph) — tracked inpolylogue-ze5i.Solution
Declare index v44 as
SEMANTIC_REPARSE. v44 addssessions.title_ref/title_confidence(polylogue-ih67, #3378). The DDL surface is two nullable columns on an 18,871-row table — trivially clone-safe — but the values come from the v44 Codex title resolver, affecting 3,201 of 18,871 sessions (100% ofcodex-session). A shape-only fast-forward would leave those NULL where a cold rebuild populates them, and that divergence must not be silently promoted.DerivedDeltaClasshas no class for "clone-safe shape, values via bounded targeted reprocess", soSEMANTIC_REPARSEis the truthful classification today. Operational behaviour is unchanged — full rebuild is what already happens.polylogue-9rw0.1owns extending the vocabulary so this delta becomes a shape fast-forward plus a Codex-scoped reprocess.Gate the policy by default —
devtools/verify.pydefault step list plus the CIlintjob. Static, archive-independent, measured at 0.63s.Rewrite the stale comment above
INDEX_SCHEMA_VERSION, which claimed index.db has no migration chain and that a stale generation needsops reset --index. It predates the declared fast-forward mechanism and is the direct reason the rebuild cost was mistaken for unavoidable. (archive_tiers/embeddings.py:19-22makes the same claim and is left alone — it is accurate, that tier has no declarations.)Verification
Anti-vacuity — the gate fails on a real violation, not just passes on a clean tree:
Tracker changes in this commit
Nine beads filed, each with bounds measured in this session rather than a single noticed instance:
9rw0.1ze5i--labcijx.1repo_ids for one repo (sinex 28, sinnix 31)cuxz.4tool_resultoutcome NULL undifferentiated: 1,333,201 rows; codex 86%, claude-code 53%, claude-ai 0%cuxz.5tool_useblocks4ts.10session_links.status/.methodempty on 9,179 of 9,179 rowsb5l.35xngpaste_spans= 4 rows / 3 sessions across 5,042,564 blocks1halbacklog-hygieneX2: 5 of 6 findings are false positivesCorrections to existing beads:
m3p9headline was 12× stale — "79% / 65,946 sessions" re-measured to 5.9% / 1,117; the original figure predated the hook de-inflation that moved the denominator 4.5×. Retitled and re-scoped.ih67— 3,101 → 3,201, and it is 100% of the Codex population, not a subset.hjpx.2— raw-frontier 15,264 → 2,593 and draining; the monotonic growth that justifiedhjpx's P0 escalation is not the current behaviour.z9ghAC7 bounded. It read "Every residual symptom is mapped to one class mechanism" — an unbounded quantifier with no enumeration procedure, making the bead unclosable by construction and causing cold agents to report the P0 as long-horizon. Intent preserved; the set must now be enumerated from a stated snapshot. Snapshot figures refreshed (18,428 → 18,871 sessions, 4.85M → 5,042,564 blocks).yla8/lkrc/hjpx— the standing "No live apply is authorized" note was reading as a permanent prohibition and is the direct cause of an ask-defer loop running since 2026-07-15. The single operator input that lifts it is now written out once onyla8, with the agent-side prerequisites that must be reported first. No authorization is asserted in this PR — the note makes the question answerable in a line.de2a/623q/xikl— acceptance criteria added (all three were P1 with none).de2acarries a live writer-starvation baseline;xikl's "operator decision" was already made on 2026-07-19 and it appears in blocked-on-decision sweeps only because the phrase occurs in its text.yla8,1xc.14,yyvg.7distinguishing agent session ids from bead ids.Ref polylogue-9rw0, polylogue-ih67, polylogue-ze5i
Summary by CodeRabbit