"""documents.created_at + created_at_manual: sourced document creation dates (phase 106) Revision ID: 0020 Revises: 0019 Create Date: 2026-09-13 Phase 106 (document dates end to end: sourced at sync, displayed, editable, recency-weighted in retrieval): every ``documents`` row carries a NOT-NULL **creation date** — sourced at sync time (the git last-commit date for git sources, the file mtime for local dirs and unpacked uploads — D2) and normalized by ``app.rag.doc_dates.normalize_doc_date`` (undetermined or future → today; stored as UTC ``timestamptz`` at full precision — D3). It is DISTINCT from ``indexed_at`` (the INDEX time, untouched — the two concepts stay separate): * ``documents.created_at`` — TIMESTAMP WITH TIME ZONE NOT NULL with server default ``now()``: pre-phase-106 rows backfill to the migration moment (≈ today — the owner's instruction, D1: "the timestamp can't be null so just set it to today's date during the migration") and the next sync replaces them with the real sourced date (the importer's unchanged path, phase 106 task 04 — a sync may move a date OLDER, D4). * ``documents.created_at_manual`` — BOOLEAN NOT NULL with server default ``false``: ``true`` only while ``created_at`` is the OWNER'S correction (D1 — the ``folder_summaries.manually_edited`` phase-97 precedent). Set ONLY by ``PATCH /api/documents/date`` (phase 106 task 05); the sync-time importer SKIPS the refresh on a manual row (the correction survives syncs) and a content change RESETS both the date and the flag (a new version = a new date — D4). One additive, fully reversible migration (A13); no other schema change. """ from __future__ import annotations import sqlalchemy as sa from alembic import op revision = "0020" down_revision = "0019" branch_labels = None depends_on = None def upgrade() -> None: op.add_column( "documents", sa.Column( "created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), ) op.add_column( "documents", sa.Column( "created_at_manual", sa.Boolean(), server_default=sa.text("false"), nullable=False, ), ) def downgrade() -> None: # Both columns are the only 0020 artefacts — dropping them leaves # 0019's schema byte-identical (A13, fully reversible). op.drop_column("documents", "created_at_manual") op.drop_column("documents", "created_at")