phase: 142_kb_overview_freshness
Build and Push Containers / build-and-push-app (push) Successful in 2m33s
Build and Push Containers / build-and-push-db (push) Successful in 12s

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:
2026-10-01 04:25:03 -04:00
parent a8c0402ad2
commit aa65d2d691
30 changed files with 1738 additions and 38 deletions
+48 -8
View File
@@ -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()