phase: 164_chat_error_honesty
All gates green. Report: - **Verified (phase 164, all 4 tasks already in `complete/`)**: `HistoryTurn`/`ChatMessage`/`ChatThinkingSegment` caps → 600 000, `history`/saved-messages lists → 500, new `SavedChatCreate/Update` total-8 000 000-char validator; exported `errorDetailText` at the two previously unguarded chat paths (pre-stream `!res.ok` + SSE `error` frame); `FAILED_REQUEST_TEXT` + `bubbleText` threading + phase-114 banner hint; dedicated E2E `tests/e2e/test_chat_error_honesty.py`. - **Final-pass additions**: `tests/integration/test_chat_api.py::test_history_rejects_one_char_over_the_text_cap` (endpoint-level >600k → 422 with the pydantic list-of-objects `msg` — the criterion's upper half was only unit-pinned); README env rows for `BOR_APP_VERSION` + `BOR_SESSION_COOKIE`; restored `test_document_viewer.py`'s tracked screenshot artifact dirtied by the E2E run (via read-only `git show`, no index/history touched). - **Tests/lint (exact commands)**: `uv run pytest --cov=app --cov-report=term-missing` → **3837 passed**, `app/` coverage **99%** (>90%); `uv run pytest tests/e2e/test_chat_error_honesty.py -v --no-cov` → **4 passed in isolation**; per-file loop over **all 148** E2E files (`uv run pytest tests/e2e/<f> -q --no-cov`) → **148 × exit 0**, incl. `test_failed_turn_retry`, `test_chat_save_payload_limits`, `test_edit_and_resend`, `test_llm_history`, `test_smoke`; `uv run ruff check .` → All checks passed; `uv run pyright` → 0 errors. - **Completion criteria**: pre-stream 422 shows the distinct line + verbatim `msg`, `[object Object]` unrepresentable + no `input` in DOM — **met** (unit totality/source pins + E2E asserts 1–2); 525k history accepted (200 settles) / >600k → 422 / saved chat 201 + 8M bound — **met**; transport + SSE-frame settles keep `FAILED_TURN_TEXT`, stop/abort untouched, §7.4 re-enables — **met**; budget trim, `message` 4000, the 7 guarded detail-readers and all SSE shapes byte-identical (diff touches only caps + the two chat paths) — **met**; README/AGENTS full review + proxy deploy note — **met**; commit/`complete/` move = pipeline gate. - **Docs**: README reviewed in full (env tables cross-checked programmatically: 74 `BOR_*` rows vs `Settings` defaults — no mismatches; every `.env.example` var documented; referenced scripts/pages/endpoints/`query_log` columns exist) — found & fixed the 2 undocumented real knobs above; the phase's payload-limits section, failed-turn copy and `client_max_body_size ≥ 16 MB` deploy note verified accurate. `AGENTS.md` verified consistent (rules, gitignore list, quick-reference commands, referenced paths all correct) — no changes needed. - **Deviations**: `ChatThinkingSegment.text` raised 32 000 → 600 000 too (not itemized in A1, but its own docstring mirrors `HistoryTurn.thinking`; without it a long-thinking turn 422s its own auto-save — same mirror invariant). No live-infra changes made (the proxy limit lives in the separate deployment repo; README note only). - **Next pending phase**: none — `164_chat_error_honesty` was the only entry in `todo/`.
This commit is contained in:
@@ -924,9 +924,21 @@ PINNED_RUN_TURN = """function runTurn(text, { reask = false, image = null } = {}
|
||||
let detail = `Brain's API answered with HTTP ${res.status}.`;
|
||||
try {
|
||||
const body = await res.json();
|
||||
if (body.detail) detail = body.detail;
|
||||
// Phase 164 (D2): the 422 `detail` is a LIST OF OBJECTS — the
|
||||
// shared serializer yields the `msg` text, never
|
||||
// "[object Object]" and never the rejected `input`.
|
||||
detail = errorDetailText(body.detail, detail);
|
||||
} catch { /* non-JSON error body */ }
|
||||
throw new Error(detail);
|
||||
// Phase 164 (D3, locked A3/A4): a pre-stream HTTP rejection gets
|
||||
// its own honest bubble (the server ANSWERED — it did not drop),
|
||||
// and the phase-114 hint channel: the banner reassures that only
|
||||
// this send died. runTurn's catch threads bubbleText into
|
||||
// finalizeFailedTurn; hint rides setUiState's opts unchanged.
|
||||
const err = new Error(detail);
|
||||
err.bubbleText = FAILED_REQUEST_TEXT;
|
||||
err.hint =
|
||||
"The app answered — it rejected the request before the model ran. Nothing was lost but this send.";
|
||||
throw err;
|
||||
}
|
||||
await readSSE(res, (ev) => {
|
||||
if (aborted) return;
|
||||
@@ -1321,7 +1333,7 @@ PINNED_RUN_TURN = """function runTurn(text, { reask = false, image = null } = {}
|
||||
// Phase 114 (TODO L6): carry the frame's optional hint (the
|
||||
// "question too long" frame has one) through the throw — the
|
||||
// banner shows it in place of the default reachability hint.
|
||||
const err = new Error(ev.detail || "Something went wrong on my side.");
|
||||
const err = new Error(errorDetailText(ev.detail, "Something went wrong on my side."));
|
||||
err.hint = ev.hint;
|
||||
throw err;
|
||||
}
|
||||
@@ -1453,6 +1465,11 @@ PINNED_RUN_TURN = """function runTurn(text, { reask = false, image = null } = {}
|
||||
segments: buildSegments(segRuns), // phase 158 (D4): the partial's timeline
|
||||
wrap,
|
||||
leavePartialIndex,
|
||||
// Phase 164 (D3): the pre-stream HTTP rejection carries its own
|
||||
// honest bubble line (FAILED_REQUEST_TEXT); every other throw
|
||||
// (transport, SSE error frame) leaves it undefined → the
|
||||
// zero-frame settle keeps FAILED_TURN_TEXT byte-identically.
|
||||
bubbleText: err?.bubbleText,
|
||||
});
|
||||
}
|
||||
// Phase 114 (TODO L6): the SSE error frame's optional hint flows to
|
||||
@@ -1511,8 +1528,15 @@ def test_run_turn_is_pinned_at_phase_159_state() -> None:
|
||||
moves BEFORE the block append — the append's height growth must
|
||||
not leak into the "pre" reading; the first-frame edge case — a
|
||||
fresh window is always pinned; the legacy block-level pin
|
||||
capture stays, no-op'ing on the divs): the full source must
|
||||
match the pin — the ``reask`` path, the turn-local resets, the
|
||||
capture stays, no-op'ing on the divs) and phase 164 (tasks 02+03)'s
|
||||
sanctioned changes (the pre-stream ``!res.ok`` detail read goes
|
||||
through the shared ``errorDetailText`` serializer — the live 422
|
||||
list-of-objects no longer stringifies to ``[object Object]``; the
|
||||
task-03 pre-stream throw carries ``bubbleText`` (FAILED_REQUEST_TEXT)
|
||||
+ the phase-114 ``hint``, and the catch forwards ``bubbleText:
|
||||
err?.bubbleText`` into the failed-turn funnel — locked A3): the
|
||||
full source must match the pin — the ``reask`` path, the turn-local
|
||||
resets, the
|
||||
SSE contract, the save points, and the sanctioned additions (the
|
||||
task-01 ``lastUserWrap`` assignment + the idle-settle mark
|
||||
pass, the task-03 aborted-settle mark pair, the phase-160
|
||||
|
||||
Reference in New Issue
Block a user