Automated Schema Validation Report
Date: 2026-05-07
Result: 26 P1 schema drift issues require attention
Critical Findings (P1)
1. 11 Tables Exist Only in migrations.py — Not in schema.py
schema.py is the documented authoritative DDL home (Architectural Invariant #3: "All table DDL lives in schema.py"), but 11 production tables are created exclusively via migrations. schema.py does not represent the full database schema.
| Table |
Migration |
Feature |
agent_notifications |
agent_notifications |
NOTIF-001 |
subscription_credentials |
subscription_credentials |
SUB-001 — also FK-referenced from agent_ownership in schema.py! |
subscription_rate_limit_events |
subscription_rate_limit_tracking |
SUB-003 |
slack_workspaces |
slack_channel_agents |
SLACK-002 |
slack_channel_agents |
slack_channel_agents |
SLACK-002 |
slack_active_threads |
slack_channel_agents |
SLACK-002 |
telegram_bindings |
telegram_bindings |
TELEGRAM-001 |
telegram_chat_links |
telegram_bindings |
TELEGRAM-001 |
telegram_group_configs |
telegram_group_configs |
TGRAM-GROUP |
whatsapp_bindings |
whatsapp_bindings |
WHATSAPP-001 |
whatsapp_chat_links |
whatsapp_bindings |
WHATSAPP-001 |
The subscription_credentials case is the most severe: schema.py's agent_ownership DDL declares FOREIGN KEY (subscription_id) REFERENCES subscription_credentials(id), but subscription_credentials itself is only created via migration. If PRAGMA foreign_keys=ON is ever enabled, fresh installs would fail.
2. 14 Columns in migrations.py Not in schema.py DDL
These columns exist in every running database but are absent from the CREATE TABLE in schema.py, making it an unreliable schema reference.
agent_ownership (3 missing):
full_capabilities — migration agent_ownership_full_capabilities
max_backlog_depth — migration backlog_support
voice_system_prompt — migration agent_ownership_voice_prompt
schedule_executions (9 missing):
source_user_id, source_user_email, source_agent_name, source_mcp_key_id, source_mcp_key_name — migration execution_origin_tracking
claude_session_id — migration execution_session_tracking
queued_at, backlog_metadata — migration backlog_support
fan_out_id — migration execution_fan_out_id
agent_schedules (2 missing):
webhook_token, webhook_enabled — migration agent_schedules_webhook
Why It Works Today (but shouldn't be relied on)
init_database() uses a two-pass init: migrations run before and after init_schema(). The first pass creates migration-only tables; the second pass adds missing columns to schema.py tables. This means fresh installs function correctly — but schema.py is no longer the single source of truth, violating Invariant #3 and making the codebase harder to reason about.
Recommended Actions
- Add all 11 missing tables to
schema.py — copy their final DDL (i.e., original CREATE TABLE + any ALTER TABLE columns from later migrations combined) into the TABLES dict
- Update
schema.py DDL for agent_ownership, schedule_executions, agent_schedules — add the 14 missing columns to their CREATE TABLE definitions
- No migration needed — existing databases already have these tables/columns;
schema.py just needs to reflect reality for fresh installs
- Verify by doing a clean install and comparing
PRAGMA table_info output against schema.py
Generated by scheduled /validate-schema run
Automated Schema Validation Report
Date: 2026-05-07
Result: 26 P1 schema drift issues require attention
Critical Findings (P1)
1. 11 Tables Exist Only in migrations.py — Not in schema.py
schema.pyis the documented authoritative DDL home (Architectural Invariant #3: "All table DDL lives in schema.py"), but 11 production tables are created exclusively via migrations.schema.pydoes not represent the full database schema.agent_notificationsagent_notificationssubscription_credentialssubscription_credentialsagent_ownershipin schema.py!subscription_rate_limit_eventssubscription_rate_limit_trackingslack_workspacesslack_channel_agentsslack_channel_agentsslack_channel_agentsslack_active_threadsslack_channel_agentstelegram_bindingstelegram_bindingstelegram_chat_linkstelegram_bindingstelegram_group_configstelegram_group_configswhatsapp_bindingswhatsapp_bindingswhatsapp_chat_linkswhatsapp_bindingsThe
subscription_credentialscase is the most severe:schema.py'sagent_ownershipDDL declaresFOREIGN KEY (subscription_id) REFERENCES subscription_credentials(id), butsubscription_credentialsitself is only created via migration. IfPRAGMA foreign_keys=ONis ever enabled, fresh installs would fail.2. 14 Columns in migrations.py Not in schema.py DDL
These columns exist in every running database but are absent from the
CREATE TABLEinschema.py, making it an unreliable schema reference.agent_ownership(3 missing):full_capabilities— migrationagent_ownership_full_capabilitiesmax_backlog_depth— migrationbacklog_supportvoice_system_prompt— migrationagent_ownership_voice_promptschedule_executions(9 missing):source_user_id,source_user_email,source_agent_name,source_mcp_key_id,source_mcp_key_name— migrationexecution_origin_trackingclaude_session_id— migrationexecution_session_trackingqueued_at,backlog_metadata— migrationbacklog_supportfan_out_id— migrationexecution_fan_out_idagent_schedules(2 missing):webhook_token,webhook_enabled— migrationagent_schedules_webhookWhy It Works Today (but shouldn't be relied on)
init_database()uses a two-pass init: migrations run before and afterinit_schema(). The first pass creates migration-only tables; the second pass adds missing columns to schema.py tables. This means fresh installs function correctly — butschema.pyis no longer the single source of truth, violating Invariant #3 and making the codebase harder to reason about.Recommended Actions
schema.py— copy their final DDL (i.e., original CREATE TABLE + any ALTER TABLE columns from later migrations combined) into theTABLESdictschema.pyDDL foragent_ownership,schedule_executions,agent_schedules— add the 14 missing columns to their CREATE TABLE definitionsschema.pyjust needs to reflect reality for fresh installsPRAGMA table_infooutput againstschema.pyGenerated by scheduled /validate-schema run