phase: 137_pdf_image_context
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:
@@ -1447,3 +1447,195 @@ def test_high_prompt_image_doc_without_context_is_byte_identical_to_pre_phase()
|
||||
twin = _doc("cluster-diagram.png", "A diagram of the cluster.", "cluster-diagram")
|
||||
assert build_high_prompt([img]) == build_high_prompt([twin])
|
||||
assert "Referenced in" not in build_high_prompt([img])
|
||||
|
||||
|
||||
# ---------- phase 137 (task 04, ASSUMPTION A6): the provenance clause ----------
|
||||
# The stored value of a PDF-embedded image doc (tasks 02/03) can carry
|
||||
# the ``Extracted from <source>/<pdf path> (page N):`` provenance block
|
||||
# — written FIRST (A2: provenance before purpose) — ahead of the
|
||||
# markdown reference blocks. The renderer turns that header line into
|
||||
# the one clause ``From <source>/<pdf> (page N)`` (the page-text body
|
||||
# is NEVER rendered), joins clauses in stored order, and keeps the
|
||||
# markdown-only values byte-identical (the phase-125 pin, unedited).
|
||||
|
||||
#: The exact stored provenance block shape with page text (tasks
|
||||
#: 02/03): the header + the page text (capped, stripped).
|
||||
PDF_CTX_SINGLE = (
|
||||
"Extracted from Homelab/docs/guide.pdf (page 2):\n"
|
||||
"The second page of the guide, with the diagram.\n"
|
||||
"More page text on the next line."
|
||||
)
|
||||
#: The tasks 02/03 COMBINED shape (A2): the document block first, then
|
||||
#: the markdown reference blocks, blank-line joined.
|
||||
PDF_CTX_BOTH = (
|
||||
"Extracted from Homelab/docs/guide.pdf (page 2):\n"
|
||||
"The second page of the guide, with the diagram.\n"
|
||||
"\n"
|
||||
"Referenced in Homelab/ops/runbook.md:\n"
|
||||
'- Alt text: "alt"'
|
||||
)
|
||||
|
||||
|
||||
def test_extract_context_line_pdf_block_alone() -> None:
|
||||
"""One provenance block → the clause EXACTLY: the locator + page
|
||||
number — the page-text body never renders."""
|
||||
line = _extract_context_line(PDF_CTX_SINGLE)
|
||||
assert line == "From Homelab/docs/guide.pdf (page 2)"
|
||||
assert "The second page of the guide" not in line
|
||||
assert "More page text" not in line
|
||||
assert line.count("\n") == 0 # genuinely ONE line
|
||||
|
||||
|
||||
def test_extract_context_line_pdf_block_textless_page_is_header_only() -> None:
|
||||
"""A text-less page stores the header line alone (tasks 02/03) →
|
||||
the same single clause."""
|
||||
assert _extract_context_line(
|
||||
"Extracted from Homelab/docs/guide.pdf (page 2):"
|
||||
) == "From Homelab/docs/guide.pdf (page 2)"
|
||||
|
||||
|
||||
def test_extract_context_line_pdf_locator_greedy_to_the_last_page_marker() -> None:
|
||||
"""The pdf path may itself contain spaces — ``loc`` is greedy to
|
||||
the LAST `` (page N):`` on the line."""
|
||||
assert _extract_context_line(
|
||||
"Extracted from Sources/My Docs/guide v2.pdf (page 10):"
|
||||
) == "From Sources/My Docs/guide v2.pdf (page 10)"
|
||||
|
||||
|
||||
def test_extract_context_line_pdf_block_page_text_body_is_skipped() -> None:
|
||||
"""The page-text body (multi-line, with a blank line and a
|
||||
look-alike ``Referenced in …:`` line WITHOUT the alt bullet) is
|
||||
skipped / dropped (the existing spurious-header rule) — the clause
|
||||
stays the single one."""
|
||||
text = (
|
||||
"Extracted from Homelab/docs/guide.pdf (page 2):\n"
|
||||
"Page text line one.\n"
|
||||
"\n"
|
||||
"Referenced in the appendix: it goes on like this.\n"
|
||||
"More page text after the look-alike header."
|
||||
)
|
||||
assert _extract_context_line(text) == "From Homelab/docs/guide.pdf (page 2)"
|
||||
|
||||
|
||||
def test_extract_context_line_spurious_pdf_header_in_page_text_emits_a_clause() -> None:
|
||||
"""The accepted asymmetry (documented in the docstring): a
|
||||
page-text line that LOOKS like a provenance header is NOT guarded
|
||||
(unlike the ``Referenced in`` look-alikes, which demand their
|
||||
bullet) — it emits a clause. The stored value is the context the
|
||||
importer wrote, not free model text."""
|
||||
text = (
|
||||
"Extracted from Homelab/guide.pdf (page 1):\n"
|
||||
"Page text carrying a look-alike line:\n"
|
||||
"Extracted from Somewhere/other.pdf (page 9):\n"
|
||||
"more page text"
|
||||
)
|
||||
assert _extract_context_line(text) == (
|
||||
"From Homelab/guide.pdf (page 1); From Somewhere/other.pdf (page 9)"
|
||||
)
|
||||
|
||||
|
||||
def test_extract_context_line_markdown_only_two_blocks_pin_unchanged() -> None:
|
||||
"""The phase-125 byte-identity pin (the task's shape): a
|
||||
MARKDOWN-ONLY value renders exactly as pre-task — ONE
|
||||
``Referenced in `` prefix over the joined clauses (the second
|
||||
block's empty alt renders bare); a single block is unchanged too."""
|
||||
text = (
|
||||
'Referenced in docs/a.md:\n- Alt text: "alt a"\n'
|
||||
"\n"
|
||||
'Referenced in docs/b.md:\n- Alt text: ""'
|
||||
)
|
||||
assert _extract_context_line(text) == 'Referenced in docs/a.md: "alt a"; docs/b.md'
|
||||
assert _extract_context_line('Referenced in docs/a.md:\n- Alt text: "alt a"') == (
|
||||
'Referenced in docs/a.md: "alt a"'
|
||||
)
|
||||
|
||||
|
||||
def test_extract_context_line_both_blocks_document_first() -> None:
|
||||
"""The tasks 02/03 combined shape (A2: the document block first,
|
||||
then the markdown blocks) renders the clauses IN STORED ORDER —
|
||||
each with its own prefix; the page-text body stays out."""
|
||||
line = _extract_context_line(PDF_CTX_BOTH)
|
||||
assert line == (
|
||||
'From Homelab/docs/guide.pdf (page 2); '
|
||||
'Referenced in Homelab/ops/runbook.md: "alt"'
|
||||
)
|
||||
assert "The second page of the guide" not in line
|
||||
|
||||
|
||||
def test_extract_context_line_markdown_first_then_pdf_keeps_stored_order() -> None:
|
||||
"""The reverse stored order (a markdown block BEFORE the document
|
||||
block — an owner-edited value) renders in stored order too: the
|
||||
complete reference block flushes when the provenance header
|
||||
arrives, and the mixed join gives each clause its own prefix."""
|
||||
text = (
|
||||
"Referenced in Homelab/ops/runbook.md:\n"
|
||||
'- Alt text: "alt"\n'
|
||||
"\n"
|
||||
"Extracted from Homelab/docs/guide.pdf (page 2):\n"
|
||||
"The second page of the guide."
|
||||
)
|
||||
assert _extract_context_line(text) == (
|
||||
'Referenced in Homelab/ops/runbook.md: "alt"; '
|
||||
"From Homelab/docs/guide.pdf (page 2)"
|
||||
)
|
||||
|
||||
|
||||
def test_extract_context_line_pdf_garbage_is_none() -> None:
|
||||
"""No parsable header of either kind → ``None`` (unchanged
|
||||
degradation): the provenance keyword without the exact header
|
||||
shape is not a block."""
|
||||
# No trailing colon ...
|
||||
assert _extract_context_line("Extracted from Homelab/guide.pdf (page 2)") is None
|
||||
# ... the lowercase keyword ...
|
||||
assert _extract_context_line("extracted from Homelab/guide.pdf (page 2):") is None
|
||||
# ... the missing parentheses.
|
||||
assert _extract_context_line("Extracted from Homelab/guide.pdf page 2:") is None
|
||||
|
||||
|
||||
def test_document_body_pdf_image_doc_leads_with_the_from_line() -> None:
|
||||
"""End-to-end: a PDF-embedded image doc's ``<document>`` body
|
||||
leads with the one-line rendering (provenance first, then the
|
||||
reference), a blank line, then the description — the page-text
|
||||
body never renders."""
|
||||
body = _document_body(_image_doc(image_context=PDF_CTX_BOTH))
|
||||
assert body == (
|
||||
'From Homelab/docs/guide.pdf (page 2); '
|
||||
'Referenced in Homelab/ops/runbook.md: "alt"\n'
|
||||
"\n"
|
||||
"A diagram of the cluster."
|
||||
)
|
||||
assert "The second page of the guide" not in body
|
||||
|
||||
|
||||
def test_document_body_text_doc_with_pdf_context_is_unchanged() -> None:
|
||||
"""The guard is ``is_image``: a TEXT doc carrying a provenance
|
||||
context keeps the byte-identical pre-phase body — the context
|
||||
never renders."""
|
||||
doc = _doc("guide.pdf", "CONTENT", "Guide", summary="Guide summary.",
|
||||
image_context=PDF_CTX_SINGLE)
|
||||
assert _document_body(doc) == "Guide summary."
|
||||
assert "Extracted from" not in build_high_prompt([doc])
|
||||
assert "From Homelab/docs/guide.pdf (page 2)" not in build_high_prompt([doc])
|
||||
|
||||
|
||||
def test_high_prompt_pdf_image_doc_block_exact_shape() -> None:
|
||||
"""The full block in the built prompt: the ``From`` line leads the
|
||||
body (provenance first) before the description — the pre-phase
|
||||
block shape with the line spliced in; the page-text body stays out
|
||||
of the whole prompt."""
|
||||
doc = _image_doc(image_context=PDF_CTX_SINGLE)
|
||||
prompt = build_high_prompt([doc])
|
||||
assert (
|
||||
'<document source="Homelab" path="cluster-diagram.png" '
|
||||
'title="cluster-diagram" date="2024-06-15">\n'
|
||||
"From Homelab/docs/guide.pdf (page 2)\n"
|
||||
"\n"
|
||||
"A diagram of the cluster.\n"
|
||||
"</document>"
|
||||
) in prompt
|
||||
line = "From Homelab/docs/guide.pdf (page 2)"
|
||||
block_start = prompt.index('<document source="Homelab"')
|
||||
assert prompt.index("<documents>") < prompt.index(line)
|
||||
assert prompt.index(line) < prompt.index("A diagram of the cluster.")
|
||||
assert block_start < prompt.index(line) < prompt.index("</document>")
|
||||
assert "The second page of the guide" not in prompt
|
||||
|
||||
Reference in New Issue
Block a user