You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Prod Postgres bootstraps from schema.sql (compose initdb mount); CI/tests bootstrap from alembic upgrade head on SQLite. There is no gate comparing the two, so CI validates a schema that differs from production. Verified divergences at e5764a5:
idx_news_items_topics_gin exists in schema.sql:160 but is absent from every Alembic migration (0 hits) — topic-array query support present in compose Postgres, missing on Alembic-bootstrapped Postgres.
documents.embedding is added Postgres-only in alembic/versions/001_canonical_schema.py:170 (ALTER TABLE documents ADD COLUMN ... vector(384)); the SQLite Alembic path lacks it and embeddings.py patches it at runtime (see related [Audit] Remove Postgres DDL from store_document runtime path #1281) — so Alembic-SQLite schema ≠ schema.sql.
chat_feedback.created_at: schema.sql declares it NOT NULL DEFAULT now(), but alembic/versions/009_chat_feedback.py:30 sets nullable=True.
Scope
Add a parity gate that fails CI when schema.sql and the Alembic-produced schema diverge on Postgres, and close the verified gaps above.
Non-Goals
Do NOT remove dialect-legitimate differences (e.g. GIN/vector are Postgres-only; SQLite equivalents may be skipped — but the gate must record them as intentional).
Do NOT change the existing scripts/check_dialect_portability.py token gate; this is a complementary structural parity check.
Tasks
Add a parity check (script or test) that bootstraps an ephemeral Postgres two ways — schema.sql vs alembic upgrade head — and diffs tables/columns/indexes/nullability, failing on undocumented divergence.
Add idx_news_items_topics_gin via a dialect-guarded Alembic migration (Postgres postgresql_using='gin'; skip/equivalent on SQLite).
Set created_at nullable=False in alembic/versions/009_chat_feedback.py:30 to match schema.sql.
Acceptance Criteria
New parity gate passes and fails if a column/index/nullability is present in one bootstrap path but not the other (without an allowlist entry).
SELECT 1 FROM pg_indexes WHERE indexname='idx_news_items_topics_gin' returns a row on both bootstrap paths.
Deliberate-break demonstration: drop the gin index from schema.sql (or revert the migration) → parity gate fails; restore → passes.
Implementation Notes
Audit baseline: main @ e5764a5 on 2026-06-28. All three divergences source-verified. This is the LMS-class "SQLite-CI vs Postgres-prod" risk: tests pass on a schema prod doesn't run.
Why
Prod Postgres bootstraps from
schema.sql(compose initdb mount); CI/tests bootstrap fromalembic upgrade headon SQLite. There is no gate comparing the two, so CI validates a schema that differs from production. Verified divergences ate5764a5:idx_news_items_topics_ginexists inschema.sql:160but is absent from every Alembic migration (0 hits) — topic-array query support present in compose Postgres, missing on Alembic-bootstrapped Postgres.documents.embeddingis added Postgres-only inalembic/versions/001_canonical_schema.py:170(ALTER TABLE documents ADD COLUMN ... vector(384)); the SQLite Alembic path lacks it andembeddings.pypatches it at runtime (see related [Audit] Remove Postgres DDL from store_document runtime path #1281) — so Alembic-SQLite schema ≠schema.sql.chat_feedback.created_at:schema.sqldeclares itNOT NULL DEFAULT now(), butalembic/versions/009_chat_feedback.py:30setsnullable=True.Scope
Add a parity gate that fails CI when
schema.sqland the Alembic-produced schema diverge on Postgres, and close the verified gaps above.Non-Goals
vectorare Postgres-only; SQLite equivalents may be skipped — but the gate must record them as intentional).scripts/check_dialect_portability.pytoken gate; this is a complementary structural parity check.Tasks
schema.sqlvsalembic upgrade head— and diffs tables/columns/indexes/nullability, failing on undocumented divergence.idx_news_items_topics_ginvia a dialect-guarded Alembic migration (Postgrespostgresql_using='gin'; skip/equivalent on SQLite).created_at nullable=Falseinalembic/versions/009_chat_feedback.py:30to matchschema.sql.Acceptance Criteria
SELECT 1 FROM pg_indexes WHERE indexname='idx_news_items_topics_gin'returns a row on both bootstrap paths.schema.sql(or revert the migration) → parity gate fails; restore → passes.Implementation Notes
Audit baseline:
main@e5764a5on 2026-06-28. All three divergences source-verified. This is the LMS-class "SQLite-CI vs Postgres-prod" risk: tests pass on a schema prod doesn't run.