phase: 182_verbatim_show_text
Build and Push Containers / build-and-push-app (push) Successful in 1m46s
Build and Push Containers / build-and-push-db (push) Successful in 12s

**Phase 182 — final verification pass (all 8 tasks already complete; this was verification + docs + one defect fix).**

- Verified: `app/rag/verbatim.py` (locator grammar, `splitlines()` slice, line-boundary cap, `resolve_answer_blocks`), `SHOW_TEXT_TOOL` + `shown_texts`, `SHOW_TEXT_CLAUSE`, `done.answer_text` (9 fields, key dropped when empty) + `ChatMessageRecord.answer_text` (≤50, 600 000-char cap counted in the 8 M budget), `verbatim.js` + 4 `{verbatim:true}` renderers, `Containerfile` minify step, `.env.example` 3 vars.
- `uv run python -c "…answer_text, show_text_max_chars, show_text_max_blocks"` → `True 12000 5`; each negative refuses startup naming its field; `0` accepted as inert ✅
- `bash .agents/validate.sh` → **validation OK**: 4320 unit+integration tests green, `TOTAL 6849 17 → 99%` (gate app/ >90% OK), `ruff check .` "All checks passed!", `pyright` 0 errors (re-run after my edits, 08:28).
- E2E, each in isolation `--no-cov`: `test_verbatim_show_text.py` 13 passed (panel bytes == `documents.content`, server caption, clipboard == server string, `/document.html?source=…&path=…`, honest unresolved state, reload+share, `show_image` regression, flag-off app) ✅; green: `test_answer_images` `test_harness_aligned_tools` `test_agent_document_tools` `test_markdown_tables` `test_markdown_inline_links_images` `test_svg_document_preview` `test_smoke` `test_document_viewer` `test_markdown_blockquotes` `test_chat_persistence` `test_thinking_timeline` `test_chat_rag` `test_settings_mobile_value_width`.
- **Defect found & fixed (pre-existing, not phase 182):** `tests/e2e/test_admin_settings_panel.py::test_admin_sees_groups_and_source_badges` failed — its whole-page `assert "reese" not in page.content()` tripped on the shell's onboarding chips, which follow the newest saved chats in the one shared E2E DB (a leftover `reeselink.json` question from a tool suite). Scoped the strict sweep to `.settings-shell` and kept a whole-page `reese:reese` credential needle → 12 passed with the leftovers still present.
- Docs: README fixed 2 inconsistencies — Quick Start omitted the **required** `BOR_ADMIN_PASSWORD`/`BOR_SESSION_SECRET` (the app fail-loud refuses boot) and the broken anchor `#chat--` → `#chat-`; full re-read of README (1816 lines) + AGENTS confirmed the phase-182 feature/settings/log/UI-contract text matches the code, and every `.env.example` var + every `Settings` field is documented.
- Notable: kept `resolve_answer_blocks` synchronous on the SSE terminal path (bounded indexed SELECTs, matching the existing `query_log` precedent and the phase's documented fence-lookup bound); no live-infra changes made.
- Next pending phase: none — `todo/` holds only 182 (its 8 task files are in `complete/`; the harness commits + moves `00_phase.md`).
This commit is contained in:
2026-10-15 08:33:12 -04:00
parent 814a400e6d
commit b34b8ca672
104 changed files with 10852 additions and 203 deletions
+128 -3
View File
@@ -9,7 +9,7 @@ The pins (00_phase, Testing & Quality):
* the auth matrix — anonymous AND a signed-in token USER get 403 on ALL
four routes (the router-wide ``require_admin`` closes the surface on
every method), the admin 200s on all four;
* the shape — ``items`` length == ``len(Settings.model_fields)`` (78),
* the shape — ``items`` length == ``len(Settings.model_fields)`` (one per field),
groups in ``GROUPS`` order (the stable order the E2E pins against),
``totals.count`` / ``overridden`` correct, ``overlay_enabled`` true;
on an empty table a field whose ``BOR_*`` var the test env sets
@@ -152,7 +152,7 @@ def test_auth_matrix_all_four_routes(client: TestClient, db: Session) -> None:
def test_catalogue_shape_and_sources_on_empty_table(admin_client) -> None:
"""The whole catalogue, grouped and stable: 78 items (exactly
"""The whole catalogue, grouped and stable: one item per field (exactly
``Settings.model_fields``), groups in ``GROUPS`` order (each group a
contiguous block, the block order = ``GROUPS``), ``totals`` correct,
``overlay_enabled`` true. On an empty table: the env-var-set field
@@ -418,7 +418,7 @@ def test_kill_switch_disables_the_mechanism_not_the_panel(
def test_catalogue_stays_under_the_byte_budget(admin_client) -> None:
"""The whole catalogue stays under the phase-167 INFO bar (64 KB —
``BOR_LOG_LARGE_RESPONSE_BYTES``), so ``GET /api/admin/settings``
never floods the log (78 items ≈ 20 KB; the audit script re-verifies
never floods the log (the whole catalogue ≈ 20 KB; the audit script re-verifies
the 256 KB budget in task 06)."""
r = admin_client.get(API)
assert r.status_code == 200
@@ -708,6 +708,131 @@ def test_llm_summary_thinking_level_row_contract(
admin_client.delete(f"{API}/llm_summary_thinking_level") # no row survives
def test_verbatim_display_rows_contract(
admin_client: TestClient, db: Session, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Phase 182 (task 01, LOCKED A8): the three verbatim-display rows are
present in the ``Agent tools`` group with the right labels and types
(``bool`` + two ``int``s, defaults ``true`` / ``12000`` / ``5``) and
behave through the generic overlay machinery:
(a) the GET rows carry group/label/type/env_var, are neither secret nor
restart-required (the chat request path reads them per turn — live);
(b) a PUT stores the RAW string in ``app_settings`` and the value
resolves live WITHOUT a restart;
(c) ``0`` is storable on both budgets — it is INERT, not an error
(``app_settings`` holds ``"0"``, the effective value is ``0``);
(d) a negative value is refused by the validate-before-store gate (A5)
with a 422 naming the field and NOTHING stored — the same rule that
refuses startup; a ``false`` for the master switch is storable
(the kill switch);
(e) a DELETE resets each to the env default;
(f) the non-admin 403 gate applies.
"""
# A deterministic base (machine-independent, the kill-switch test's
# idiom): the code defaults, no row in the table.
for var in (
"BOR_ANSWER_TEXT",
"BOR_SHOW_TEXT_MAX_CHARS",
"BOR_SHOW_TEXT_MAX_BLOCKS",
):
monkeypatch.delenv(var, raising=False)
invalidate_settings()
# (a) the rows.
r = admin_client.get(API)
assert r.status_code == 200, r.text
expect = {
"answer_text": ("Answer text", "bool", "true", "BOR_ANSWER_TEXT"),
"show_text_max_chars": (
"Show text max chars",
"int",
"12000",
"BOR_SHOW_TEXT_MAX_CHARS",
),
"show_text_max_blocks": (
"Show text max blocks",
"int",
"5",
"BOR_SHOW_TEXT_MAX_BLOCKS",
),
}
for key, (label, typ, default, envname) in expect.items():
item = _item(r.json(), key)
assert item["group"] == "Agent tools"
assert item["label"] == label
assert item["type"] == typ
assert item["env_var"] == envname
assert item["secret"] is False
assert item["masked"] is False
assert item["restart_required"] is False
assert item["overridable"] is True
assert item["value"] == default
assert item["default"] == default
assert item["source"] == "default" # env var unset, no row
assert item["has_override"] is False
assert item["db_value"] is None
# (b) PUT stores the raw string and resolves live, no restart.
for key, sent, resolved in (
("answer_text", "false", False),
("show_text_max_chars", "2000", 2000),
("show_text_max_blocks", "2", 2),
):
r = admin_client.put(f"{API}/{key}", json={"value": sent})
assert r.status_code == 200, r.text
assert r.json()["source"] == "db"
assert r.json()["has_override"] is True
assert r.json()["db_value"] == sent
assert db.execute(
text("SELECT value FROM app_settings WHERE key = :k"), {"k": key}
).scalar() == sent # stored raw, exactly as sent
assert getattr(get_settings(), key) == resolved # live, no restart
# (c) ``0`` is storable on both budgets (inert, not an error).
for key in ("show_text_max_chars", "show_text_max_blocks"):
r = admin_client.put(f"{API}/{key}", json={"value": "0"})
assert r.status_code == 200, r.text
assert r.json()["value"] == "0"
assert db.execute(
text("SELECT value FROM app_settings WHERE key = :k"), {"k": key}
).scalar() == "0"
assert getattr(get_settings(), key) == 0
# (d) a negative value is refused before anything is stored (A5) —
# the 422 names the field, the previous row is untouched.
for key in ("show_text_max_chars", "show_text_max_blocks"):
r = admin_client.put(f"{API}/{key}", json={"value": "-1"})
assert r.status_code == 422, r.text
assert key in r.json()["detail"]
assert db.execute(
text("SELECT value FROM app_settings WHERE key = :k"), {"k": key}
).scalar() == "0" # unchanged
assert getattr(get_settings(), key) == 0 # the live view is undisturbed
# (e) DELETE resets each to the env default (the vars are unset here,
# so the code default wins again).
for key, default in (
("answer_text", True),
("show_text_max_chars", 12_000),
("show_text_max_blocks", 5),
):
r = admin_client.delete(f"{API}/{key}")
assert r.status_code == 200, r.text
assert r.json()["has_override"] is False
assert r.json()["source"] == "default"
assert getattr(get_settings(), key) == default
assert db.execute(
text("SELECT count(*) FROM app_settings WHERE key = :k"), {"k": key}
).scalar() == 0
assert db.execute(text("SELECT count(*) FROM app_settings")).scalar() == 0
# (f) the non-admin 403 gate (asserted once for this family — the full
# four-route matrix is ``test_auth_matrix_all_four_routes``).
anon = TestClient(fastapi_app)
r = anon.put(f"{API}/answer_text", json={"value": "false"})
assert r.status_code == 403 and r.json() == {"detail": "admin only"}
def test_docs_token_is_write_only_masked_end_to_end(
admin_client: TestClient, db: Session, monkeypatch: pytest.MonkeyPatch
) -> None: