fix(store): retype legacy belief_corroborations.belief_id INTEGER -> TEXT (#762) - #765
Conversation
Add _maybe_retype_belief_corroborations_belief_id() one-shot migration that detects the legacy INTEGER column via PRAGMA table_info and runs the SQLite-recommended column-retype recipe (CREATE new -> COPY -> DROP old -> RENAME) under PRAGMA foreign_keys=OFF. Wired into __init__ before _maybe_consolidate_content_hash_duplicates so that pass's synthetic consolidation_migration rows write against the fixed shape. Idempotent via SCHEMA_META_CORROBORATIONS_BELIEF_ID_RETYPED; fresh stores stamp the marker without doing work. Root cause: the released v1.5.0 package shipped with belief_id INTEGER on some user DBs, but CREATE TABLE IF NOT EXISTS in _SCHEMA never overwrote the broken column. beliefs.id is TEXT PRIMARY KEY, so the FK silently missed on every hex-id insert and aelf lock <text> against Jaccard-close existing beliefs raised FOREIGN KEY constraint failed in record_corroboration.
Five tests against an in-memory + tmp_path DB seeded directly via sqlite3 to reproduce the legacy v1.5.0 INTEGER schema: - legacy DB is retyped on first open; marker stamped - re-opens are no-ops; marker unchanged - fresh stores stamp marker without doing work - record_corroboration with a hex TEXT id now succeeds (#762 repro) - pre-existing rows carry over with their TEXT belief_id preserved
There was a problem hiding this comment.
Sorry @robotrocketscience, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (2)
✨ 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 |
PR-size soft capThis PR is over the advisory size threshold:
Bigger PRs collide with more open work, which under the parallel-session workflow tends to produce repeated This is advisory only — nothing is blocked. If the size is intentional (large refactor, module removal, generated code), apply the |
|
[claim:review:pascal:2026-05-13T23:22:11Z] |
robotrocketscience
left a comment
There was a problem hiding this comment.
LGTM. (Cannot formally --approve — shared GH author with PR opener; merge-train bot gates on signatures + green checks + ready-to-merge label, not GH review state.)
Reviewed:
- Migration logic in
_maybe_retype_belief_corroborations_belief_idfollows the SQLite-recommended column-retype recipe (CREATE _new → INSERT SELECT → DROP → RENAME → recreate index), wrapped inPRAGMA foreign_keys=OFF/ONoutside the explicit BEGIN/COMMIT. - New table shape (lines 1131-1141) matches the canonical
_SCHEMAdefinition at store.py:257-265 exactly:belief_id TEXT NOT NULL REFERENCES beliefs(id) ON DELETE CASCADE,ingested_at TEXT NOT NULL(no default, consistent with canonical). - Ladder order is correct: runs before
_maybe_consolidate_content_hash_duplicates, so the syntheticconsolidation_migrationcorroboration rows write against the fixed shape (body's explicit ordering rationale). - Idempotency via
SCHEMA_META_CORROBORATIONS_BELIEF_ID_RETYPED. Fresh stores and non-INTEGER shapes both stamp the marker without doing the swap. - The "missing table" branch stamps the marker after
_SCHEMA's CREATE IF NOT EXISTS has already created the canonical shape on this open — correct. - Tests cover five distinct paths: legacy retype, idempotency across re-opens, fresh-store no-op, the
record_corroborationrepro from the issue, pre-existing row carry-over. Hex-affinity claim about pre-existing rows surviving the copy as TEXT is correct (SQLite NUMERIC affinity coerces only well-formed integer literals;4fadaddd9b67b614containsa-fso survives). - "Confirm not affected" claim verifies —
test_cli_confirm.py:172assertsaelf:confirmwrites tofeedback_history, notbelief_corroborations.
CI: pytest 3.12/3.13, CodeQL, all Staging Gate jobs green. mergeable=MERGEABLE. Discretion grep clean. Three atomic signed commits with conventional prefixes (fix / test / docs).
Two non-blocking notes for posterity:
- The FK=OFF toggle is defense-in-depth — no other table references
belief_corroborations, so DROP would succeed under FKs ON anyway. Fine to keep. - In the cosmically improbable case of a 16-char hex ID that happens to be all decimal digits (~1 in 3e12 for
secrets.token_hex(8)), legacy INTEGER affinity would have coerced it on insert and lost leading zeros. Pre-existing rows in that corner case would already be corrupted on disk; the migration's straight copy preserves whatever's stored, which is the only sensible behavior.
Applying ready-to-merge.
|
[release:review:pascal:2026-05-13T23:24:46Z] |
|
merge-train: merged 3ffad99 → |
Closes #762.
Problem
aelf lock <statement>fails withsqlite3.IntegrityError: FOREIGN KEY constraint failedon DBs created against the released v1.5.0 package whenever the statement is Jaccard-close enough to an existing belief to route throughStore.insert_or_corroborate→record_corroboration.Some legacy DBs shipped with
belief_corroborations.belief_id INTEGEReven thoughbeliefs.idisTEXT PRIMARY KEY. Because every DDL in_SCHEMAusesCREATE TABLE IF NOT EXISTS, the canonical corrected shape never reached pre-existing stores — the FK silently misses on every hex-id insert.Silent until the dedup path triggers, so users likely worked around by adding noise text rather than reporting.
Fix
New
MemoryStore._maybe_retype_belief_corroborations_belief_id()one-shot migration:PRAGMA table_info(belief_corroborations)— only run whenbelief_idreports typeINTEGER(uppercased). Anything else stamps the marker and exits.PRAGMA foreign_keys=OFF(outside any transaction, per SQLite altertable guidance).BEGIN…COMMIT:CREATE belief_corroborations_newwith the canonical TEXT FK →INSERT … SELECTto copy rows →DROP belief_corroborations→ALTER … RENAME→ recreate the index.SCHEMA_META_CORROBORATIONS_BELIEF_ID_RETYPED.Wired into
__init__before_maybe_consolidate_content_hash_duplicatesso the latter's syntheticconsolidation_migrationrows write against the fixed shape.Hex belief ids round-trip cleanly through the straight
INSERT … SELECT: digit-only literals would have been coerced under INTEGER affinity, but hex strings are not valid INTEGER literals, so SQLite preserved them as TEXT in storage.Verification
tests/test_belief_corroborations_retype_migration.py:test_legacy_integer_schema_is_retyped_on_open— pre-condition is INTEGER, post-condition is TEXT + marker.test_retype_is_idempotent_across_opens— re-open does not rewrite the marker.test_fresh_store_stamps_marker_without_swap— fresh store gets canonical schema + marker.test_record_corroboration_succeeds_after_retype— the fix(store): aelf lock fails FK on Jaccard-match — belief_corroborations.belief_id INTEGER vs beliefs.id TEXT (no migration) #762 repro: hex-idrecord_corroborationno longer raises.test_existing_corroboration_rows_carry_over— pre-existing rows survive the swap with TEXT belief_id intact.test_corroborations.py,test_content_hash_consolidation.py,test_insert_or_corroborate.py,test_speculative_hash_v2_migration.py,test_legacy_migration.py,test_v1_to_v1x_migration.py.test_store_crud.py,test_setup_migrate_order.py,test_cli_confirm.py.Notes
belief_corroborations, so synthetic-row writes (consolidation_migrationaudit rows) land on the fixed schema.PRAGMA foreign_keys=OFF; that mirrors the affinity-coercion path that lets some pre-existing rows survive on broken stores in the wild.aelf:confirmwrites tofeedback_history, notbelief_corroborations(pertest_cli_confirm.py:test_confirm_does_not_write_belief_corroborations), so the fix(store): aelf lock fails FK on Jaccard-match — belief_corroborations.belief_id INTEGER vs beliefs.id TEXT (no migration) #762 report's "presumably broken on old DBs for the same reason" speculation does not apply to confirm. Only the dedup / corroboration ingest path was affected.