Skip to content

refactor(storage): consolidate table/column/index exists checks into introspection - #3690

Merged
Sinity merged 4 commits into
masterfrom
feature/storage/consolidate-table-exists-introspection
Aug 3, 2026
Merged

refactor(storage): consolidate table/column/index exists checks into introspection#3690
Sinity merged 4 commits into
masterfrom
feature/storage/consolidate-table-exists-introspection

Conversation

@Sinity

@Sinity Sinity commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Summary

Consolidates ~30 independently maintained _table_exists/_column_exists/_index_exists copies (and sync/async variants) into one canonical module, polylogue/storage/introspection.py, and adds a grep/AST-based lint tripwire to devtools verify --quick that forbids reintroducing the duplication.

Problem

polylogue-48h found the pattern grown to ~25 near-identical _table_exists-shaped helpers scattered across cli/, daemon/, storage/, sources/, insights/, and operations/, each trivially small and subtly different (a schema= kwarg on some, type IN (...) alternatives that never actually match anything in sqlite_master on others, unparameterized vs. parameterized SQL on others). A prior PR (#2912) had already consolidated table_exists/table_exists_async into polylogue/storage/table_existence.py and redirected ~25 call sites, but column_exists/index_exists were never added, and the remaining ~25 call sites (not covered by that PR) each still carried their own local definition — the bead's own live-count corroboration found _table_exists still appearing 11x and table_exists 29x across the tree.

Solution

  • Renamed polylogue/storage/table_existence.py to polylogue/storage/introspection.py, keeping it at storage/ root rather than storage/sqlite/ for the same documented reason as before: storage/sqlite/__init__.py eagerly imports the heavy async backend stack, so a low-level module importing a storage.sqlite sibling triggers a real circular import.
  • Added column_exists/column_exists_async/index_exists/index_exists_async alongside the existing table_exists/table_exists_async. Schema-qualification (schema= kwarg) is omitted for the default "main" schema so every pre-consolidation bare PRAGMA table_info(...)/sqlite_master query stays byte-identical — several call sites are covered by mock-based tests that pattern-match exact query text.
  • Redirected all ~25 remaining duplicate-definition call sites to import from the canonical module. Several copies checked type IN ('table', 'virtual table') or type IN ('table', 'shadow'); neither value is ever a real sqlite_master type (virtual tables, including FTS5 shadow tables, register with type='table' — confirmed directly against a live FTS5 vtable), so redirecting to the canonical type='table' check is behavior-preserving.
  • Kept genuinely distinct behavior as a thin, differently-named wrapper delegating to the canonical function rather than folding it away: usage.py's _table_exists_in_schema and daemon/metrics.py's _attached_table_exists swallow sqlite3.Error for a schema alias that may not be ATTACHed yet; embeddings/support.py's table_exists_sync_missing_safe/table_exists_async_missing_safe swallow a still-in-flight sqlite3.OperationalError; cli/commands/status.py and storage/archive_readiness.py keep their local _schema_object_exists/_view_exists (view support is out of this bead's scope) and only redirect _table_exists/_column_exists.
  • storage/sqlite/run_projection_relations.py's table_exists_sync had no callers anywhere in the repo; deleted outright.
  • Added devtools/verify_table_exists_duplication.py, a grep-based tripwire wired into devtools verify --quick (the lab policy static-check block) that forbids a new top-level def named table_exists/column_exists/index_exists (or a _-prefixed/_sync/_async variant) outside the canonical module. Registered as devtools lab policy table-exists-duplication.
  • Updated docs/plans/layering-surface-baseline.json: renamed the 5 existing table_existence baseline entries and added 5 new entries for modules that previously defined their own _table_exists (so were never in the baseline for this substrate import) and now correctly need one.

Alternatives rejected

  • Creating the module at polylogue/storage/sqlite/introspection.py as literally named in the bead's original design note — rejected because it would reintroduce the exact circular-import problem the existing table_existence.py module's docstring already documents and was placed at storage/ root specifically to avoid.
  • Schema-qualifying every PRAGMA/sqlite_master query unconditionally (matching the pre-existing table_existence.py behavior) — rejected after it broke several mock-based tests that pattern-match exact query text; omitting the qualifier for the default "main" schema is behaviorally identical in real SQLite (an unqualified sqlite_master read never merges attached-database schemas) and keeps every existing call site's query text unchanged.

Verification

  • mypy --strict polylogue devtools tests/unit/storage/test_introspection.py tests/unit/cli/commands/test_status.py tests/unit/storage/test_embedding_contracts.py tests/unit/daemon/test_daemon_cli.pySuccess: no issues found in 1342 source files
  • ruff check polylogue/ devtools/ tests/All checks passed!; ruff format --check → all formatted
  • devtools lab policy table-exists-duplicationTable/column/index existence-check consolidation intact: no module outside polylogue/storage/introspection.py redefines table_exists/column_exists/index_exists (polylogue-48h).
  • devtools test tests/unit/storage/test_introspection.py13 passed
  • Focused devtools test runs across every touched module's associated test files (~1850 tests total across several batches) → all green except two failures independently confirmed pre-existing and unrelated (verified against origin/master before this branch's changes): tests/unit/cli/test_status.py::test_archive_facade_route_catalog_covers_public_async_facade (an unrelated MCP route-catalog mismatch) and tests/unit/daemon/test_health_contract.py::TestTierInventoryContract::test_medium_tier_inventory_pinned (an unrelated pinned health-check inventory drift); tests/unit/daemon/test_metrics_endpoint.py::...test_embedding_backlog_and_latest_catchup_state fails in polylogue/storage/sqlite/archive_tiers/ops_write.py (a file this PR never touches) on an unrelated status IN ('running', 'completed', 'failed', 'interrupted') CHECK constraint / "cancelled" enum mismatch.
  • devtools verify --quickexit_code: 0 (post-rebase onto latest origin/master)

Ref polylogue-48h, polylogue-a7xr.9

Sinity added 4 commits August 3, 2026 22:59
…dex checks

Ref polylogue-48h, polylogue-a7xr.9

Problem: polylogue-48h found ~25 independently maintained
_table_exists/_column_exists/_index_exists copies (plus sync/async and
schema-qualified variants) scattered across cli/, daemon/, storage/,
sources/, insights/, and operations/ -- each trivially small and subtly
different. A prior PR (#2912) had already consolidated table_exists/
table_exists_async into polylogue/storage/table_existence.py and redirected
most call sites, but column_exists/index_exists were never added and ~25
call sites still carried their own local definition.

Solution: rename polylogue/storage/table_existence.py to
polylogue/storage/introspection.py (kept at storage/ root, not
storage/sqlite/, for the same documented circular-import reason as before:
storage/sqlite/__init__.py eagerly imports the heavy async backend stack),
and add column_exists/column_exists_async/index_exists/index_exists_async
alongside the existing table_exists/table_exists_async. Schema-qualification
(the schema= kwarg) is omitted for the default "main" schema so every
pre-consolidation bare `PRAGMA table_info(...)`/`sqlite_master` query stays
byte-identical -- several call sites redirected in the next commits are
covered by mock-based tests that pattern-match exact query text.
…ospection

Ref polylogue-48h

Mechanical import-path update for the 25 modules that already imported
table_exists/table_exists_async from the (now renamed) storage.table_existence
module, following the rename in the previous commit. No behavior change.
…exists checks

Ref polylogue-48h

Replace the ~25 remaining independently maintained _table_exists/
_column_exists/_index_exists definitions (and sync/async pairs) with imports
from polylogue.storage.introspection. Several copies checked
`type IN ('table', 'virtual table')` or `type IN ('table', 'shadow')`;
neither value is ever a real sqlite_master `type` (virtual tables, including
FTS5 shadow tables, register with type='table'), so redirecting to the
canonical `type='table'` check is behavior-preserving -- confirmed directly
against a live FTS5 vtable (all 6 shadow tables show type='table').

Genuinely distinct behavior is kept as a thin, differently-named wrapper
delegating to the canonical function rather than folded away:
- usage.py's _table_exists_in_schema and daemon/metrics.py's
  _attached_table_exists swallow sqlite3.Error for a schema alias that may
  not be ATTACHed yet.
- embeddings/support.py's table_exists_sync_missing_safe/
  table_exists_async_missing_safe swallow a still-in-flight
  sqlite3.OperationalError ("no such table").
- cli/commands/status.py and storage/archive_readiness.py keep their local
  _schema_object_exists/_view_exists (view support isn't part of this
  bead's scope) and only redirect _table_exists/_column_exists.
- storage/sqlite/run_projection_relations.py's table_exists_sync had no
  callers anywhere in the repo; deleted outright instead of redirected.

Test fixes for query-text-sensitive mocks broken by the consolidation:
- tests/unit/daemon/test_daemon_cli.py: FakeConnection assertions expected
  the old `type IN ('table', 'virtual table')` text; updated to the
  behaviorally-identical `type='table'` text the canonical function emits.
- tests/unit/storage/test_embedding_contracts.py: _VeclessConnection.execute
  dropped bind parameters entirely, which only worked because the old
  support.py table_exists_sync interpolated the table name into the SQL
  string instead of binding it. The canonical function binds `name` as a
  real parameter, so the fake now forwards parameters to the real
  sqlite3.Connection.execute -- the fake was silently wrong before, not the
  production code.
- tests/unit/cli/commands/test_status.py: mypy --strict's no-implicit-
  reexport rule correctly rejects importing _table_exists/_column_exists
  through status.py's own re-import; the test now imports table_exists/
  column_exists directly from polylogue.storage.introspection, matching the
  existing polylogue-ogn1 precedent for _archive_readiness_status.
…e updates

Ref polylogue-48h

Add devtools/verify_table_exists_duplication.py: a grep/AST-based tripwire
wired into `devtools verify --quick` (via the "lab policy" static-check
block) that forbids a new top-level def named table_exists/column_exists/
index_exists (or a _-prefixed/_sync/_async variant) outside
polylogue/storage/introspection.py -- the same pattern the bead's
consolidation removed. Registered as `devtools lab policy
table-exists-duplication` in command_catalog.py; docs/devtools.md
regenerated via `devtools render devtools-reference`.

docs/plans/layering-surface-baseline.json: renamed the 5 existing
table_existence baseline entries to introspection, then added 5 new entries
for modules (cli/commands/status.py, daemon/embedding_backlog.py,
daemon/fts_automerge.py, daemon/fts_startup.py, daemon/fts_status.py) that
previously defined their own _table_exists and so were never in the
baseline for this import -- now that they import the shared substrate
module, `devtools verify layering`'s cli/daemon-may-not-import-storage
ratchet correctly requires them to be declared.
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@Sinity, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 5 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 92ea4311-ff24-4d93-bf0e-f70eb62dee39

📥 Commits

Reviewing files that changed from the base of the PR and between 69fed11 and 1aca750.

📒 Files selected for processing (60)
  • devtools/command_catalog.py
  • devtools/verify.py
  • devtools/verify_table_exists_duplication.py
  • docs/devtools.md
  • docs/plans/layering-surface-baseline.json
  • polylogue/browser_capture/receiver.py
  • polylogue/cli/commands/status.py
  • polylogue/cli/commands/tutorial.py
  • polylogue/cli/read_views/streaming_markdown.py
  • polylogue/daemon/convergence_stages.py
  • polylogue/daemon/embedding_backlog.py
  • polylogue/daemon/fts_automerge.py
  • polylogue/daemon/fts_orphan_audit.py
  • polylogue/daemon/fts_startup.py
  • polylogue/daemon/fts_status.py
  • polylogue/daemon/metrics.py
  • polylogue/hooks/__init__.py
  • polylogue/insights/capture_coverage.py
  • polylogue/insights/readiness.py
  • polylogue/maintenance/archive_verification.py
  • polylogue/maintenance/rebuild_index.py
  • polylogue/operations/archive_debt.py
  • polylogue/schemas/generation/archive_workload_profile.py
  • polylogue/sources/live/convergence_debt_retry.py
  • polylogue/sources/live/hook_paste_enrichment.py
  • polylogue/storage/archive_readiness.py
  • polylogue/storage/blob_gc.py
  • polylogue/storage/blob_integrity.py
  • polylogue/storage/blob_publication.py
  • polylogue/storage/blob_repair.py
  • polylogue/storage/derived/derived_status.py
  • polylogue/storage/embeddings/embedding_stats.py
  • polylogue/storage/embeddings/materialization.py
  • polylogue/storage/embeddings/preflight.py
  • polylogue/storage/embeddings/status_payload.py
  • polylogue/storage/embeddings/support.py
  • polylogue/storage/fts/dangling_repair.py
  • polylogue/storage/fts/freshness.py
  • polylogue/storage/fts/fts_lifecycle.py
  • polylogue/storage/fts/session_repair.py
  • polylogue/storage/hook_payload_ref_reconciliation.py
  • polylogue/storage/insights/feedback/__init__.py
  • polylogue/storage/introspection.py
  • polylogue/storage/raw_retention.py
  • polylogue/storage/search/runtime.py
  • polylogue/storage/session_replacement.py
  • polylogue/storage/source_sessions.py
  • polylogue/storage/sqlite/archive_tiers/archive.py
  • polylogue/storage/sqlite/archive_tiers/session_annotations_write.py
  • polylogue/storage/sqlite/archive_tiers/source_write.py
  • polylogue/storage/sqlite/archive_tiers/user_audit.py
  • polylogue/storage/sqlite/archive_tiers/user_overlay.py
  • polylogue/storage/sqlite/archive_tiers/user_write.py
  • polylogue/storage/sqlite/run_projection_relations.py
  • polylogue/storage/table_existence.py
  • polylogue/storage/usage.py
  • tests/unit/cli/commands/test_status.py
  • tests/unit/daemon/test_daemon_cli.py
  • tests/unit/storage/test_embedding_contracts.py
  • tests/unit/storage/test_introspection.py

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Sinity
Sinity merged commit 3f86ec9 into master Aug 3, 2026
2 of 3 checks passed
@Sinity
Sinity deleted the feature/storage/consolidate-table-exists-introspection branch August 3, 2026 21:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant