phase: 147_sync_changed_only
Build and Push Containers / build-and-push-app (push) Successful in 2m26s
Build and Push Containers / build-and-push-db (push) Successful in 12s

All defaults match. Verification complete — everything is green with no defects found.

**Phase 147 final verification pass — report**

- All 3 task files found in `complete/147_sync_changed_only/` → verification pass only; no code changes needed.
- Verified: migration 0025 (`documents.file_hash` nullable String(64), no index) + model field; fast path at top of `_index_file` (streamed 1 MiB sha256 → row lookup → `_unchanged_doc`); stamping on all three branches' create/update + shared helper; pre-phase NULL-hash fallthrough; retriever/`content_hash` untouched.
- **Tests:** `uv run pytest --cov=app --cov-report=term-missing` → 3438 passed, `app/` coverage **99%** (>90% ✓); `uv run pytest tests/e2e/test_sync_changed_only.py -v --no-cov` → 3 passed in isolation ✓; `uv run ruff check . && uv run pyright` → all checks passed, 0 errors ✓; prior-phase `tests/e2e/test_sync_button.py` → 3 passed (no regression) ✓.
- **Completion criteria:** all 7 pass — (1) column + round-trip ✓ (2) unchanged text/image/garbage-PDF/pictorial-PDF cost zero LLM/extraction/vision, `summary.unchanged` counted, rows byte-still ✓ (3) changed file re-indexes + re-stamps; bytes-changed/content-same stamps only ✓ (4) backfill/D1/D4 + log wordings pinned verbatim ✓ (5) `content_hash`/retriever byte-identical ✓ (6) all gates green ✓ (7) no prior-phase behavior change ✓.
- **Documentation:** full review of README.md (1,031 lines) and AGENTS.md against the code: every `.env.example`/code `BOR_*` var documented, all config defaults match `app/config.py`, quick-start/QA commands and referenced test files exist, `file://` URL + troubleshooting claims accurate; the phase's README "Updating the documents" edit is accurate. **Verified consistent — no changes needed.**
- **Notable:** no live-infrastructure changes (dev Postgres container used for the standard test gates only). Working tree left uncommitted per harness protocol.
- **Next pending phase:** `148_pdf_pages_in_sources`.
This commit is contained in:
2026-10-01 14:23:33 -04:00
parent ab3f5f685b
commit 863519e980
27 changed files with 2442 additions and 205 deletions
+75
View File
@@ -128,6 +128,81 @@ def test_documents_unique_source_path() -> None:
assert uq, "documents must be unique on (source, path) — the upsert key"
def test_documents_file_hash_column_contract() -> None:
"""Phase 147 (task 01): ``documents.file_hash`` — the sha256 of the
RAW SOURCE FILE's bytes (the sync's change signal): VARCHAR(64),
NULLABLE (NULL = "unknown" — pre-phase rows until the first
post-upgrade sync stamps it), NO server default, and NO index
(the fast path compares it in Python after the unique
``(source, path)`` lookup — internal bookkeeping, never a query
surface). ``content_hash`` (0001) stays untouched: NOT NULL and
still indexed — the retriever's dedupe/caching identity."""
from sqlalchemy import String as SAString
documents = Base.metadata.tables["documents"]
col = documents.c["file_hash"]
assert col.nullable is True, ("file_hash must be NULLABLE — NULL is the pre-phase state")
assert isinstance(col.type, SAString), "file_hash must be String(64)"
assert col.type.length == 64, "file_hash must be String(64) — a sha256 hex digest"
assert col.server_default is None, (
"file_hash must carry NO server default — NULL is the meaningful"
" pre-phase state"
)
# No index: neither the inline flag (None = unspecified) nor any
# table-level index may cover file_hash (unlike content_hash — a
# query/exposure surface).
assert not col.index, ("file_hash must be UNINDEXED — compared in Python")
covering = [
idx
for idx in documents.indexes
if any(c.name == "file_hash" for c in idx.columns)
]
assert not covering, "no table-level index may cover file_hash"
uq_covering = [
c
for c in documents.constraints
if isinstance(c, UniqueConstraint) and any(col.name == "file_hash" for col in c.columns)
]
assert not uq_covering, "no unique constraint may cover file_hash"
# content_hash is untouched: still NOT NULL and indexed.
content = documents.c["content_hash"]
assert content.nullable is False, ("content_hash must stay NOT NULL (0001)")
assert content.index is True, "content_hash must stay INDEXED (0001)"
content_covering = [
idx for idx in documents.indexes if any(c.name == "content_hash" for c in idx.columns)
]
assert content_covering, "the content_hash index must survive phase 147"
def test_document_file_hash_orm_defaults_to_none() -> None:
"""A freshly constructed ``Document`` (no DB) exposes ``file_hash``:
the omitted kwarg and explicit ``None`` read NULL (the pre-phase
shape — NULL is backfilled with a real digest only by the first
post-upgrade sync), and a set digest round-trips as-is (the real
DB round-trip is pinned in ``test_migration_0025.py``)."""
from app.models import Document
base = dict(
source="s",
path="p.md",
full_path="/tmp/s/p.md",
title="p",
content="c",
content_hash="1" * 64,
)
stamped = Document(**base, file_hash="a" * 64)
assert stamped.file_hash == "a" * 64
explicit_null = Document(**base, file_hash=None)
assert explicit_null.file_hash is None
omitted = Document(**base)
assert omitted.file_hash is None, (
"an omitted file_hash must read NULL (the pre-phase shape)"
)
def test_doc_drafts_token_is_unique_not_null() -> None:
"""Phase 59: the draft's URL credential — an unguessable uuid4,
UNIQUE + NOT NULL (no "un-drafted" state, unlike the NULLable