Files
brain-of-reese/alembic/versions/0020_documents_created_at.py
ducoterra ee3efb28c9
Build and Push Containers / build-and-push-app (push) Successful in 4m35s
Build and Push Containers / build-and-push-db (push) Successful in 14s
phase: 106_document_dates
Everything is verified green. Final report:

**Phase 106 — Document dates (final verification pass; all 10 tasks already complete)**

- Verified all phase artifacts: alembic `0020` (dev DB at `0020`), `doc_dates.py`, git `file_commit_dates`, importer `doc_dates_by_root`/`dates_updated`, both entry-point wirings, date APIs + tree `created_at`/`updated_at`, LLM surfaces (prompt block, `read` line 2, appended `ls` field), `apply_recency_boost` in `retrieve()`, UI columns/badge, admin editor, mock-LLM regex — all present and correct; no defects found, no fixes needed.
- `uv run pytest --cov=app --cov-report=term-missing` → **2299 passed, TOTAL 99%** (>90% ✓)
- `uv run pytest tests/e2e/test_document_dates.py -v --no-cov` → **6/6 passed** in isolation (DB up)
- 12 regression E2E suites (retrieval_quality, whole_document_context, agent_document_tools, ls_tree_drilldown, read_truncation_cap, kb_tree, kb_tree_nav, document_viewer, edit_summaries, import_documents, sync_button, hidden_folders_toggle, smoke) → **all green in isolation**
- `uv run ruff check .` → clean; `uv run pyright` → **0 errors, 0 warnings**

**Completion criteria:** 1) non-null `created_at` + 0020 upgrade/downgrade on dev DB ✓ (real-Alembic integration tests) 2) sync refresh/older/manual-persists/content-reset/no sources_meta bump ✓ 3) zip/tar mtime + future→today ✓ 4) LLM date surfaces + cross-check ✓ 5) UI Created/Updated/badge positions ✓ 6) admin editor set+revert round-trip ✓ 7) old-correct-beats-new-similar (defaults & boost-off) + near-tie + `BOR_RECENCY_BOOST=0` byte-identical ✓ 8) full gate ✓ 9) commit/phase-move — left to harness per instructions.

- **Notable:** recency default tuned 0.001 → **0.0007** (task 07 step 5 explicitly permits; measured margins recorded in `test_recency_boost.py` docstring).
- **Next pending phase:** none — `todo/` holds only this phase.
2026-09-13 19:28:05 -04:00

70 lines
2.5 KiB
Python

"""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")