phase: 137_pdf_image_context
Build and Push Containers / build-and-push-app (push) Successful in 2m51s
Build and Push Containers / build-and-push-db (push) Successful in 13s

All verification complete — everything is 100% green. Final report:

**Phase 137 final verification pass — all tasks already complete, all criteria verified**

- **Full suite:** `uv run pytest --cov=app --cov-report=term-missing` → 3272 passed, exit 0; `app/` coverage **99%** (>90% ✓)
- **Phase E2E in isolation:** `uv run pytest tests/e2e/test_pdf_image_context.py -v --no-cov` → **4 passed** (forward context, display line, standalone parity, both-blocks markdown case)
- **Pre-existing suites in isolation:** `test_pdf_support.py` + `test_image_anchor_context.py` + `test_image_documents.py` → **14 passed**; ripple-edited `test_garbage_pdf.py` → **3 passed**
- **Lint/types:** `uv run ruff check .` → all passed; `uv run pyright` → 0 errors

**Completion criteria:** (1) forward-path context in vision msg + stored `image_context`/summary — ✓ (unit builder/seam + integration fake-LLM + E2E); (2) both blocks document-first + standalone describe byte-identical — ✓ (combiner + standalone pins); (3) backfill with context + zero-vision idempotent re-sync + parent-gone origin-only — ✓ (6 integration tests); (4) suggestion block leads with `From <source>/<pdf> (page N)` — ✓ (renderer units + E2E); (5) all gates green — ✓ above; (6) no behavior change in phases 122/125/126 + sync `detail` shape untouched — ✓ (verified in code: new counter is log-only); (7) ruff+pyright — ✓; (8) commit — left to the harness per pipeline rules (working tree untouched by me).

**Documentation:** README.md audited in full — the new phase-137 bullet verified against code (6 000-char cap, log-only `image_context_backfilled=N`, `<document>`-body locator placement), all env tables/defaults cross-checked vs `app/config.py` and `.env.example` — consistent, no changes needed. AGENTS.md: every rule/command verified — consistent, no changes needed.

**Notable deviations (pre-existing, documented in task-05 report, forced by design):** `test_pdf_support.py`/`test_garbage_pdf.py` got minimal ripple edits (the "UNEDITED" pin was unsatisfiable once describe calls carry context by design); the unit `test_image_anchor_context.py` CONTEXT_PREFIX pin generalized per ASSUMPTION A5; one extra mock line (`_seed_context_line` accepts `From …`).

**Next pending phase:** none — `todo/` holds only this phase.
This commit is contained in:
2026-09-29 02:14:46 -04:00
parent 0035bdf533
commit 5e87622b30
39 changed files with 3565 additions and 167 deletions
+61 -19
View File
@@ -523,15 +523,22 @@ Implements just enough of the aipi surface:
deterministic ZERO-READ answer that QUOTES THE CONTEXT LINE of the
seeded image doc's ``<document>`` block (the same image-doc pick
as ``DIAGRAM_TRIGGER``): an ENRICHED image doc's block (phase 125
task 05, LOCKED A4) leads with the one-line
``Referenced in <source>/<path>: "<alt>"`` context line BEFORE
the description, so the mock's quote — the block's exact first
line — proves the line WAS in the prompt (and that its text is
the task-05 one-line rendering, not the stored multi-line block).
task 05, LOCKED A4; phase 137 task 04) leads with the one-line
context line BEFORE the description — the
``Referenced in <source>/<path>: "<alt>"`` reference rendering
(phase 125) or the ``From <source>/<pdf> (page N)`` provenance
rendering (phase 137 — a PDF-embedded image's block, a combined
context joining both with ``; `` in stored order) — so the
mock's quote — the block's exact first line — proves the line
WAS in the prompt (and that its text is the one-line rendering,
not the stored multi-line block).
A non-enriched (standalone) image doc's block is the bare
description (byte-identical pre-phase): its first line never
starts with ``Referenced in `` (the mock's fixed description
cannot begin that way), so the mock answers that the image
starts with ``Referenced in `` or ``From `` (the mock's fixed
description cannot begin that way — the context-aware reply
does, but only ever rides a doc whose stored context makes the
``From …`` line the block's first line), so the mock answers
that the image
carries no reference line — the byte-identity criterion's
deterministic witness. Checked BEFORE the ``DEFLECT_MODE``
branch (the marker lives in the user message — the
@@ -914,6 +921,21 @@ IMAGE_DESCRIPTION_ANSWER = (
"cable runs. Title: Homelab Network Map."
)
#: The phase-137 witness — the mock's vision reply when the
#: ``IMAGE_DESCRIPTION_MODE`` message's text part carries the
#: document-context block (its ``Extracted from …`` header). It
#: names the document + page (the context reached the model — the
#: deterministic proof) and keeps the diagram wording of
#: :data:`IMAGE_DESCRIPTION_ANSWER` so the story question's
#: token-overlap retrieval still grounds on the image doc.
IMAGE_DESCRIPTION_CONTEXT_ANSWER = (
"From report.pdf, page 2: a network diagram of the homelab "
"server room: a core router on top, a core switch in the "
"middle, and three labeled subnets at the bottom — VLAN 10 "
"office, VLAN 20 lab, and VLAN 30 storage — with a legend of "
"cable runs. Title: Homelab Network Map."
)
TABLE_ANSWER = (
"Here's the shape, in a table:\n"
"\n"
@@ -1966,21 +1988,29 @@ def _seeded_image_doc(system: str) -> tuple[str, str, str] | None:
def _seed_context_line(block_content: str) -> str | None:
"""The phase-125 context line of a seeded image doc's block
(task 06, LOCKED A4) — or ``None``.
"""The context line of a seeded image doc's block (phase 125
task 06, LOCKED A4; phase 137 task 04/05) — or ``None``.
An ENRICHED image doc's ``<document>`` block leads with the
one-line ``Referenced in …`` rendering (the task-05
one-line context rendering (the task-05/task-04
:func:`app.rag.prompts._extract_context_line` output, BEFORE the
description): its first line, verbatim, is the line the mock
quotes (the prompt-injection echo convention). A non-enriched
block is the bare description — its first line never starts with
``Referenced in `` (the mock's fixed
:data:`IMAGE_DESCRIPTION_ANSWER` cannot begin that way), so
``None`` pins the byte-identical pre-phase shape.
description): the one-line ``Referenced in …`` reference shape
(phase 125) or the one-line ``From <source>/<pdf> (page N)``
provenance shape (phase 137 — a PDF-embedded image, whose block
leads with the document-context clause, a combined context
joining both with ``; ``). Its first line, verbatim, is the line
the mock quotes (the prompt-injection echo convention). A
non-enriched block is the bare description — its first line never
starts with ``Referenced in `` or ``From `` (the mock's fixed
:data:`IMAGE_DESCRIPTION_ANSWER` begins "A network diagram" — the
context-aware :data:`IMAGE_DESCRIPTION_CONTEXT_ANSWER` begins
"From " but only ever rides a doc whose stored context makes the
``From …`` CONTEXT LINE the block's first line, so the bare
description is never the first line for it), so ``None`` pins the
byte-identical pre-phase shape.
"""
first = block_content.split("\n", 1)[0].strip()
if first.startswith("Referenced in "):
if first.startswith("Referenced in ") or first.startswith("From "):
return first
return None
@@ -2661,8 +2691,20 @@ def compose_answer(body: dict[str, Any]) -> str:
# the marker is a fixed app constant, and a real question is
# never expected to type it (the other user triggers are owner
# phrasings a question might legitimately contain — this one
# is an app-to-app marker).
answer = IMAGE_DESCRIPTION_ANSWER
# is an app-to-app marker). Phase 137 (task 05): the
# document-context trigger — when the text part carries the
# provenance header (``Extracted from `` — the importer's
# block builder, tasks 02/03), the canned reply becomes the
# context-aware :data:`IMAGE_DESCRIPTION_CONTEXT_ANSWER`
# (the deterministic witness that the context REACHED the
# vision model); the bare path (standalone images, the
# pre-137 embedded shape) stays on the bare
# :data:`IMAGE_DESCRIPTION_ANSWER` — byte-identical.
answer = (
IMAGE_DESCRIPTION_CONTEXT_ANSWER
if "Extracted from " in user
else IMAGE_DESCRIPTION_ANSWER
)
elif TABLE_TRIGGER in user.lower():
# Markdown tables (phase 44, TODO.md L6): the story E2E's
# deterministic table answer — a 3-column table, the