phase: 114_embed_question_length
All verification passes complete — the phase was already fully implemented in the working tree, and every gate is green. No defects found; no code changes were needed.
**Final verification pass — Phase 114 (embed question length):**
- Verified truncation: `chat.py:459` embeds `request.message[:settings.embed_question_max_chars]` (default 1200, `BOR_EMBED_QUESTION_MAX_CHARS`, `>0` validator); full question still reaches the LLM prompt/log.
- Verified error mapping: `EmbeddingInputTooLargeError(EmbeddingError)` (byte-identical message) caught **before** `EmbeddingError` → no retry, terminal frame `detail="Question too long — trim it and re-ask."` + reachability-fine hint; `ChatErrorEvent.hint` additive.
- Verified frontend chain: frame `hint` → `err.hint` → `setUiState(error, …, {hint})` → `showErrorBanner(…, {retryable: true})` — hint replaces default `ERROR_HINT`, phase-111 `#banner-retry` button revealed. 4,000-char clamp untouched (A2).
- `uv run pytest tests/unit/test_embed_question_length.py -v --no-cov` → 21 passed
- `uv run pytest tests/e2e/test_embed_question_length.py -v --no-cov` (isolation, DB up) → 1 passed (4,000-char question → done, no banner)
- Regression: `test_llm_retry.py` 4 passed · `test_oneshot_llm_retry.py` 2 passed · `test_chip_sizing_question_cap.py` 6 passed
- `uv run pytest --cov=app --cov-report=term-missing` → 2444 passed, TOTAL **99%** (>90% gate)
- `uv run ruff check .` → All checks passed; `uv run pyright` → 0 errors, 0 warnings
**Completion criteria:** (1) 4,000-char question embeds prefix + full prompt ✅ · (2) too-large → accurate frame + hint + Retry button ✅ · (3) reachability failure byte-identical (retries + old copy) ✅ · (4) all gates green ✅ · (5) commit/phase-move → left to the harness per instructions (no `git add`/`commit` run).
**Deviations:** none. **Next pending phase:** `115_doc_draft_discard`.
This commit is contained in:
@@ -668,9 +668,11 @@ def test_chat_mid_stream_failure_yields_error_after_partial_deltas(client, db) -
|
||||
def test_error_event_matches_contract_shape(
|
||||
client, db, seeded_kb, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""The SSE error event (PLAN §4) is exactly ``{type, detail}`` — the
|
||||
client's loading-feedback state machine (phase 06) keys off this shape
|
||||
to flip to the error state and re-enable the send button.
|
||||
"""The SSE error event (PLAN §4) is exactly ``{type, detail, hint}`` —
|
||||
the client's loading-feedback state machine (phase 06) keys off the
|
||||
``type``/``detail`` shape to flip to the error state and re-enable
|
||||
the send button; ``hint`` (phase 114, TODO L6) is additive — present
|
||||
as ``null`` on reachability frames, old clients ignore it.
|
||||
``llm_retries=0`` keeps this a single-attempt turn: the contract under
|
||||
test is the error frame itself, not the phase-67 retry loop."""
|
||||
broken = FakeRagLLM(embed_error=EmbeddingError("embeddings endpoint down"))
|
||||
@@ -686,9 +688,10 @@ def test_error_event_matches_contract_shape(
|
||||
|
||||
assert len(frames) == 1
|
||||
event = frames[0]
|
||||
assert set(event.keys()) == {"type", "detail"}
|
||||
assert set(event.keys()) == {"type", "detail", "hint"}
|
||||
assert event["type"] == "error"
|
||||
assert isinstance(event["detail"], str) and event["detail"]
|
||||
assert event["hint"] is None # reachability frame — no too-long hint
|
||||
|
||||
|
||||
def test_chat_db_down_returns_503_json(client, monkeypatch) -> None:
|
||||
@@ -1704,9 +1707,9 @@ def test_deflected_scaffolding_twice_settles_malformed(
|
||||
) -> None:
|
||||
"""(b) The recovery answer is scaffolding again — a second empty
|
||||
reply is terminal: the DEDICATED error frame (the exact copy), no
|
||||
``done``, no query_log row — byte-for-byte today's ``LLMError``
|
||||
terminal shape — and no third request (at most one recovery per
|
||||
turn)."""
|
||||
``done``, no query_log row — the standard ``LLMError`` terminal
|
||||
shape (phase 114: the additive ``hint`` field is ``null`` here) —
|
||||
and no third request (at most one recovery per turn)."""
|
||||
span = _scaffold_span()
|
||||
dead = FakeRagLLM(answer_sequence=[span, span])
|
||||
live = get_settings()
|
||||
@@ -1721,7 +1724,12 @@ def test_deflected_scaffolding_twice_settles_malformed(
|
||||
assert frames[0]["detail"] == (
|
||||
"The model returned a malformed reply — please try again."
|
||||
)
|
||||
assert set(frames[0].keys()) == {"type", "detail"} # the contract shape
|
||||
assert set(frames[0].keys()) == {
|
||||
"type",
|
||||
"detail",
|
||||
"hint",
|
||||
} # the contract shape (phase 114: additive hint — null here)
|
||||
assert frames[0]["hint"] is None
|
||||
assert span not in json.dumps(frames)
|
||||
assert not any(f["type"] == "done" for f in frames)
|
||||
assert db.scalars(select(QueryLog)).all() == []
|
||||
|
||||
Reference in New Issue
Block a user