phase: 185_sync_defer_content_fast_path
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:
@@ -1,55 +0,0 @@
|
||||
# Phase 185 — The unchanged fast path stops fetching 15 KB of markdown per file
|
||||
|
||||
**Source:** owner incident 2026-10-15 (as phases 183/184) — every one of the ~27k files the walk inspected hydrated its `Document` row, `content` included, and `expire_on_commit=False` kept it resident for the whole run.
|
||||
**Story:** n/a (incident-derived, owner directive 2026-10-15)
|
||||
**Depends on:** none functionally; sequenced after `183`/`184` because it touches the same walk and reuses their statement-capture technique.
|
||||
|
||||
**Context:** `app/rag/importer.py` — `_index_file` (**L1384**; the row load **`doc = session.scalar(select(Document).where(Document.source == source, Document.path == rel))` at L1452** — the ONLY place the fast path gets its row, `_file_hash` streamed digest at L1451, the fast-path guard at L1460–1469, the three sibling loads at **L1914** (PDF), **L2675** (embedded image), **L3674** (image) which the phase-147 fast path shares), `_row_kind_mismatch` (**L1352** — reads `doc.is_image` only), `_unchanged_doc` (**L1061** — `doc.file_hash = file_digest` at L1137, the **phase-118 summary-NULL backfill `if doc.summary is None: await _store_summary(..., content=doc.content ...)` at L1139–1142**, `_regate_strip` at L1145–1157 which reads `doc.page_count` + `doc.path` + `doc.full_path` for a `.pdf` with a strip, the D1 manual-date lock at L1163, the D4 date refresh at L1172), `_store_summary` (L1595), `ImportSummary.log()` (L520); `app/db.py:30` (`expire_on_commit=False`, unchanged); `tests/unit/test_importer_unchanged_fast_path.py` (the phase-147 matrix, whose docstring already pins "the phase-118 backfill on the fast path runs from the STORED `doc.content`"), `tests/unit/test_importer.py`, `tests/integration/test_importer.py`, `tests/integration/test_importer_e2e.py`, `tests/e2e/test_document_viewer.py` + `tests/e2e/test_document_summaries.py` (the surfaces that render stored `content` and summaries), `tests/e2e/test_sync_changed_only.py`.
|
||||
|
||||
## Measured baseline (2026-10-15, incident box)
|
||||
`documents`: 17,407 rows, `avg(length(content)) = 15,217` chars, **252.6 MB of markdown total**. `_index_file` loads the whole row for every file it inspects — the incident run inspected ~27k files and re-indexed **217**, so the run paid ~250 MB of markdown resident (`expire_on_commit=False`) to discover that 99% of the KB had not changed.
|
||||
|
||||
## Objective
|
||||
Keep the phase-147 unchanged fast path's behaviour exactly as it is and make it stop hydrating `Document.content`: the row load defers the one column the fast path does not read, so a full walk's memory scales with row metadata instead of markdown — while the phase-118 summary-NULL backfill (the one fast-path branch that genuinely needs the stored content) keeps working from the stored text.
|
||||
|
||||
## Design (shared by all tasks — the executor reads this, not the chat)
|
||||
- **D1 — `defer(Document.content)`, not a `load_only` allowlist (task 02).** The surgical form: `select(Document).options(defer(Document.content)).where(...)` at the row loads. A `load_only` allowlist would defer every column not named — including columns added by a future phase, which is how a "memory fix" turns into a mysterious N+1 six phases later. `defer()` names exactly what is dropped, so everything else keeps today's semantics, and pyright/the suite stay honest. Applies to the shared row load at **L1452** and the three sibling loads that share the phase-147 fast path — **L1914** (PDF), **L2675** (PDF-embedded image), **L3674** (image) — via ONE module-level helper so the four sites cannot drift: `_DOC_ROW_OPTS: tuple[Callable[[str], Any], ...] = (defer(Document.content),)` (or a tiny `_doc_row_stmt(source, rel)` helper returning the prepared `select` — the executor picks one, and a unit pin asserts every fast-path load uses it).
|
||||
- **D2 — NO `raiseload`.** `noload`/`raiseload` would break the phase-118 backfill (L1139–1142) and any future legitimate reader. With plain `defer`, reading `doc.content` emits one lazy SELECT for that row and behaves exactly as today. The trade is explicit: unchanged files with a stored summary pay ZERO extra queries; a summary-NULL backfill pays ONE extra SELECT, which is a per-row, one-time, already-rare cost. The docstring says so.
|
||||
- **D3 — the pin is the statement's column list, deterministic (task 01).** `tests/integration/test_import_fast_path_columns.py` captures SQL through `before_cursor_execute` (the phase-183 task 01 technique) and asserts, for an unchanged re-sync of a seeded KB: (a) the documents SELECT issued by `_index_file` does **not** mention `documents.content` in its select list — PRE-fix that assertion FAILS, so task 01 writes it as the *inverse* assertion (content IS present, green pre-fix) and task 02 flips it, exactly the 179 pattern; (b) exactly ONE documents SELECT per file (no lazy follow-up) for a doc WITH a summary; (c) for a doc with `summary IS NULL`, the backfill still runs and emits exactly ONE extra documents SELECT (the lazy `content` load) and produces the same summary chunk as today.
|
||||
- **D4 — observable parity is non-negotiable.** Every count (`files/added/updated/unchanged/dates_updated`), every log word (`import: unchanged …`, `import: date-refreshed …`, `import: summary …`), the D1 manual-date lock, the phase-163 PDF strip re-gate and the phase-118 backfill result stay byte-identical — this phase changes WHICH COLUMNS travel, never what the run does. `tests/unit/test_importer_unchanged_fast_path.py` must go green with **no expectation edits** (if an expectation must change, the design is wrong — stop and flag).
|
||||
- **D5 — the E2E pins the two surfaces `content` feeds (task 03).** `tests/e2e/test_fast_path_deferred_content.py`: (1) a doc whose `summary` is NULL in the DB (the test nulls it directly, the way phase 118's tests do) is re-synced from the Sources page and its summary comes back from the STORED content (the document page's summary renders + the summary chunk exists + `dates_updated`/counts stay honest); (2) a normal unchanged doc's viewer still shows the FULL stored text (no truncation, no blank) after a sync that ran with the deferral; (3) a changed file re-indexes and its viewer shows the NEW text. `E2E_REAL_LLM=1` skipped.
|
||||
- **NOT touched:** `app/db.py:30`, the walk itself (`iter_importable_files`, L624 — its `sorted(root.rglob("*"))` materialisation is a separate, much smaller cost and stays), `_unchanged_doc`'s logic, `_store_summary`, the index/summary/vision pipelines, the retrieval path, the frontend, phases 183/184's files beyond the shared row-load line.
|
||||
|
||||
## Dependencies
|
||||
— (none functionally; run after `183_sync_passive_delete_cascade` and `184_sync_bounded_session` so the walk's three memory fixes land in one coherent story)
|
||||
|
||||
## Tasks
|
||||
1. `01_fast_path_statement_probe.md` — the statement-capture probe pinning that the fast-path row load hydrates `content` (PRE-fix, green now), the zero-extra-SELECT pin for a doc with a summary, and the backfill's lazy-load pin.
|
||||
2. `02_defer_content.md` — the fix: the shared `_doc_row_stmt` / `defer(Document.content)` helper wired into the four fast-path row loads, the docstrings, the probe flipped.
|
||||
3. `03_e2e_deferred_content.md` — `tests/e2e/test_fast_path_deferred_content.py` (the phase's dedicated Playwright suite), the full gates, the docs pass, the atomic commit.
|
||||
|
||||
## Testing & Quality
|
||||
- **Unit:** `tests/unit/test_importer_unchanged_fast_path.py` green with **no expectation edits** (the phase-147 matrix: zero-LLM-call, D1/D4 date rules, the phase-118 backfill from stored content); `tests/unit/test_importer.py` green.
|
||||
- **Integration:** `tests/integration/test_import_fast_path_columns.py` (the column-list + statement-count pins), `tests/integration/test_importer.py`, `test_importer_e2e.py`, `test_importer_dates.py`, `test_sync_api.py`.
|
||||
- **E2E (AGENTS rule 4, task 03):** `tests/e2e/test_fast_path_deferred_content.py`, run **in isolation**.
|
||||
- **Coverage:** **>90%** on `app/` (`uv run pytest --cov=app --cov-report=term-missing`).
|
||||
- **Lint/types:** `uv run ruff check . && uv run pyright`.
|
||||
|
||||
## Documentation & Consistency
|
||||
- Final pass reviews `README.md` and `AGENTS.md` in their entirety against the post-phase app and fixes every inconsistency found, pre-existing ones included.
|
||||
- `README.md` (sync/import operations): one sentence — the unchanged fast path reads row metadata only, so a full-resync walk's cost is metadata-bound; the rare summary-NULL backfill re-reads the stored content (one query) and that is intentional.
|
||||
- `app/rag/importer.py`: the module docstring's phase-147 fast-path paragraph and `_index_file`'s docstring gain the deferral note (with the D2 no-`raiseload` rationale), so nobody "optimises" it into a lazy-load landmine.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] The four fast-path row loads share ONE deferral helper; `Document.content` is deferred and nothing else is.
|
||||
- [ ] `uv run pytest tests/integration/test_import_fast_path_columns.py -v` green with the POST-fix column-list assertion (no `documents.content` in the fast-path SELECT), the one-SELECT-per-file pin, and the backfill's single lazy-load pin, all measured numbers in the module docstring.
|
||||
- [ ] `uv run pytest tests/unit/test_importer_unchanged_fast_path.py -v` green with zero edited expectations.
|
||||
- [ ] `uv run pytest` green, `app/` coverage >90%, ruff + pyright clean.
|
||||
- [ ] `uv run pytest tests/e2e/test_fast_path_deferred_content.py -v --no-cov` green **in isolation** (backfill from stored content, full text in the viewer, changed file re-indexes).
|
||||
- [ ] `README.md` and `AGENTS.md` accurate and consistent with the whole project (verified in the final pass).
|
||||
- [ ] One atomic `--no-gpg-sign` Conventional Commit (e.g. `perf(import): defer Document.content on the unchanged fast path so a full walk no longer hydrates the KB`); the phase dir moves to `.agents/phases/complete/`.
|
||||
|
||||
## Locked decisions (owner-confirmed 2026-10-15)
|
||||
- **A1 — `defer(Document.content)` only.** Not a `load_only` allowlist (future columns would silently become lazy), not `undefer_group`s, not a DTO/read-model rewrite of the importer (too large a change to keep phase-147 parity provable).
|
||||
- **A2 — no `raiseload`/`noload`.** The phase-118 backfill must keep reading the stored content; failing loudly there would be a behaviour regression, and the extra SELECT is per-row and one-time.
|
||||
- **A3 — the walk itself (`iter_importable_files`) is out of scope** this phase — its `sorted(root.rglob("*"))` list is a one-off per source (tens of thousands of `Path`s, ~10 MB), orders of magnitude below the markdown this phase removes.
|
||||
@@ -1,27 +0,0 @@
|
||||
# Task 01 — Pin that the fast path hydrates the markdown it never reads
|
||||
|
||||
**Phase:** `185_sync_defer_content_fast_path` · **Source:** owner incident 2026-10-15
|
||||
**Story:** n/a
|
||||
|
||||
## Objective
|
||||
A deterministic, SQL-level probe proving that the phase-147 unchanged fast path SELECTs `documents.content` for every file it inspects (green PRE-fix, task 02 flips it), that it issues exactly one documents SELECT per unchanged file, and that the phase-118 summary-NULL backfill path behaves as documented.
|
||||
|
||||
## Work
|
||||
1. `tests/integration/test_import_fast_path_columns.py` — a NEW file, house patterns (`tests/integration/test_importer_e2e.py`'s fixture-KB + fake-embedder shape; the statement capture is phase 183 task 01's: a `sqlalchemy.event` `before_cursor_execute` listener installed and removed in a fixture).
|
||||
- Fixture: 4 `.md` files (each ~8 KB so `content` is unmistakably large), seeded through the real `import_sources` so rows look production-shaped, then a SECOND `import_sources` run over the same tree (the unchanged run) with the capture active. One of the four rows gets `documents.summary = NULL` committed before the second run (the phase-118 backfill case).
|
||||
- Helpers: `doc_selects(captured)` → statements that are SELECTs `FROM documents`; `loads_content(stmt_text)` → True when the statement's select list names `content` (match on the aliased/plain column token, and state the matcher in a comment so it cannot match `content_hash`).
|
||||
- `test_unchanged_run_hydrates_content_pre_fix`: assert `len(doc_selects(captured)) >= 4` and `all(loads_content(s) for s in doc_selects(captured))` — **the PRE-fix assertion**, with the marker comment `# task 02 flips this: no content in the fast-path SELECT`. Record the captured statement text for ONE file in the docstring (the evidence).
|
||||
- `test_unchanged_file_is_one_select`: for a doc WITH a summary, the per-file documents statement count is exactly **1** (this pin must hold before AND after the fix — post-fix it is the guard against the deferral turning into a per-row lazy-load storm).
|
||||
- `test_summary_null_backfill_reads_stored_content`: for the summary-NULL row, assert the second run produced a summary chunk (`is_summary` chunk exists, its content matches the stored markdown) and, before the fix, no extra documents SELECT is needed; structure the assertion so task 02's post-fix expectation is a documented constant: `EXPECTED_EXTRA_DOC_SELECTS_FOR_BACKFILL = 0` → task 02 sets it to `1` (the single lazy `content` load) with the arithmetic in the comment.
|
||||
2. Docstring: the measured baseline (17,407 rows, avg 15,217 chars, 252.6 MB), the mechanism (`_index_file` L1452 loads the whole row; `app/db.py:30` `expire_on_commit=False` keeps it), the captured statement evidence, and the verdict **CONFIRMED — the fast path SELECTs `content` for every file inspected**.
|
||||
3. Verify: `uv run pytest tests/integration/test_import_fast_path_columns.py -v` green; `uv run pytest` green.
|
||||
|
||||
## Testing & Quality
|
||||
- Integration: the three pins above.
|
||||
- Coverage: **>90%** on `app/` (test-only task).
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] `uv run pytest tests/integration/test_import_fast_path_columns.py -v` green with the PRE-fix content-in-SELECT assertion and `EXPECTED_EXTRA_DOC_SELECTS_FOR_BACKFILL = 0`.
|
||||
- [ ] The one-SELECT-per-file pin is green and marked as must-stay-green post-fix.
|
||||
- [ ] The docstring carries the baseline numbers, the captured statement, and the **CONFIRMED** verdict.
|
||||
- [ ] `uv run pytest` green; no `app/` change in this task.
|
||||
@@ -1,53 +0,0 @@
|
||||
# Task 02 — The fix: defer `Document.content` on the fast-path row loads
|
||||
|
||||
**Phase:** `185_sync_defer_content_fast_path` · **Source:** owner incident 2026-10-15
|
||||
**Story:** n/a
|
||||
|
||||
## Objective
|
||||
Make the unchanged fast path read row metadata instead of markdown: one shared statement helper that defers `Document.content` (and nothing else), wired into the four row loads that share the phase-147 fast path, with task 01's probe flipped and zero behaviour change.
|
||||
|
||||
## Work
|
||||
1. `app/rag/importer.py` — add ONE module-level helper next to `_file_hash` (L687):
|
||||
```python
|
||||
def _doc_row_stmt(source: str, rel: str) -> Select[tuple[Document]]:
|
||||
"""The importer's single Document-row statement (phase 185).
|
||||
|
||||
``defer(Document.content)`` — the unchanged fast path (phase 147) reads
|
||||
file_hash/is_image/page_count/summary/dates, never the stored markdown,
|
||||
and app/db.py builds sessions with expire_on_commit=False, so hydrating
|
||||
content for EVERY inspected file is what took the 2026-10-15 run to
|
||||
~250 MB on a 17k-document KB (avg 15,217 chars/row). ONE column is
|
||||
deferred on purpose: a load_only allowlist would silently defer every
|
||||
column a later phase adds, and the deferred read would then surface as
|
||||
an unexplained N+1. No raiseload/noload: the phase-118 summary-NULL
|
||||
backfill (``_unchanged_doc``) genuinely reads ``doc.content`` and must
|
||||
keep working — it costs one extra SELECT for that one row.
|
||||
"""
|
||||
return (
|
||||
select(Document)
|
||||
.options(defer(Document.content))
|
||||
.where(Document.source == source, Document.path == rel)
|
||||
)
|
||||
```
|
||||
with `from sqlalchemy import select` already present and `from sqlalchemy.orm import defer` added (import style: match the module's existing import block).
|
||||
2. Replace the four identical fast-path row loads with `session.scalar(_doc_row_stmt(source, rel))`:
|
||||
- `_index_file` **L1452** (the text/branch dispatch),
|
||||
- `_index_pdf_file` **L1914**,
|
||||
- `_index_one_embedded_image` **L2675** (the `image_doc` variant — same statement shape, different variable name),
|
||||
- `_index_image_file` **L3674**.
|
||||
Keep every surrounding line untouched (`file_digest` computed first, the guard, the branch bodies). Grep for any OTHER `select(Document).where(Document.source ==` in the importer to confirm no fifth site exists; if one does (e.g. the post-passes at L3241/L3391 select differently shaped rows), leave it and say so in the task note — those are NOT fast-path loads.
|
||||
3. Docstrings: the module docstring's phase-147 paragraph and `_index_file`'s docstring (L1440–1470 region) gain the deferral sentence; `_unchanged_doc`'s step-3 comment (the phase-118 backfill) gains "…which is the one fast-path read that pulls the deferred `content` back — one SELECT, one time, by design".
|
||||
4. `tests/integration/test_import_fast_path_columns.py` — flip: `EXPECTED_CONTENT_IN_FAST_PATH_SELECT = False` (assert NO `content` token in the fast-path documents SELECT list) and `EXPECTED_EXTRA_DOC_SELECTS_FOR_BACKFILL = 1`; keep the one-SELECT-per-file pin green; docstring `CONFIRMED → FIXED (phase 185, defer(Document.content))` with the post-fix captured statement text (the evidence that markdown no longer travels).
|
||||
5. `tests/unit/test_importer_unchanged_fast_path.py` — must go green **without editing an expectation**. If a unit test stubs the session (no real lazy load possible) and fails on the deferral, fix the TEST HARNESS honestly (e.g. the fake returns a real `Document` instance, which already has `content` set) rather than weakening a pin — and say in the task note which harness detail was adjusted.
|
||||
6. Verify: `uv run pytest tests/integration/test_import_fast_path_columns.py tests/unit/test_importer_unchanged_fast_path.py -v`; then `uv run pytest tests/integration/test_importer.py tests/integration/test_importer_e2e.py tests/integration/test_importer_dates.py tests/integration/test_import_extensions_env.py tests/integration/test_sync_api.py -v`; then `uv run pytest`.
|
||||
|
||||
## Testing & Quality
|
||||
- Integration: the flipped column pin, the one-SELECT pin, the single-lazy-load backfill pin.
|
||||
- Unit: the phase-147 matrix green, zero edited expectations.
|
||||
- Coverage: **>90%** on `app/`.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] `_doc_row_stmt` exists and all four fast-path loads use it (grep evidence in the task note); only `Document.content` is deferred.
|
||||
- [ ] `uv run pytest tests/integration/test_import_fast_path_columns.py -v` green post-fix with both flipped constants and the post-fix captured statement in the docstring.
|
||||
- [ ] `uv run pytest tests/unit/test_importer_unchanged_fast_path.py -v` green with zero edited expectations (any harness adjustment documented).
|
||||
- [ ] `uv run pytest` green, `app/` coverage >90%, ruff + pyright clean.
|
||||
@@ -1,32 +0,0 @@
|
||||
# Task 03 — E2E: the deferred column still shows up where the owner reads it (the phase's dedicated Playwright suite)
|
||||
|
||||
**Phase:** `185_sync_defer_content_fast_path` · **Source:** owner incident 2026-10-15
|
||||
**Story:** n/a
|
||||
|
||||
## Objective
|
||||
Prove through the UI that the deferral is invisible to the owner: the phase-118 summary-NULL backfill still produces a summary from the STORED content, an unchanged document's viewer still renders its full stored text, and a changed file re-indexes to the new text. Then the full gates, the docs pass, the atomic commit.
|
||||
|
||||
## Work
|
||||
1. `tests/e2e/test_fast_path_deferred_content.py` — a NEW module, mock-only (`E2E_REAL_LLM=1` skipped), the `tests/e2e/test_document_summaries.py` / `test_document_viewer.py` / `test_sync_changed_only.py` house shapes (dedicated module app + port, mock LLM, dedicated `BOR_SOURCES_DIR`, the shared E2E Postgres truncated before AND after each test, admin login via `tests/e2e/auth_helpers.py`).
|
||||
- **Test 1 `test_summary_null_backfill_survives_the_deferral`:** seed+sync a fixture with 2 files, then set `documents.summary = NULL` for one row directly (the way phase 118's tests do), re-sync from the Sources page → the run reports `unchanged` (not `added`/`updated`) and the document page shows a non-empty summary; the `is_summary` chunk exists for that document; exactly one summary chunk (no duplicate).
|
||||
- **Test 2 `test_unchanged_document_viewer_shows_full_stored_text`:** re-sync without touching the files → open the document view and assert the rendered text equals the stored `content` (the sentinel phrase at the START and the END of the file both present, no truncation, no empty body) — the deferral must not have made the viewer's source row thinner.
|
||||
- **Test 3 `test_changed_file_reindexes_under_the_deferral`:** rewrite one file on disk (new sentinel + changed length), re-sync → the run reports `updated=1`, the viewer shows the NEW text and no stale fragment, the chunk count changes, and a third sync reports all `unchanged` (the fast path still fires after a deferral-mediated write).
|
||||
- Module docstring: the incident reference, the deferral (`defer(Document.content)`, one column, no `raiseload` — A1/A2), the test→observable mapping, the run-in-isolation command, prerequisites (`podman compose up -d db`).
|
||||
2. **Docs pass:** `README.md` + `AGENTS.md` reviewed in full against the post-phase app; every inconsistency fixed, pre-existing included. `README.md` gains the one-sentence operations note (a full re-sync walk is metadata-bound; the summary-NULL backfill intentionally re-reads the stored content once).
|
||||
3. **The full gates:**
|
||||
- `uv run pytest tests/e2e/test_fast_path_deferred_content.py -v --no-cov` — green **in isolation**.
|
||||
- `uv run pytest tests/e2e/test_document_dates.py -v --no-cov` and `uv run pytest tests/e2e/test_sync_changed_only.py -v --no-cov` — green in isolation (the fast path's UI neighbours).
|
||||
- `uv run pytest --cov=app --cov-report=term-missing` — full suite green, `app/` coverage **>90%**.
|
||||
- `uv run ruff check . && uv run pyright` — clean.
|
||||
4. **Commit + move:** one atomic `--no-gpg-sign` Conventional Commit — e.g. `perf(import): defer Document.content on the unchanged fast path so a full walk no longer hydrates the KB` — covering `app/rag/importer.py`, the integration + E2E files, the docs and `.agents/`. Move the phase dir to `.agents/phases/complete/185_sync_defer_content_fast_path/`.
|
||||
|
||||
## Testing & Quality
|
||||
- E2E: the dedicated suite + the two regression E2E families in isolation.
|
||||
- Coverage: **>90%** on `app/`; ruff + pyright clean.
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] `uv run pytest tests/e2e/test_fast_path_deferred_content.py -v --no-cov` green **in isolation** (backfill, full text, changed-file re-index).
|
||||
- [ ] `tests/e2e/test_document_dates.py` and `tests/e2e/test_sync_changed_only.py` green in isolation.
|
||||
- [ ] `uv run pytest --cov=app --cov-report=term-missing` green with `app/` coverage >90%; ruff + pyright clean.
|
||||
- [ ] `README.md` and `AGENTS.md` accurate and consistent with the whole project.
|
||||
- [ ] One atomic `--no-gpg-sign` Conventional Commit; the phase dir moved to `.agents/phases/complete/`.
|
||||
Reference in New Issue
Block a user