phase: 142_kb_overview_freshness
All verification complete. Final report: **Phase 142 final verification pass — all green** - **Verified implemented** (working tree matches design; app diff is exactly 2 lines of logic): `app/rag/overview.py` — `regenerate_overview` now orders the lite-model document list `created_at` desc, `(source, path)` tie-break (newest docs survive the cap); `app/rag/prompts.py` — `_KB_INTRO` rewritten to the honest intro (recency bias, "map not inventory", verify-with-`ls`/`grep`; "(generated at import time)" gone). - **Completion criteria:** 1. Forced-cap outline leads with newest docs — ✅ E2E `test_kb_overview_freshness.py` test 1 (cap 300, digest from `zz_new/z.md`, no "old" tokens) + integration repro (cap 150 → `mydocs zz new 03 md zz new 03`) 2. New honest intro served in `<knowledge_base>` — ✅ unit pin (verbatim) + E2E test 2 (`build_kb_section` surface, tag/cap byte-stable) 3. Gates — ✅ `uv run pytest --cov=app --cov-report=term-missing`: **3359 passed**, `app/` TOTAL **99%** (>90%); `uv run pytest tests/e2e/test_kb_overview_freshness.py -v --no-cov`: **3 passed** in isolation; `uv run pytest tests/e2e/test_kb_overview.py -v --no-cov`: **4 passed** in isolation; `uv run ruff check . && uv run pyright`: **clean, 0 errors** 4. No behavior change in completed phases — ✅ cap env knob, change-gated sync trigger, `build_kb_section` cap/marker, deflect path all byte-identical (pinned pins pass in the 3359) - **Documentation:** README.md reviewed in full (every `.env.example` var documented with matching defaults, all endpoints/routes verified against `app/api/`, Quick Start/deploy/troubleshooting claims verified) and AGENTS.md re-reviewed (rules + quick reference accurate) — **verified consistent, no changes needed**. - **Decisions:** none required — no defects found; no live-infra changes (only the already-running local `db` container was used by tests). - **Next pending phase:** `.agents/phases/todo/143_mobile_keyboard_after_answer`.
This commit is contained in:
@@ -21,7 +21,9 @@ multi-line so the echo must pick the FIRST bullet — not the intro line,
|
||||
not a later line. The last test additionally runs the real
|
||||
``regenerate_overview`` against the mock to cover the mock's
|
||||
``KB_OVERVIEW_MODE`` generation branch end-to-end (stored outline is the
|
||||
byte-stable 8-token digest of the generator's document list).
|
||||
byte-stable 8-token digest of the generator's document list — re-pinned
|
||||
in phase 142, task 02: the generator's list is now RECENT-FIRST, so the
|
||||
digest is of the NEWEST document's line, not the lexicographic-first).
|
||||
|
||||
Test → story mapping (Playwright Mapping Rule):
|
||||
1. ``test_on_topic_answer_echoes_kb_overview``
|
||||
@@ -33,6 +35,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from threading import Thread
|
||||
from typing import Any
|
||||
@@ -47,7 +50,6 @@ from app.rag.importer import ImportSummary, import_sources
|
||||
from app.rag.llm import LLMClient
|
||||
from app.rag.overview import build_overview_prompt, regenerate_overview
|
||||
from e2e.auth_helpers import login
|
||||
from tests.e2e.mock_llm import TOKEN_RE
|
||||
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
FIXTURES = REPO / "tests" / "fixtures" / "docs"
|
||||
@@ -153,10 +155,13 @@ def _overview_row() -> KbOverview | None:
|
||||
|
||||
|
||||
def _doc_rows() -> list[tuple[str, str, str, str | None]]:
|
||||
"""The catalogue the way the generator sees it (phase 142, task 02):
|
||||
RECENT-FIRST by the document's date, ``(source, path)`` tie-break —
|
||||
the same order ``regenerate_overview`` SELECTs."""
|
||||
with SessionLocal() as db:
|
||||
rows = db.execute(
|
||||
select(Document.source, Document.path, Document.title, Document.summary)
|
||||
.order_by(Document.source, Document.path)
|
||||
.order_by(Document.created_at.desc(), Document.source, Document.path)
|
||||
).all()
|
||||
return [(source, path, title, summary) for source, path, title, summary in rows]
|
||||
|
||||
@@ -249,11 +254,38 @@ def test_mock_generated_outline_is_stored_and_echoed(
|
||||
"""The mock's ``KB_OVERVIEW_MODE`` branch is exercised end-to-end:
|
||||
the real ``regenerate_overview`` (``LLMClient`` pointed at the mock)
|
||||
stores the byte-stable 8-token digest of the generator's document
|
||||
list, and a chat turn echoes its first bullet."""
|
||||
list, and a chat turn echoes its first bullet.
|
||||
|
||||
Phase 142, task 02 (recency-first re-pin): the generator's list is
|
||||
now ``created_at`` desc, ``(source, path)`` tie-break. The fixture
|
||||
files' on-disk mtimes are checkout-dependent (git preserves none),
|
||||
so the documents' dates are pinned explicitly BEFORE the
|
||||
regeneration: every doc shares one date EXCEPT
|
||||
``homelab/tables.md`` — the lexicographic LAST path, the owner's
|
||||
phase-dir shape where the newest sorts last — which gets the newer
|
||||
one. The stored digest is therefore of THAT doc's line, not of the
|
||||
pre-fix lexicographic-first one (``deployments/new-service.md``).
|
||||
"""
|
||||
summary = _reset_db(mock_llm, seed=True, overview=None)
|
||||
assert summary is not None and summary.added == 13 # phase 47 added quadlet+j2
|
||||
assert _overview_row() is None # direct import never regenerates
|
||||
|
||||
# Pin the document dates (raw SQL — ``normalize_doc_date`` is an
|
||||
# importer concern, the ordering reads the stored column): one
|
||||
# shared date for all but the newest doc, a strictly newer date for
|
||||
# it. Without this the order — and the digest — would depend on the
|
||||
# fixture files' checkout mtimes.
|
||||
with SessionLocal() as db:
|
||||
db.execute(
|
||||
text("UPDATE documents SET created_at = :d WHERE path != :newest"),
|
||||
{"d": datetime(2026, 1, 1, tzinfo=UTC), "newest": "homelab/tables.md"},
|
||||
)
|
||||
db.execute(
|
||||
text("UPDATE documents SET created_at = :d WHERE path = :newest"),
|
||||
{"d": datetime(2026, 9, 1, tzinfo=UTC), "newest": "homelab/tables.md"},
|
||||
)
|
||||
db.commit()
|
||||
|
||||
kwargs: dict[str, Any] = {
|
||||
"_env_file": None,
|
||||
"llm_base_url": f"http://127.0.0.1:{mock_llm}/v1",
|
||||
@@ -264,11 +296,19 @@ def test_mock_generated_outline_is_stored_and_echoed(
|
||||
|
||||
row = _overview_row()
|
||||
assert row is not None and row.id == 1
|
||||
# Byte-stable expectation: the mock digests the generator's user
|
||||
# message (the same document list, the same cap) to its first 8 tokens.
|
||||
# Byte-stable expectation, re-pinned for recency-first: the mock
|
||||
# digests the generator's user message (the same document list, the
|
||||
# same cap) to its first 8 tokens — and the list now LEADS with the
|
||||
# newest doc, so the digest is of ITS line:
|
||||
# "docs — homelab/tables.md — Service Port Table — This document
|
||||
# covers …" → "docs homelab tables md service port table this"
|
||||
# (pre-fix the lexicographic-first doc was deployments/new-service.md).
|
||||
_, user = build_overview_prompt(_doc_rows())
|
||||
expected = "Knowledge base outline:\n- " + " ".join(
|
||||
TOKEN_RE.findall(user.lower())[:8]
|
||||
assert user.splitlines()[0].startswith(
|
||||
"docs — homelab/tables.md — Service Port Table"
|
||||
), "the generator's list must lead with the NEWEST doc (recency-first)"
|
||||
expected = (
|
||||
"Knowledge base outline:\n- docs homelab tables md service port table this"
|
||||
)
|
||||
assert row.content == expected
|
||||
first_bullet = row.content.splitlines()[1].removeprefix("- ").strip()
|
||||
|
||||
Reference in New Issue
Block a user