phase: 113_source_chip_quality
Build and Push Containers / build-and-push-app (push) Successful in 2m2s
Build and Push Containers / build-and-push-db (push) Successful in 15s

All gates green — no defects found; this pass was verification only.

**Phase 113 final verification pass — report**

- Verified (no code changes needed): `select_documents_tiered` cited/related tiering + `select_documents` wrapper, `TurnPlan.related_docs`, `ChatDoneEvent.related` (additive, old payloads parse), `appendRelated` UI row (`.related-doc`, never `.source-chip`), done-frame + restore-path wiring, two settings with validators, `.env.example` entries
- `uv run pytest --cov=app --cov-report=term-missing` → 2422 passed, app/ coverage **99%** (>90% gate)
- `uv run pytest tests/e2e/test_source_chip_quality.py -v --no-cov` (isolated) → 2 passed
- Regression E2E `test_retrieval_quality.py` + `test_honest_deflection.py` + `test_chat_rag.py` + `test_sources_midstream_bug.py` → 17 passed
- `uv run ruff check . && uv run pyright` → clean (0 errors); `bash .agents/validate.sh` → "validation OK"

Completion criteria:
1. Single-doc question → exactly one `.source-chip` (E2E): ✅ passed
2. Weak 2nd doc only in de-emphasized related row, never `.source-chip` (unit + E2E): ✅ passed
3. Deflected turn → zero citation chips, weak hits in related row: ✅ passed
4. Full suite green, coverage >90%, isolated E2E green, lint/types clean: ✅ passed
5. `--no-gpg-sign` commit + phase dir move: left to harness per pass rules (task files already in `complete/`)

No deviations. Next pending phase: `114_embed_question_length`.
This commit is contained in:
2026-09-15 03:11:05 -04:00
parent 1374faf136
commit 97d663d16d
31 changed files with 2370 additions and 52 deletions
+48
View File
@@ -126,6 +126,29 @@ class Settings(BaseSettings):
# ``relevance_threshold`` (a floor above the threshold is a typo that
# would make every FTS hit require a HIGH cosine anyway).
lexical_support_floor: float = 0.35
#: Usefulness bar for the citation slot (phase 113, LOCKED A2): a
#: retrieved document earns ``done.sources`` (the UI's citation chip)
#: only when the **cosine** of its best hit chunk clears this floor —
#: the vector signal must corroborate the citation, mirroring the A8
#: honesty gate's ``lexical_support_floor``. Documents that scored but
#: stay below the bar are demoted to the secondary related-doc tier
#: (at most ``related_max_docs``). ``top_n_docs`` is the CEILING for
#: the cited tier, never a quota: a single strong document yields one
#: citation. Default 0.35 — the same bar as ``lexical_support_floor``;
#: tunable via ``BOR_SOURCE_USEFULNESS_FLOOR``. Must satisfy
#: ``0 <= source_usefulness_floor <= relevance_threshold`` (a floor
#: above the threshold would demote to the related tier documents the
#: gate itself calls grounded — the ``lexical_support_floor`` typo
#: guard). ``0`` disables the bar (every scored doc is citable — the
#: pre-phase behavior, the kill switch).
source_usefulness_floor: float = 0.35
#: Cap on the secondary related-doc tier (phase 113, LOCKED A4):
#: documents that scored but did not clear ``source_usefulness_floor``
#: ride the ``done`` frame's ``related`` list (the UI's de-emphasized
#: "nearby docs" row — never a citation chip). ``0`` = no related
#: docs at all (the kill switch); a negative value fails startup
#: loudly (the ``agent_max_rounds`` pattern).
related_max_docs: int = 2
#: Maximum output tokens a chat answer may use (owner instruction
#: 2026-08-22: answers must run to their natural end — the old hard
#: 700-token cap cut long answers off mid-sentence).
@@ -327,6 +350,31 @@ class Settings(BaseSettings):
)
return v
@field_validator("source_usefulness_floor")
@classmethod
def _source_usefulness_floor_bounds(cls, v: float, info: ValidationInfo) -> float:
"""The usefulness bar must be in [0, relevance_threshold]. A value
above the relevance threshold would be a typo — it would demote to
the related tier documents the honesty gate itself calls grounded
(the ``lexical_support_floor`` typo guard, phase 113)."""
if v < 0:
raise ValueError("source_usefulness_floor must be >= 0")
threshold = info.data.get("relevance_threshold")
if isinstance(threshold, float) and v > threshold:
raise ValueError(
f"source_usefulness_floor ({v}) must be <= relevance_threshold ({threshold})"
)
return v
@field_validator("related_max_docs")
@classmethod
def _related_max_docs_non_negative(cls, v: int) -> int:
"""``0`` is the no-related-docs kill switch — a negative cap is a
typo (the ``agent_max_rounds`` pattern, phase 113)."""
if v < 0:
raise ValueError("related_max_docs must be >= 0 (0 = no related docs)")
return v
@field_validator("import_extensions")
@classmethod
def _import_extensions_known(cls, v: str) -> str: