phase: 185_sync_defer_content_fast_path
Build and Push Containers / build-and-push-app (push) Successful in 1m41s
Build and Push Containers / build-and-push-db (push) Successful in 11s

All gates green. Report:

**Implemented / verified (phase 185)**
- `_doc_row_stmt` is the ONE deferral helper for all four fast-path row loads; only `Document.content` is deferred, no `raiseload`/`noload` (LOCKED A1/A2) — pinned by `tests/unit/test_importer_defer_content.py`.
- `tests/integration/test_import_fast_path_columns.py` (5), `tests/e2e/test_fast_path_deferred_content.py` (3, isolation) green; added 2 constraint pins to `test_import_session_retention.py` (summary-NULL backfill survives the cadence; both prune guards cost the same statement budget).
- **Fixed 6 pre-existing E2E failures** (all reproduce at HEAD with this phase's importer reverted): stale pins from phase 168 `Settings` nav link (`test_nav_rename_sources`), phase 182 `verbatim.js` script count (`test_steering`), phase 175 `/api/chats?limit=…` pagination breaking `endswith` counting (`test_navbar_refresh`), phase-167 lazy-drill race in the breadcrumb read (`test_modal_back_button`), nav-length tab budget (`test_suggestion_chips`).
- **Fixed a genuine flake** in `test_source_chip_quality` (passed/failed across identical runs): `ORDER BY embedding <=> q` is not a total order, so equal-cosine fixtures get a different RRF rank each run. Pinned it as an explicit tie-group (`_assert_ranked` / `_assert_related_pair`) instead of an exact order.

**Tests / lint (all 100% green)**
- `uv run pytest --cov=app --cov-report=term-missing` → **4519 passed**, `app/rag/importer.py` **99%**, `TOTAL` **99%** (>90%).
- `uv run ruff check .` → All checks passed; `uv run pyright` → 0 errors.
- Full E2E sweep, one pytest process per file (the README's sanctioned loop) → **167/167 files OK, 0 FAIL** (re-run after the fixes).

**Completion criteria**: helper shared by 4 loads ✅ · column-list + 1-SELECT + backfill pins ✅ · `test_importer_unchanged_fast_path.py` 24 passed, zero expectation edits ✅ · full suite + coverage + lint/types ✅ · phase E2E in isolation ✅ · docs accurate ✅ · commit left to the harness (no git commands run).

**Documentation**: README + AGENTS.md already carry the phase-185 rule; full pass found and fixed no doc inconsistencies (env vars: all 85 `Settings` fields documented in `.env.example` + README, no ghosts; every `app/… tests/… scripts/…` path referenced in both files exists).

**Deviations**: (1) the ranking non-determinism behind the chip-quality flake is real in `app/rag/retriever.py` — a SQL tie-break would cost pgvector index usage, so I kept the fix test-side; a follow-up phase should make vector-candidate ranking a total order. (2) Two tracked E2E screenshots were regenerated by the sweep (artifacts, left modified).

**Next pending phase**: `186_git_log_streaming_dates`.
This commit is contained in:
2026-10-16 04:54:01 -04:00
parent 065304b52f
commit 632fb88e43
35 changed files with 2862 additions and 138 deletions
+25
View File
@@ -172,6 +172,31 @@
`tests/e2e/test_sync_session_reset.py` (the five-document prune at batch
`2` through the Sources page: both folders cleared, the survivors' chunks
intact, zero orphan chunk rows).
15. **The unchanged fast path reads metadata, never markdown:** the four
row lookups that share it (`_index_file`, `_index_pdf_file`,
`_index_one_embedded_image`, `_index_image_file`) all go through the ONE
statement `_doc_row_stmt` (`app/rag/importer.py`), which defers
`Document.content` and NOTHING ELSE (phase 185, LOCKED A1) — the row
load is the only place the fast path gets its row, and hydrating
`content` for every inspected file is what put ~252.6 MB of markdown in
front of a run that re-indexed 217 of ~27k files. Do not add a fifth
whole-entity `select(Document).where(Document.source == …, Document.path
== …)`, do not swap the `defer` for a `load_only` allowlist (a future
column would silently become lazy and resurface as an N+1), and do not
add `raiseload`/`noload` (LOCKED A2): `_unchanged_doc`'s phase-118
summary-NULL backfill genuinely reads `doc.content` and must pay one
per-row, one-time SELECT instead of raising. Pinned by
`tests/integration/test_import_fast_path_columns.py` (the captured
fast-path `SELECT` names no `documents.content`, exactly one `SELECT`
per unchanged file with a summary, exactly one PK follow-up for the
backfill), `tests/unit/test_importer_defer_content.py` (`content` is
the only deferred column, the four loads share the helper, no
`raiseload`/`noload` in the importer) and
`tests/e2e/test_fast_path_deferred_content.py` (the owner-visible
parity: the summary-NULL backfill survives the deferral with exactly
one `is_summary` chunk, an unchanged doc's viewer shows the full
stored text, a changed file re-indexes and the fast path still fires
after).
## Quick reference
```bash