phase: 125_image_anchor_context
Build and Push Containers / build-and-push-app (push) Successful in 2m8s
Build and Push Containers / build-and-push-db (push) Failing after 1m38s

All completion criteria verified. The repository is left functional with all changes in the working tree (no commits made, per the harness protocol).

## Final report — phase 125 `image_anchor_context` (verification pass)

All 6 tasks were already complete; this pass verified every criterion and ran the full gates:

- **Migration 0023** `documents.image_context` (TEXT nullable, reversible) — applied (`alembic current` = 0023 head); round-trip test (4 passed) exercises real upgrade → downgrade 0022 → upgrade.
- **Criterion 1** (referenced image enriched + context line in retrieval): integration `test_markdown_referenced_image_gets_context_and_enriched_chunks` + E2E `test_sync_enriches_the_image_doc_with_markdown_context`, `test_chat_answer_quotes_the_context_line` (wire prompt pinned: `<document>` block leads with the one-line `Referenced in …: "alt"` line) — verified present and passing.
- **Criterion 2** (standalone image byte-identical): E2E `test_standalone_*` (NULL `image_context`, block body exactly the bare description, no `Referenced in` anywhere in the prompt) + unit `test_describe_image_without_context_is_byte_identical` — passing.
- **Criterion 3** (markdown-first: record stored on markdown doc, image picks up context later): integration `test_markdown_indexed_before_image_stores_record_and_image_picks_it_up` + post-index enrichment pass test (markdown file gone from disk) — passing.
- **Gates:** `uv run pytest` → 2938 passed, exit 0 · `uv run pytest --cov=app --cov-report=term-missing` → exit 0, TOTAL **99%** (>90%) · `uv run ruff check .` → All checks passed · `uv run pyright` → 0 errors, 0 warnings.
- **Phase E2E in isolation:** `uv run pytest tests/e2e/test_image_anchor_context.py -v --no-cov` → 4 passed.
- **Mock-LLM change regression check:** neighboring image E2E suites green in isolation — `test_smoke` (3), `test_image_documents` (16), `test_chat_image_questions` (17), `test_answer_images` (17).
- No defects found; no code changes needed. No live-infrastructure changes (local dev DB only). No commit made (harness commits).
- Next pending phase: check `.agents/phases/todo/` after the harness moves this phase.
This commit is contained in:
2026-09-27 05:53:12 -04:00
parent 62e4f6b572
commit e0307fe3ff
60 changed files with 6583 additions and 82 deletions
+88
View File
@@ -517,6 +517,28 @@ Implements just enough of the aipi surface:
marker question always gets the echo whatever the honesty gate
says. Verified 2026-09-26: no existing E2E question or fixture
file contains the phrase, so every other suite is unaffected.
- user message containing ``quote the image reference``
(``CONTEXT_LINE_TRIGGER``, phase 125 task 06 — the image-anchor
context suite ``tests/e2e/test_image_anchor_context.py``) -> the
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).
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
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
``TABLE_TRIGGER``/``HISTORY_TRIGGER`` convention), so a marker
question always gets the echo whatever the honesty gate says.
Verified 2026-09-27: no existing E2E question or fixture file
contains the phrase, so every other suite is unaffected.
Failure injection (phase 67, LLM retry, TODO.md L3) — deterministic
dead-endpoint behavior for the retry E2E suite (``tests/e2e/
@@ -1843,6 +1865,26 @@ SHOW_IMAGE_TRIGGER = "show me the picture of"
#: says.
DIAGRAM_TRIGGER = "which diagram is in your notes"
#: The phase-125 (task 06, image-anchor context, LOCKED A4) trigger
#: (case-insensitive): the mock QUOTES THE CONTEXT LINE of the seeded
#: image doc's ``<document>`` block (the same image-doc pick as
#: :data:`DIAGRAM_TRIGGER`). For an ENRICHED image doc the block
#: leads with the one-line ``Referenced in <source>/<path>: "<alt>"``
#: line (the task-05 rendering of the stored ``image_context``,
#: BEFORE the description) — quoting the block's first line proves
#: the line reached the prompt; for a non-enriched (standalone) doc
#: the block is the bare description (byte-identical pre-phase) and
#: its first line never starts with ``Referenced in `` (the mock's
#: fixed description cannot begin that way) — the mock then answers
#: that the image carries no reference line. Checked BEFORE the
#: ``DEFLECT_MODE`` branch in :func:`compose_answer` (the marker
#: lives in the user message — the TABLE/HISTORY convention), so a
#: marker question always gets the echo whatever the honesty gate
#: says. Disjoint from every other trigger phrase; verified
#: 2026-09-27: no existing E2E question or fixture file contains the
#: phrase, so every other suite is unaffected.
CONTEXT_LINE_TRIGGER = "quote the image reference"
#: The agent's ``show_image`` SUCCESS result's confirming first line
#: (``app.rag.agent``'s ``show_image`` branch — the line ``read``'s
#: result never carries): the SHOW_IMAGE flow's follow-up request
@@ -1878,6 +1920,26 @@ def _seeded_image_doc(system: str) -> tuple[str, str, str] | None:
return 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``.
An ENRICHED image doc's ``<document>`` block leads with the
one-line ``Referenced in …`` rendering (the task-05
: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.
"""
first = block_content.split("\n", 1)[0].strip()
if first.startswith("Referenced in "):
return first
return None
def _show_image_flow(body: dict[str, Any]) -> tuple[str, ...] | None:
"""Classify a phase-124 SHOW_IMAGE request (see the module
docstring). Stateless over the messages, like the other marker
@@ -2467,6 +2529,32 @@ def compose_answer(body: dict[str, Any]) -> str:
else "Your notes don't carry that diagram — there is nothing to show. "
"(Deterministic mock answer for E2E.)"
)
elif CONTEXT_LINE_TRIGGER in user.lower():
# Phase 125 (task 06, image-anchor context, LOCKED A4): the
# ZERO-READ context-line echo — the mock QUOTES THE CONTEXT
# LINE of the seeded image doc's <document> block (the block's
# FIRST line — the task-05 one-line rendering for an enriched
# doc; a standalone doc's block is the bare description, whose
# first line never matches — the "no reference line" answer
# is the byte-identity witness). Checked BEFORE the
# DEFLECT_MODE branch (the marker lives in the user message —
# the TABLE/HISTORY convention), so a marker question always
# gets the echo whatever the honesty gate says.
image_doc = _seeded_image_doc(system)
if image_doc is None:
answer = (
"Your notes don't carry that image — there is nothing to reference. "
"(Deterministic mock answer for E2E.)"
)
else:
context_line = _seed_context_line(image_doc[2])
answer = (
f"The notes reference the image thus: {context_line} "
"(Deterministic mock answer for E2E.)"
if context_line is not None
else "The image stands alone — the notes carry no reference line for it. "
"(Deterministic mock answer for E2E.)"
)
elif "DEFLECT_MODE" in system:
answer = (
"Ah — I haven't done anything like that, so I don't want to make stuff up! "