From 5d408934bafbee010aa070f3588b701bb6b21cc1 Mon Sep 17 00:00:00 2001 From: Agent Zero Date: Tue, 4 Aug 2026 22:39:07 +0200 Subject: [PATCH] fix: Add Alembic migration 0104 for missing embedding + audit_log columns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two critical bugs found by API integration tests: 1. contacts.embedding (vector(768)) — ORM model updated in Phase 5.3 but plugin migration 0002_embeddings.sql was never run as Alembic migration. Also adds embedding columns to mails, companies, files, calendar_entries, tags. 2. audit_log.created_at, updated_at, deleted_at — AuditLog inherits TenantMixin which expects these columns, but they were never added to the DB table. Also adds to deletion_log. Migration uses IF NOT EXISTS checks for all columns/indexes. Alembic head: 0103 → 0104 --- ...0104_add_embedding_and_audit_timestamps.py | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 alembic/versions/0104_add_embedding_and_audit_timestamps.py diff --git a/alembic/versions/0104_add_embedding_and_audit_timestamps.py b/alembic/versions/0104_add_embedding_and_audit_timestamps.py new file mode 100644 index 0000000..8d37bb4 --- /dev/null +++ b/alembic/versions/0104_add_embedding_and_audit_timestamps.py @@ -0,0 +1,99 @@ +"""Add embedding column to contacts + timestamp columns to audit_log. + +Fixes two issues found by API integration tests: +1. contacts.embedding (vector(768)) — ORM model was updated in Phase 5.3 but + the plugin migration 0002_embeddings.sql was never run as an Alembic migration. +2. audit_log.created_at, updated_at, deleted_at — AuditLog model inherits TenantMixin + which expects these columns, but they were never added to the DB table. + +Revision ID: 0104 +""" + +from alembic import op +import sqlalchemy as sa + +revision = "0104" +down_revision = "0103" + + +def upgrade() -> None: + conn = op.get_bind() + + # 1. Add embedding column to contacts (if not exists) + result = conn.execute(sa.text( + "SELECT column_name FROM information_schema.columns " + "WHERE table_name = 'contacts' AND column_name = 'embedding'" + )) + if result.fetchone() is None: + op.execute("ALTER TABLE contacts ADD COLUMN embedding vector(768)") + op.execute( + "CREATE INDEX IF NOT EXISTS ix_contacts_embedding " + "ON contacts USING hnsw(embedding vector_cosine_ops)" + ) + + # 2. Add embedding columns to other tables (from plugin migration 0002) + for table in ["mails", "companies", "files", "calendar_entries"]: + result = conn.execute(sa.text( + f"SELECT column_name FROM information_schema.columns " + f"WHERE table_name = '{table}' AND column_name = 'embedding'" + )) + if result.fetchone() is None: + op.execute(f"ALTER TABLE {table} ADD COLUMN embedding vector(768)") + op.execute( + f"CREATE INDEX IF NOT EXISTS ix_{table}_embedding " + f"ON {table} USING hnsw(embedding vector_cosine_ops)" + ) + + # Tags use 384-dim embeddings + result = conn.execute(sa.text( + "SELECT column_name FROM information_schema.columns " + "WHERE table_name = 'tags' AND column_name = 'embedding'" + )) + if result.fetchone() is None: + op.execute("ALTER TABLE tags ADD COLUMN embedding vector(384)") + op.execute( + "CREATE INDEX IF NOT EXISTS ix_tags_embedding " + "ON tags USING hnsw(embedding vector_cosine_ops)" + ) + + # 3. Add timestamp columns to audit_log (if not exists) + for col in ["created_at", "updated_at", "deleted_at"]: + result = conn.execute(sa.text( + f"SELECT column_name FROM information_schema.columns " + f"WHERE table_name = 'audit_log' AND column_name = '{col}'" + )) + if result.fetchone() is None: + op.execute( + f"ALTER TABLE audit_log ADD COLUMN {col} " + f"TIMESTAMPTZ DEFAULT NOW()" + ) + + # 4. Add timestamp columns to deletion_log (if not exists) + for col in ["created_at", "updated_at", "deleted_at"]: + result = conn.execute(sa.text( + f"SELECT column_name FROM information_schema.columns " + f"WHERE table_name = 'deletion_log' AND column_name = '{col}'" + )) + if result.fetchone() is None: + op.execute( + f"ALTER TABLE deletion_log ADD COLUMN {col} " + f"TIMESTAMPTZ DEFAULT NOW()" + ) + + +def downgrade() -> None: + # Drop embedding columns + for table in ["contacts", "mails", "companies", "files", "calendar_entries"]: + op.execute(f"DROP INDEX IF EXISTS ix_{table}_embedding") + op.execute(f"ALTER TABLE {table} DROP COLUMN IF EXISTS embedding") + + op.execute("DROP INDEX IF EXISTS ix_tags_embedding") + op.execute("ALTER TABLE tags DROP COLUMN IF EXISTS embedding") + + # Drop timestamp columns from audit_log + for col in ["created_at", "updated_at", "deleted_at"]: + op.execute(f"ALTER TABLE audit_log DROP COLUMN IF EXISTS {col}") + + # Drop timestamp columns from deletion_log + for col in ["created_at", "updated_at", "deleted_at"]: + op.execute(f"ALTER TABLE deletion_log DROP COLUMN IF EXISTS {col}")