phase: 124_image_chips_and_answers
Build and Push Containers / build-and-push-app (push) Successful in 2m12s
Build and Push Containers / build-and-push-db (push) Failing after 1m39s

All green. Final report:

**Phase 124 final verification pass — all criteria verified, no defects found (nothing to fix)**
- Verified the implemented phase (all 7 tasks done, changes in working tree): `BOR_ANSWER_IMAGES` toggle, `show_image` tool + refusals + `holder.shown_images`, toggle-gated prompt clause, `answer_image` frame field (None-omitting serializer) + `ChatMessage` persistence, `merge_cited_docs` auto-cite bug fix, answer-figure UI (live/restore/shared/CSS), mock triggers + E2E suite. Also confirmed the bytes route → public change matches the phase-123 shared-chat precedent (task 06 design).
- `uv run pytest --cov=app --cov-report=term-missing` → **2848 passed**, TOTAL **99%** (>90% gate)
- `bash .agents/validate.sh` → **validation OK** (tests + coverage + ruff + pyright)
- `uv run ruff check . && uv run pyright` → clean, 0 errors
- `uv run pytest tests/e2e/test_answer_images.py -v --no-cov` (isolated) → **5/5 passed**
- Mock-inert spot checks: `tests/e2e/test_image_documents.py` 4/4, `tests/e2e/test_agent_document_tools.py` 4/4

**Completion criteria:** ① zero-read suggested-tier image doc chips with figure — verified (unit matrix, integration `test_zero_read_grounded_turn_auto_cites_suggested_image_doc`, E2E bug-fix flows); ② show_image → answer figure below text/above chips, second call refused, persists + restore + shared — verified (unit handler matrix, integration round-trip, E2E feature flow); ③ default-off byte-identical (3 tools, no clause, no key) — verified (unit + integration + E2E toggle-off); ④ full suite green / >90% / lint clean — verified above; ⑤ commit + phase move — harness responsibility (changes left uncommitted in working tree, no live-infrastructure changes made).

**Next pending phase:** none — `todo/` holds only this phase; the pipeline is clear once the harness moves it.
This commit is contained in:
2026-09-26 17:06:09 -04:00
parent 7bd6335929
commit 62e4f6b572
65 changed files with 4909 additions and 46 deletions
+17 -11
View File
@@ -1305,24 +1305,30 @@ def test_image_route_404_matrix(admin_client: TestClient, db, tmp_path: Path) ->
_cleanup_kb(db)
def test_image_route_requires_user_like_the_content_endpoint(
def test_image_route_is_public_like_the_shared_chat_content(
admin_client: TestClient, db, tmp_path: Path
) -> None:
"""Phase 79 posture (the task's "PUBLIC, like the document content
endpoint" — the content endpoint has been user-gated since phase
79; the ONLY anonymous surface is the shared chats, PLAN A10): an
anonymous caller gets 401 ``authentication required`` before any
row is read (a FRESH client — the module's fixture ``client``
stays unsigned here), a signed-in caller gets the bytes."""
"""Phase 124 (LOCKED A4 — the answer figure is "visible … on the
shared page"): the BYTES route is PUBLIC (the phase-123
chat-image precedent — the image is part of a shared chat's
rendered content, and the shared chat is the app's ONLY anonymous
surface, PLAN A10; the unguessable ``uuid4`` document id IS the
credential, no enumeration value): an ANONYMOUS caller (a FRESH
client — no session) gets the bytes with the image Content-Type,
and a signed-in caller gets the same. The document's TEXT (the
content endpoint) stays user-gated — only the bytes are public."""
_cleanup_kb(db)
try:
doc = _seed_image_doc(db, tmp_path)
anonymous = TestClient(fastapi_app)
r = anonymous.get(f"/api/documents/{doc.id}/image")
assert r.status_code == 401
assert r.json() == {"detail": "authentication required"}
# The signed-in client (admin) passes.
assert admin_client.get(f"/api/documents/{doc.id}/image").status_code == 200
assert r.status_code == 200
assert r.headers["content-type"].startswith("image/")
assert r.content == PNG_1X1
# The signed-in client (admin) gets the same bytes.
r_admin = admin_client.get(f"/api/documents/{doc.id}/image")
assert r_admin.status_code == 200
assert r_admin.content == PNG_1X1
finally:
_cleanup_kb(db)