phase: 192_verbatim_inline_midturn
Build and Push Containers / build-and-push-app (push) Successful in 1m56s
Build and Push Containers / build-and-push-db (push) Successful in 14s

Phase 192 — final verification pass complete.

- **Verified (all 4 tasks already complete):** additive `verbatim_block` SSE frame (`VerbatimBlockPiece` → `ChatVerbatimBlockEvent` → chat dispatch, after the round's `tool`/`tool_result`, before the next round); `hydrateVerbatimNow` matched-only mid-turn pass + app.js branch; `dropPlacedTwin` per-locator done/restore dedupe; the dedicated E2E suite.
- **Root cause of the rejected attempt:** my own concurrent background `pytest` run deadlocked the harness validation run on the shared Postgres (`psycopg.errors.DeadlockDetected`). No code defect — re-ran every gate **exclusively**.
- `uv run pytest -q --cov=app --cov-report=term-missing` → **exit 0**, `TOTAL 7181 16 99%` (>90%)
- `uv run pytest tests/e2e/test_verbatim_inline_midturn.py --no-cov` → **6 passed** (isolated)
- `uv run pytest tests/e2e/test_verbatim_show_text.py tests/e2e/test_chat_history.py --no-cov` → **18 passed** (`test_chat_history.py` byte-unedited; the phase-182 suite's "no eighth type" pin was tightened, not loosened, to seven + `verbatim_block` with frame-order asserts)
- `uv run ruff check .` → All checks passed (exit 0) · `uv run pyright` → **0 errors**
- **Completion criteria:** single frame per success + zero per refusal ✅ · `BOR_ANSWER_TEXT=0` wire byte-identical (integration spelled baseline + E2E App B) ✅ · fence hydrates in place / unfenced lands at the call moment, before `done` ✅ · append+fence → 1 panel, two ranges → 2 panels ✅ · restore 1 panel/locator, `done.answer_text` + record key + `verbatim_blocks=N` unchanged ✅ · suite green, coverage 99%, lint/types clean ✅ · no-show_text turn byte-identical ✅ · commit → left to the harness (no git index/history commands run).
- **Docs:** README reviewed line-by-line (2399 lines) + AGENTS.md in full; mechanically verified all 86 `BOR_*` settings, every `.env.example` var, table defaults, internal anchors (0 broken), ports 8123/8901, tree depth 1–8, pagination 25/1–100, 17 palette colors, `0029`/`0030` migrations, and every cited test file. Fixed 1 inconsistency: the verbatim "Parity" bullet claimed the live turn/reload/shared page share "the same three globals" — now names the settle-path trio and states that `hydrateVerbatimNow` is the ONE chat-only addition (a shared page has no stream).
- **Notable:** `run_agent`'s four repeated piece unions were collapsed into the ONE exported `AgentPiece` alias, so a future piece type is spelled in one place; `test_frontend_edit_message.py`'s `runTurn` byte-pin was regenerated, not weakened.
- **Next pending phase:** `193_sync_remaining_count`.
This commit is contained in:
2026-10-19 12:19:51 -04:00
parent 4f20c256d7
commit fb17d15fbe
49 changed files with 17313 additions and 102 deletions
+195
View File
@@ -750,6 +750,22 @@ Implements just enough of the aipi surface:
for, so the client must land on its honest unresolved state (no badge,
no body, none of the model's bytes). Checked BEFORE the
``DEFLECT_MODE`` branch, same convention.
- user message containing ``walk through the stored text as you go``
(``MIDTURN_TRIGGER``, phase 192 task 04 — the mid-turn panel suite
``tests/e2e/test_verbatim_inline_midturn.py``) -> the MID-TURN turn the
phase-182 flow cannot produce: a content delta (the intro, plus the
closed fence in the fence-first arm) THEN the ``show_text`` call, and
the final answer only after :data:`MIDTURN_GAP_S` of silence — the
window in which the ``verbatim_block`` frame's panel is the only thing
on screen. Arm markers, the two ranges of one file, the sentinel token
and the byte-exact answer constants are documented at the definitions
(``MIDTURN_*``). NOT clause-gated on purpose: the kill-switch arm boots
a ``BOR_ANSWER_TEXT=0`` app with the SAME question, so the attempt
reaches the server-side ``Unknown tool.`` refusal and the wire can be
pinned byte-identical to pre-phase. Checked BEFORE the phase-182
``SHOW_TEXT_TRIGGER`` flow (disjoint phrases — the ordering
convention); no existing E2E question or fixture file carries the
phrase.
- user message containing ``which diagram is in your notes``
(``DIAGRAM_TRIGGER``, phase 124 task 07 — the BUG-FIX-ONLY
trigger, same suite) -> the deterministic ZERO-READ answer: the
@@ -1619,6 +1635,157 @@ INLINE_ANSWER = (
"And the inline markdown ends here — a plain closing line."
)
# ---------------------------------------------------------------------------
# Phase 192 (task 04): the MID-TURN show_text flow (the phase's own E2E suite)
# ---------------------------------------------------------------------------
#: A user message containing this substring (case-insensitive) drives the
#: deterministic phase-192 MID-TURN turn for
#: ``tests/e2e/test_verbatim_inline_midturn.py`` — the shape the phase-182
#: flow CANNOT produce, because that one streams the tool call with NO
#: content in front of it and answers with NO gap behind it:
#:
#: round 1 ``delta``(the intro prose — and, in the fence-first arm, the
#: closed ```bor fence itself) → a ``show_text`` tool_calls delta
#: round 2 (the server has by now sent ``tool`` + the task-01
#: ``verbatim_block`` frame) → a deliberate
#: ``MIDTURN_GAP_S`` silence → the answer's ``delta`` frames,
#: the LAST of them carrying ``MIDTURN_SENTINEL`` → ``done``
#:
#: The gap is the test's observation window: the panel the frame paints is
#: on screen while the answer has NOT streamed a single token, so
#: "the panel landed mid-turn" is pinned by the DOM itself, not by a poll.
#: Arms, discriminated by markers in the question (all disjoint from every
#: other trigger, ordering convention intact):
#
#: * default — no fence anywhere: the mid-turn block is APPENDED at the
#: answer position (``appendUnlocatedVerbatim``).
#: * ``MIDTURN_FENCE_BEFORE_MARKER`` — the fence rides round 1, so the
#: placeholder EXISTS when the frame lands: the mid-turn
#: ``hydrateVerbatimNow`` pass resolves it IN PLACE.
#: * ``MIDTURN_FENCE_AFTER_MARKER`` — the fence rides the final answer
#: (the realistic shape: the model quotes after the call), so the
#: mid-turn panel is appended and the ``done`` pass re-homes it onto
#: the fence and drops the twin (task 03's ``dropPlacedTwin``).
#: * ``MIDTURN_TWO_RANGES_MARKER`` — TWO ``show_text`` calls (the two
#: ranges the question names), no fences: two panels, in call order.
#: Stateless per the house rule (the tool-result count is the state).
#:
#: Deliberately NOT gated on ``_SHOW_TEXT_CLAUSE_MARKER``: the kill-switch
#: arm boots a ``BOR_ANSWER_TEXT=0`` app and asks the SAME question, where
#: the attempt must reach the server-side ``UNKNOWN_TOOL`` refusal (the
#: tool is not offered) so the wire can be pinned byte-identical to the
#: pre-phase baseline. The trigger phrase appears in no other suite's
#: question (verified by the suite's own grep) and no other trigger is a
#: substring of it.
MIDTURN_TRIGGER = "walk through the stored text as you go"
#: The module KB's source + path (the locator the panel captions).
MIDTURN_SOURCE = "midturnlab"
MIDTURN_PATH = "runbooks/valve-panel.md"
#: The ``show_text`` ``path`` argument: the COMBINED ``source/path`` identity
#: (``app/rag/agent.py`` resolves it through ``_resolve_path`` — a bare
#: ``path`` names a document that does not exist, the phase-72 refusal). The
#: call identity stays DERIVED, never a label (phase 191 LOCKED D2).
MIDTURN_COMBINED = f"{MIDTURN_SOURCE}/{MIDTURN_PATH}"
#: The range the single-call arms request.
MIDTURN_START, MIDTURN_END = 12, 20
#: The second call's range in the two-range arm.
MIDTURN_SECOND_START, MIDTURN_SECOND_END = 1, 10
#: Arm markers (question text only — never rendered by the app).
MIDTURN_FENCE_BEFORE_MARKER = "fence it before the call"
MIDTURN_FENCE_AFTER_MARKER = "fence it in the answer"
MIDTURN_TWO_RANGES_MARKER = "then the next block"
#: The round-1 content delta — on screen BEFORE the panel, so the suite can
#: assert the panel lands BELOW the text the model had already written.
MIDTURN_INTRO = "Let me pull the stored block up here — MIDTURN_INTRO_4412"
#: The final answer's witness token: it exists ONLY in the deltas that
#: stream after the frame, so "no sentinel in the DOM" == "the turn is live".
MIDTURN_SENTINEL = "MIDTURN_SENTINEL_7731"
#: Bytes the model re-types into a fence body — they must NEVER paint.
MIDTURN_FENCE_BODY_MARKER = "MIDTURN-FENCE-BODY-NEVER-ON-SCREEN"
#: The silence between the ``verbatim_block`` frame and the answer's first
#: delta (seconds). 1.5 s is comfortably longer than any Playwright query
#: round trip and comfortably shorter than every turn guard.
MIDTURN_GAP_S = 1.5
#: The unfenced / fence-first answer (byte-exact: the suite compares the
#: concatenated ``delta`` payloads against it on BOTH apps).
MIDTURN_PLAIN_ANSWER = (
"The panel above is the file itself, not my retyping of it.\n\n"
f"{MIDTURN_SENTINEL} — the answer kept streaming after the panel landed.\n\n"
"(Deterministic mock answer for E2E.)"
)
#: The fence-after-call answer (the dedupe arm).
MIDTURN_FENCED_ANSWER = (
"The block I pulled is quoted below.\n\n"
f"```bor {MIDTURN_COMBINED}#{MIDTURN_START}-{MIDTURN_END}\n"
f"{MIDTURN_FENCE_BODY_MARKER} — the model re-typed these bytes\n"
"```\n\n"
f"{MIDTURN_SENTINEL} — the answer kept streaming after the panel landed.\n\n"
"(Deterministic mock answer for E2E.)"
)
def _midturn_fence() -> str:
"""One CLOSED ```bor fence for the arm's primary range (the model's
own bytes inside — the placeholder drops them), padded by blank lines:
the client concatenates a mid-round ``delta`` and the next round's answer
with NO separator, so a fence that ends without its own trailing newline
has its closing ``` glued to the following word and stops being a fence
(a REAL model writes the blank line — the mock has to be as well-formed
as the thing it stands in for)."""
return (
f"```bor {MIDTURN_COMBINED}#{MIDTURN_START}-{MIDTURN_END}\n"
f"{MIDTURN_FENCE_BODY_MARKER} — the model re-typed these bytes\n"
"```\n\n"
)
def _midturn_ranges(text: str) -> list[tuple[int, int]]:
"""Every ``N-M`` line range the question names, in order (the two-range
arm carries two). Empty → the primary range."""
found = [
(int(start), int(end))
for start, end in re.findall(r"\b(\d+)\s*-\s*(\d+)\b", text)
]
return found or [(MIDTURN_START, MIDTURN_END)]
def _midturn_flow(body: dict) -> tuple[str, ...] | None:
"""The deterministic phase-192 mid-turn step (see MIDTURN_TRIGGER).
``("call", path, start, end, mid_round_delta)`` — the round's content
delta (possibly empty) followed by the scripted ``show_text`` call (the
line numbers are STRINGS, the ``_show_text_request`` convention);
``("answer", text)`` — the final answer, streamed after
``MIDTURN_GAP_S`` of silence.
"""
text = _user(body)
lower = text.lower()
if MIDTURN_TRIGGER not in lower:
return None
ranges = _midturn_ranges(text)
results = _tool_results(body)
if not results:
delta = MIDTURN_INTRO
if MIDTURN_FENCE_BEFORE_MARKER in lower:
delta = f"{MIDTURN_INTRO}\n\n{_midturn_fence()}"
start, end = ranges[0]
return ("call", MIDTURN_COMBINED, str(start), str(end), delta)
if (
MIDTURN_TWO_RANGES_MARKER in lower
and len(results) == 1
and len(ranges) > 1
):
start, end = ranges[1]
return ("call", MIDTURN_COMBINED, str(start), str(end), "")
return (
"answer",
MIDTURN_FENCED_ANSWER
if MIDTURN_FENCE_AFTER_MARKER in lower
else MIDTURN_PLAIN_ANSWER,
)
# ---------------------------------------------------------------------------
# Phase 67 (LLM retry, TODO.md L3): deterministic failure injection
# ---------------------------------------------------------------------------
@@ -5710,6 +5877,34 @@ def chat_completions(body: dict[str, Any]) -> Any:
media_type="text/event-stream",
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
)
# Phase 192 (task 04): the MID-TURN show_text turn — the round-1
# content delta (+ optionally the fence itself) then the call, the
# final answer only after MIDTURN_GAP_S of silence, so the panel
# the verbatim_block frame paints is on screen BEFORE any answer
# token. Checked BEFORE the phase-182 show_text flow (disjoint
# trigger phrases — the ordering convention); that flow's own arms
# stay byte-identical.
midturn = _midturn_flow(body)
if midturn is not None:
if midturn[0] == "call":
stream = _midround_tool_stream(
"",
midturn[4],
"show_text",
_show_text_call_arguments(midturn[1], midturn[2], midturn[3]),
"call_midturn_show",
)
else: # "answer" — the final answer, after the observation gap
stream = _sse_stream(
_apply_max_tokens(midturn[1], body.get("max_tokens")),
0.0,
pre_content_delay=MIDTURN_GAP_S,
)
return StreamingResponse(
stream,
media_type="text/event-stream",
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
)
turn_progress = _turn_progress_flow(body)
if turn_progress is not None:
stream = _turn_progress_stream(turn_progress)