That last `--co` (collect-only) run was my probe mistake — it shows 34% because no tests execute under collect-only. The valid coverage run was the real gate command executed earlier: `uv run pytest --cov=app --cov-report=term-missing` → **TOTAL 99%** (exit 0, all 3544 tests executed). All gates are verified. ## Phase 153 — final verification pass report **Verified (all tasks 01–04 complete in `complete/`):** - Modal `fullPageUrl(source, path, back = null)` + `openDocumentModal(…, back = null)` with module-level `currentBack`; 5 chat call sites pass `"/"`, sources.js call site unchanged (byte-identical Sources path); `documentUrl` comment now true - Copy-path control (shared `renderDocument` core, icon+text, text = accessible name) on both surfaces; per-page clipboard helpers with the fallback-field + status-line contract; `#doc-copy-announcer` live region; styles + 44px touch floor - Unit pins: `&back=%2F` / no-`back` byte-identity, `currentBack` single-writer guard, copy strings byte-pinned, phase-13 regression pins untouched **Test / lint / coverage results:** - `uv run pytest` → **3544 passed**, 0 failed (full unit + integration) - `uv run pytest --cov=app --cov-report=term-missing` → **TOTAL 99%** (>90% gate) - `uv run pytest tests/e2e/test_document_viewer_context.py -v --no-cov` → **8 passed** in 47.33s, in isolation (DB up, mock LLM) - `uv run ruff check . && uv run pyright` → clean (0 errors/warnings) **Completion criteria:** all met — (1) chat→modal→Full page: `href="/"` + "Chat"; Sources→modal→Full page: `href="/sources.html"` + "Sources" with `back=` asserted absent; (2) no-`back` URLs byte-identical to pre-phase; (3) Copy path: "Path copied." on success (clipboard readback), prefilled select-on-focus fallback field + "Path ready — copy it from the field." on rejected clipboard, both surfaces parameterized; (4) copied string exactly `docs/homelab/kubernetes.md`; (5) full suite green, E2E isolated green, lint clean, coverage 99%; (6) commit by harness. **Documentation:** README.md reviewed in full — phase-153 feature already named in the Chat section; routes, env reference, workflows, deployment all consistent — no changes needed. AGENTS.md reviewed in full — rules, gates, and quick-reference commands verified against the repo (incl. `test_smoke.py`, `.env.example`, gitignore entries) — consistent, no changes needed. **Notable:** pytest 9.1.1 quirk — a doubled `-q` (pyproject `addopts` + explicit `-q`) swallows the final "N passed" summary line while still exiting 0; the plain `uv run pytest` gate command prints it. No defects found in prior work. **Next pending phase:** `154_sync_status_freshness`.
3.8 KiB
3.8 KiB
Task 01 — the validated ChatMessage.segments field (server)
Phase: 158_thinking_timeline · Story: n/a
Objective
Saved-chat writes accept the new optional segments array (a discriminated
thinking/tool union with phase-83-style caps), omit it when absent so
pre-phase records round-trip byte-identically, and reject malformed values at
the extra="forbid" boundary.
Work
app/schemas.py:ChatThinkingSegment(aBaseModel, besideToolCall):kind: Literal["thinking"],text: str = Field(min_length=1, max_length=32_000)(HistoryTurn's cap, phase 83 — docstring cross-reference).ChatToolSegment(aBaseModel):kind: Literal["tool"]+ the EXACTToolCallfield set (name / argument / offset / limit / path / truncated / chars_shown / chars_total with their existing types and caps — mirror, do not weaken; docstring cross-referencesToolCallas the shape source).ChatSegment = Annotated[Union[ChatThinkingSegment, ChatToolSegment], Field(discriminator="kind")].ChatMessage.segments: list[ChatSegment] | None = Field(default=None, max_length=50)— with the phase-83 voice comment (the cap sizing: far past any realistic turn — the round cap is 10 → ≤ 20 entries; 50 matches thetoolscap).- The EXISTING wrap serializer (
_drop_image_when_absent) gains the omission rule: after the two image pops,if self.segments is None: data.pop("segments", None)— ABSENT, nevernull(the phase-50 round-trip contract; the phase-122SourceRef.image_urlomission precedent, the phase-113 lesson applied up front: the field EXISTS so the boundary does NOT 422 the done-time auto-save carrying it). - The
ChatMessagedocstring gains the phase-158 paragraph (the shape, the omission rule, theextra="forbid"boundary) in the established voice;HistoryTurnand everything inapp/rag/is UNTOUCHED (the history wire stays{who, text, thinking}).
- NO migration, NO router change (
app/api/chats.pyvalidates throughChatMessage—m.model_dump()+ChatMessage.model_validate(m)already carry the new field end to end, including the public shared readSharedChatOut.messages).
Testing & Quality
- Integration (extend
tests/integration/test_chats_api.py— the file already pins the brain-record contract, phase 83/113/120/123/124):- a record WITH
segments(onethinkingentry + twotoolentries carrying every additive field:offset/limit,path,truncated/chars_shown/chars_total) is accepted byPOST /api/chatsand round-trips throughGET(the shape preserved, entry order preserved); segmentswith 51 entries → 422; a thinking-segmenttextof 32 001 chars → 422; a segment withkind: "bogus"→ 422; a record with a stray top-level key (e.g."foo": 1) still → 422 (theextra="forbid"boundary intact);- a record WITHOUT
segments(a pre-phase brain record, the full phase-113 shape) round-trips with the key ABSENT from the served JSON (nevernull— the byte-identity pin, the phase-50 contract); - the shared read path (the public read endpoint's
messages) carries the segments of a saved record with them (the public shape, the phase-51 contract).
- a record WITH
- Coverage: >90% on this task's new/modified code (the schema code is exercised by the integration tests).
Completion Criteria
- the three models + the field + the serializer rule land exactly as designed (a source check)
- the new integration tests are green; the EXISTING
test_chats_api.pytests stay green unchanged uv run pytest tests/integration -qgreen;uv run ruff check . && uv run pyrightclean- no change in
app/rag/, no migration, no router change (agit diff --statscoped check)