feat: phases 77–80 — navbar view refresh, static background, API tokens, history suggestion chips
Build and Push Containers / build-and-push-app (push) Successful in 1m45s
Build and Push Containers / build-and-push-db (push) Successful in 13s

Single consolidated commit for four completed, validated phases (77, 78,
79, 80). The pipeline run left all work uncommitted because the harness
commits only with PHASE_COMMIT=1 while child executors are forbidden from
committing; the phases themselves all passed validation and moved to
.agents/phases/complete/.

Phase 77 — navbar view refresh
- router.js dispatches bor:view-refresh on re-show / active re-click /
  popstate (gated on wasMounted; first show and boot exempt)
- History / RAG / Sources / Tuning re-fetch on refresh (admin branch);
  Chat deliberately excluded (stream survival)
- History "Refresh" button (admin-only, in-flight disable + status line)
- New story suite tests/e2e/test_navbar_refresh.py (7 tests)

Phase 78 — static background
- Removed the animated glow layers; static 44px grid over the flat --bg
  canvas; default and reduced-motion renders byte-identical
- Updated background/theme E2E suites; removed bg-glow test pins

Phase 79 — API tokens
- api_tokens model + migration 0012; hash-only token service
- Admin tokens API + Tokens admin view; POST /api/token-auth;
  live-revoking require_user on chat / suggestions / document content
- Frontend token gate with localStorage cache; anonymous E2E suites
  migrated to token login
- New story suite tests/e2e/test_api_tokens.py (9 tests)

Phase 80 — history suggestion chips
- last_questions() endpoint with SEED fallback; startNewChat() refetch
- Seed-semantics docs (config.py, .env.example, README)
- Integration state matrix + E2E suite rewritten to the 4 chip states

Also included: phase-76 report artifacts and the repo restore-test-db
skill (previously untracked), scripts/* ruff fixes from phase 77.

Final gate state (phase 80 final pass, covers everything above):
- uv run pytest --cov=app → 1637 passed, 0 failed, app/ coverage 99%
- uv run ruff check . && uv run pyright → clean, 0 errors
- Per-phase story E2E suites green in isolation
This commit is contained in:
2026-09-07 12:39:01 -04:00
parent 495d042a98
commit 7fce6572d0
215 changed files with 10142 additions and 1643 deletions
@@ -0,0 +1,8 @@
**Task 06 (phase 76 final) — complete.**
- **Gates (item 1):** `uv run pytest --cov=app` → 1565 passed, app/ **99%** (>90%); `uv run ruff check . && uv run pyright` → clean; isolation spot-checks — all 8 watch-list suites **PASS** (nav_switch_keeps_stream, sources_midstream_bug, hidden_tab_stream, stop_generation, chat_persistence, chat_history, header_consistency, cache_busting).
- **Full E2E batch:** 286 passed / 23 failed in one process; all 23 re-ran **green in isolation** (11 files, +8 watch-list = 19 suites PASS) — the known pre-existing port-sharing artifact (documented in this phase's task-03 report; AGENTS.md rule 9 = isolation is the protocol). No phase-76 regressions found; no prior defects to fix.
- **Owner repro (real browser, real LLM, :8010):** `tell me about everquest` → RAG mid-stream → Chat: window sentinel survived (same document), FULL answer, **one** brain bubble, no error banner, **+1 settled `query_log` row**, zero `turn cancelled` lines; auto-saved row matches. Control (real nav to `/shared.html` mid-stream): sentinel wiped, no settled row, cancel line logged, partial persisted. 9 screenshots in `.agents/screenshots/` (tracked, committed).
- **Commit:** `ffa919b fix(chat): keep in-flight answers alive across in-app view switches` (`--no-gpg-sign`, 78 files) — body records root cause, the owner-confirmed phase-48 refinement, Option A over B/C, boundaries, and the phase-20 suite rewrite. Phase dir moved to `.agents/phases/complete/`; `git status` clean.
- **Deviations:** n/a file is empty (no further phases queued); used `/shared.html` for the control (not `/login.html`, which auto-redirects admins) — same real-departure control the suite pins; the repro's answer was an honest deflection (`deflected=True`) since the dev KB holds no EverQuest docs — the settle (not the topic) is the proof, as expected post-e2e fixture seeding.
- **Next pending task:** none — phase 76 complete; `todo/` is empty.
@@ -0,0 +1,78 @@
........................................................................ [ 4%]
........................................................................ [ 9%]
........................................................................ [ 13%]
........................................................................ [ 18%]
........................................................................ [ 23%]
........................................................................ [ 27%]
........................................................................ [ 32%]
........................................................................ [ 36%]
........................................................................ [ 41%]
........................................................................ [ 46%]
........................................................................ [ 50%]
........................................................................ [ 55%]
........................................................................ [ 59%]
........................................................................ [ 64%]
........................................................................ [ 69%]
........................................................................ [ 73%]
........................................................................ [ 78%]
........................................................................ [ 82%]
........................................................................ [ 87%]
........................................................................ [ 92%]
........................................................................ [ 96%]
..................................................... [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 22 0 100%
app/api/chat.py 177 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 8 0 100%
app/api/sync.py 101 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 20 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/db.py 21 0 100%
app/main.py 60 0 100%
app/models.py 86 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 210 0 100%
-----------------------------------------------
TOTAL 2997 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
@@ -0,0 +1,12 @@
All gates green. This was the final verification pass — every task (01–03) was already implemented in the working tree and its task files were already staged in `.agents/phases/complete/77_navbar_refresh/`; I verified the implementation against the task specs, the E2E suite, and the unit pins, and ran every completion criterion. No defects found; no fixes needed.
**Phase 77 — final verification report**
- Verified (already implemented in working tree): `bor:view-refresh` dispatch in `router.js` (re-show + active re-click + popstate, `wasMounted` pre-mount gate, first-show/boot exempt); re-entrant re-fetch in History/RAG/Sources/Tuning (admin-branch listeners, Chat excluded with pinning comment); `#history-refresh` button (page-head, admin-only, in-flight disable + `#history-status` announce); unit pins in `test_frontend_router.py`; new story suite `test_navbar_refresh.py` (7 tests incl. sentinel, no-pushState, request-log, stream-survival control).
- `uv run pytest tests/e2e/test_navbar_refresh.py -v --no-cov` → **7 passed** (17.6s, isolated, DB up). ✅
- `uv run pytest tests/e2e/test_nav_switch_keeps_stream.py -v --no-cov` → **4 passed**; file byte-identical (no git diff). ✅
- `uv run pytest --cov=app --cov-report=term-missing` → **1575 passed**, TOTAL coverage **99%** (>90% floor). ✅
- `uv run ruff check . && uv run pyright` → clean (0 errors). ✅
- Commit/phase-dir move: left to the harness per executor rules (task files already in `complete/`; no `git add`/`commit` run by me).
- Notable: pre-existing uncommitted changes to `scripts/*` and phase-76 report artifacts are in the tree from earlier work — untouched, will ride along in the harness commit.
- Next pending phase: `78_static_background`.
@@ -0,0 +1,78 @@
........................................................................ [ 4%]
........................................................................ [ 9%]
........................................................................ [ 13%]
........................................................................ [ 18%]
........................................................................ [ 22%]
........................................................................ [ 27%]
........................................................................ [ 32%]
........................................................................ [ 36%]
........................................................................ [ 41%]
........................................................................ [ 45%]
........................................................................ [ 50%]
........................................................................ [ 54%]
........................................................................ [ 59%]
........................................................................ [ 64%]
........................................................................ [ 68%]
........................................................................ [ 73%]
........................................................................ [ 77%]
........................................................................ [ 82%]
........................................................................ [ 86%]
........................................................................ [ 91%]
........................................................................ [ 96%]
............................................................... [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 22 0 100%
app/api/chat.py 177 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 8 0 100%
app/api/sync.py 101 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 20 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/db.py 21 0 100%
app/main.py 60 0 100%
app/models.py 86 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 210 0 100%
-----------------------------------------------
TOTAL 2997 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
@@ -0,0 +1,19 @@
Task 01 complete. All gates green.
**Implemented**
- `frontend/assets/router.js`: `switchTo` captures `const wasMounted = mounted[name]` before the mount block and dispatches `bor:view-refresh` on the view's section after show + head/nav state (before the focus tail), gated on `wasMounted` — first show and boot never fire it; popstate inherits the gate. The `name === current` re-click branch now dispatches the event on `viewEls[name]` and returns (no `pushState`, menu-close unchanged). Header contract comment updated.
- `frontend/assets/history.js`: `loadChats()` is re-entrant (drops every `tr` except `#history-empty-row` before fetching — replaces, never duplicates); the ADMIN branch (after the whoami gate) arms `root.addEventListener("bor:view-refresh", () => { if (started) loadChats(); })` with `started` flipping true at the first `loadChats()`; anonymous branch never arms a listener.
- `tests/unit/test_frontend_router.py`: 3 new source pins — event literal + dispatch inside the pre-mount `wasMounted` capture with visible→refresh→focus ordering; re-click branch dispatches (no pushState/switchTo); history listener admin-branch-only + gated + row-clearing precedes fetch.
- Fixed 9 pre-existing ruff errors in `scripts/` (left by commit f221b10) so the gate is green.
**Results**
- `uv run pytest tests/unit/test_frontend_router.py -v` → 16 passed
- `uv run pytest` → 1568 passed
- `uv run pytest --cov=app --cov-report=term-missing` → TOTAL 99% (>90% floor)
- `uv run ruff check . && uv run pyright` → clean
- `uv run pytest tests/e2e/test_nav_switch_keeps_stream.py -v --no-cov` → 4 passed (phase-76 stream-survival contract holds with the hook live, runs the new router code in a real browser)
- `node --check` on both edited JS files → OK
**Decisions**: followed the phase design exactly (gate on `wasMounted` only — boot-onto-chat dispatch is harmless since Chat never listens); optional manual spot-check covered by the phase-76 E2E + syntax checks (the dedicated freshness E2E lands in task 03). No deviations.
**Next pending task**: `.agents/phases/todo/77_navbar_refresh/02_refresh_other_views.md`
@@ -0,0 +1,78 @@
........................................................................ [ 4%]
........................................................................ [ 9%]
........................................................................ [ 13%]
........................................................................ [ 18%]
........................................................................ [ 22%]
........................................................................ [ 27%]
........................................................................ [ 32%]
........................................................................ [ 36%]
........................................................................ [ 41%]
........................................................................ [ 45%]
........................................................................ [ 50%]
........................................................................ [ 55%]
........................................................................ [ 59%]
........................................................................ [ 64%]
........................................................................ [ 68%]
........................................................................ [ 73%]
........................................................................ [ 78%]
........................................................................ [ 82%]
........................................................................ [ 87%]
........................................................................ [ 91%]
........................................................................ [ 96%]
........................................................ [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 22 0 100%
app/api/chat.py 177 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 8 0 100%
app/api/sync.py 101 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 20 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/db.py 21 0 100%
app/main.py 60 0 100%
app/models.py 86 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 210 0 100%
-----------------------------------------------
TOTAL 2997 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
@@ -0,0 +1,18 @@
All green. Final report:
**Implemented (task 02 — RAG/Sources/Tuning join the refresh, Chat stays out):**
- `sources.js`: `loadDocs()` re-entrant — `tbody.replaceChildren()` moved to the top (before fetch; removed the redundant populated-path clear) so populated→empty refreshes leave no ghost rows; `bor:view-refresh` listener armed in the admin branch after the whoami gate
- `git-sources.js`: listener `() => loadSources()` in admin branch; verified a re-call resets all three states (success path runs `hideLoadError()` before `renderSources`, which replaces the tbody)
- `tuning.js`: listener `() => loadNotes()` in admin branch; failed-refresh keeps last rendered list (documented contract, unchanged)
- `app.js`: NO listener — one-line comment at the module-scope state pinning the deliberate exclusion (phase-76 stream survival)
- `tests/unit/test_frontend_router.py`: 4 new pins — per-view listener + gate ordering + re-entrancy checks, plus the app.js negative pin (targets the listener, not the literal, since the exclusion comment carries the literal)
**Results:**
- `uv run pytest tests/unit/test_frontend_router.py -v --no-cov` → 20 passed
- `uv run pytest --cov=app --cov-report=term-missing` → 1572 passed; `app/` coverage 99% (>90%)
- `uv run ruff check . && uv run pyright` → clean (0 errors)
- `uv run pytest tests/e2e/test_nav_switch_keeps_stream.py -v --no-cov` → 4 passed (unchanged, stream-survival holds)
**Decisions:** git-sources listener placed before `await loadSources()` to preserve the existing "LAST statement" unit pin; no `started` flag needed (listener armed post-gate, per task text).
**Next pending task:** `77_navbar_refresh/03_history_refresh_button.md` (History Refresh button + story E2E suite + regression sweep).
@@ -0,0 +1,78 @@
........................................................................ [ 4%]
........................................................................ [ 9%]
........................................................................ [ 13%]
........................................................................ [ 18%]
........................................................................ [ 22%]
........................................................................ [ 27%]
........................................................................ [ 32%]
........................................................................ [ 36%]
........................................................................ [ 41%]
........................................................................ [ 45%]
........................................................................ [ 50%]
........................................................................ [ 54%]
........................................................................ [ 59%]
........................................................................ [ 64%]
........................................................................ [ 68%]
........................................................................ [ 73%]
........................................................................ [ 77%]
........................................................................ [ 82%]
........................................................................ [ 87%]
........................................................................ [ 91%]
........................................................................ [ 96%]
............................................................ [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 22 0 100%
app/api/chat.py 177 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 8 0 100%
app/api/sync.py 101 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 20 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/db.py 21 0 100%
app/main.py 60 0 100%
app/models.py 86 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 210 0 100%
-----------------------------------------------
TOTAL 2997 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
@@ -0,0 +1,19 @@
Everything is complete and verified. Final state of all gates (run in the final file state):
**Implemented (task 03):**
- `frontend/index.html` — History `.page-head` is now a flex row (title block left, actions right): `#history-refresh` button with the house inline-SVG refresh glyph (`aria-hidden`) + visible "Refresh" label, `aria-label="Refresh saved chats"`, outside the table wrap
- `frontend/assets/history.js` — admin-branch binding: click → disable → `loadChats()` → announce `Saved chats refreshed.` (0-row = success) / failure lines (`Couldn't load saved chats — is the app reachable?` / `— try again.` now live inside `loadChats`, so every caller sees them) → re-enable in `finally`; anonymous branch hides the button (no dead control)
- `frontend/assets/styles.css` — `.history-refresh` reuses the `.new-chat-btn` language (brand pill 5.2:1, ≥44px, hover, `:disabled`, global focus-visible); `#view-history .page-head` flex row scoped (other four views untouched); icon-only ≤640px (phase-46 convention)
- `tests/unit/test_frontend_router.py` — 3 new source pins (markup placement, admin-only binding + outcome lines, CSS language/mobile)
- `tests/e2e/test_navbar_refresh.py` — new story suite, 7 tests: re-show re-fetch (+ no-duplicates + same-doc sentinel), active re-click (no pushState, `history.length` unchanged), refresh button (disabled in-flight via the house request-hold pattern, status line, re-enable, keyboard focus), popstate re-fetch, four-views request-log (+History row-delta), stream-survival control (full mock answer + settled `query_log`), API row-shape sanity
**Test / lint / coverage results:**
- `uv run pytest tests/e2e/test_navbar_refresh.py -v --no-cov` → 7 passed (isolated, DB up)
- `uv run pytest tests/e2e/test_nav_switch_keeps_stream.py -v --no-cov` → 4 passed, unchanged
- `uv run pytest --cov=app --cov-report=term-missing` → 1575 passed, TOTAL 99% (>90% floor)
- `uv run ruff check . && uv run pyright` → clean
- Regression sweep green: `test_chat_history`, `test_share_chat`, `test_history_copy` (10), `test_stale_saved_chats`+`test_llm_history` (6), header/nav/cache/persistence (23), `test_hidden_tab_stream`+`test_stop_generation` (7)
**Decisions/deviations:** failure announce moved into `loadChats` (no existing load-failure copy existed — house "reachable?/try again." family reused, and the §7.4 contract improved for all callers); anonymous hides the button (task's "admin branch only" read as gate-gated control, not a dead button); E2E request-log polling must use `page.wait_for_timeout` (sync API starves `page.on` events under raw `time.sleep` — documented in `_wait_until`); per harness rules the atomic commit + phase-dir move are left to the harness (task's commit item intentionally not executed).
**Next pending task:** none in phase 77 (task 03 was the last); next phase in `todo/` is `78_static_background`.
@@ -0,0 +1,78 @@
........................................................................ [ 4%]
........................................................................ [ 9%]
........................................................................ [ 13%]
........................................................................ [ 18%]
........................................................................ [ 22%]
........................................................................ [ 27%]
........................................................................ [ 32%]
........................................................................ [ 36%]
........................................................................ [ 41%]
........................................................................ [ 45%]
........................................................................ [ 50%]
........................................................................ [ 54%]
........................................................................ [ 59%]
........................................................................ [ 64%]
........................................................................ [ 68%]
........................................................................ [ 73%]
........................................................................ [ 77%]
........................................................................ [ 82%]
........................................................................ [ 86%]
........................................................................ [ 91%]
........................................................................ [ 96%]
............................................................... [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 22 0 100%
app/api/chat.py 177 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 8 0 100%
app/api/sync.py 101 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 20 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/db.py 21 0 100%
app/main.py 60 0 100%
app/models.py 86 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 210 0 100%
-----------------------------------------------
TOTAL 2997 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
@@ -0,0 +1,12 @@
All checks green. Final report:
**Phase 78 — Static background: verification pass complete (both tasks were already implemented; verified end-to-end, no defects found).**
- Verified: glow rules (`body::after`, `html::before`, `html::after`), `@keyframes bg-glow-a/b/c`, and the background-only `prefers-reduced-motion` block deleted from `styles.css`; `body::before` grid rule byte-identical to HEAD (44px, 60% `--line` alpha, radial mask); phase-78 comment header in place; other 7 reduced-motion blocks + UI animations untouched; `frontend/assets/themes/` (indigo) glow-free.
- Verified: `tests/unit/test_background_no_motion.py` + `tests/e2e/test_background_no_motion.py` rewritten to the static contract; both `test_background_animation.py` suites deleted; no stale references.
- Tests: `uv run pytest` → 1561 passed · `uv run pytest --cov=app --cov-report=term-missing` → 99% on `app/` (>90%) · `uv run ruff check . && uv run pyright` → clean (0 errors)
- E2E in isolation (DB up): `test_background_no_motion.py` 5 passed · `test_dark_tech_theme.py` 6 passed · `test_smoke.py` 3 passed
- Visual check (real server :8791, Chromium): static grid over flat `--bg` canvas, zero glow spots, `document.getAnimations()` empty, reduced-motion profile identical (screenshots confirmed).
- Completion criteria: 1 no bg-glow/animation in CSS + grid renders — PASS · 2 suites green, deletions gone — PASS · 3 pytest/coverage/lint — PASS · 4 commit + phase-dir move — deferred to harness per executor rules (no git commands run).
- Deviation: `test_dark_tech_theme.py` had its 2 background tests + docstring re-pinned to the static contract (its glow-fade assertions could not survive the deletion); palette/emoji/behavior tests untouched — same house pattern as phase 76.
- Next pending phase: `79_api_tokens`.
@@ -0,0 +1,78 @@
........................................................................ [ 4%]
........................................................................ [ 9%]
........................................................................ [ 13%]
........................................................................ [ 18%]
........................................................................ [ 23%]
........................................................................ [ 27%]
........................................................................ [ 32%]
........................................................................ [ 36%]
........................................................................ [ 41%]
........................................................................ [ 46%]
........................................................................ [ 50%]
........................................................................ [ 55%]
........................................................................ [ 59%]
........................................................................ [ 64%]
........................................................................ [ 69%]
........................................................................ [ 73%]
........................................................................ [ 78%]
........................................................................ [ 83%]
........................................................................ [ 87%]
........................................................................ [ 92%]
........................................................................ [ 96%]
................................................. [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 22 0 100%
app/api/chat.py 177 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 8 0 100%
app/api/sync.py 101 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 20 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/db.py 21 0 100%
app/main.py 60 0 100%
app/models.py 86 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 210 0 100%
-----------------------------------------------
TOTAL 2997 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
@@ -0,0 +1,10 @@
All green. The remaining diff entries (`app.js`, `index.html`, `test_navbar_refresh.py`, etc.) are phase-77's uncommitted working-tree changes, not mine.
**Report — phase 78, task 01 (remove glow layers)**
- `styles.css`: deleted `body::after`/`html::before`/`html::after` glow rules, the three `bg-glow-*` keyframes, and the background-only reduced-motion block (7 unrelated reduced-motion blocks untouched); replaced the phase-08/25 header with a 4-line phase-78 note; `body::before` grid rule byte-identical.
- Rewrote `tests/unit/test_background_no_motion.py` (8 source pins: no `bg-*` token in CSS, glow rules absent, grid static + 44px + mask, no-occlusion plumbing, reduced-motion block gone, no blur) and `tests/e2e/test_background_no_motion.py` (5 Playwright computed-style tests incl. "no box" proof via `content: none`/`position: static`).
- Deleted `tests/unit/test_background_animation.py` + `tests/e2e/test_background_animation.py` (superseded chain 08→25→78).
- **Deviation:** `test_dark_tech_theme.py` could not stay "UNCHANGED" — it pinned `bg-glow-a/b/c` fades and glow images in `test_animated_background`/`test_reduced_motion_honored`, which the deletion breaks (and the literals violate the zero-hit rg criterion). Minimal contract-preserving update: renamed to `test_static_background` pinning the static contract, updated the reduced-motion test, dropped the now-unused `_seconds`/`pytest` import. All other tests in the file untouched.
- Tests: unit pins + themes 14 passed; e2e `test_background_no_motion.py` 5 passed in isolation; `test_dark_tech_theme.py` 6 passed; full `uv run pytest` 1561 passed; `--cov=app` **99%** (>90% floor); `uv run ruff check .` clean; `uv run pyright` 0 errors; `rg "bg-glow" app/ frontend/ tests/` zero hits; themes carry no glow/keyframes.
- Next pending task: `.agents/phases/todo/78_static_background/02_regression_sweep_commit.md`.
@@ -0,0 +1,78 @@
........................................................................ [ 4%]
........................................................................ [ 9%]
........................................................................ [ 13%]
........................................................................ [ 18%]
........................................................................ [ 23%]
........................................................................ [ 27%]
........................................................................ [ 32%]
........................................................................ [ 36%]
........................................................................ [ 41%]
........................................................................ [ 46%]
........................................................................ [ 50%]
........................................................................ [ 55%]
........................................................................ [ 59%]
........................................................................ [ 64%]
........................................................................ [ 69%]
........................................................................ [ 73%]
........................................................................ [ 78%]
........................................................................ [ 83%]
........................................................................ [ 87%]
........................................................................ [ 92%]
........................................................................ [ 96%]
................................................. [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 22 0 100%
app/api/chat.py 177 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 8 0 100%
app/api/sync.py 101 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 20 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/db.py 21 0 100%
app/main.py 60 0 100%
app/models.py 86 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 210 0 100%
-----------------------------------------------
TOTAL 2997 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
@@ -0,0 +1,9 @@
**Report — phase 78, task 02 (regression sweep + commit)**
- E2E in isolation (DB up): `uv run pytest tests/e2e/test_background_no_motion.py -v --no-cov` → 5 passed; `uv run pytest tests/e2e/test_dark_tech_theme.py -v --no-cov` → 6 passed; `uv run pytest tests/e2e/test_smoke.py -v --no-cov` → 3 passed.
- Visual check (real server, no mock): booted two real uvicorn instances (built-in palette + `BOR_THEME=indigo.css`), captured 8 screenshots (chat + sources × default/reduced-motion × both palettes). Result: static 44px grid over the flat `--bg` canvas, zero glow spots, `document.getAnimations()` empty; default vs reduced-motion screenshots are **byte-identical**; indigo applies cleanly (`--bg` `rgb(10,14,26)`, grid follows `--line`).
- Full gate: `uv run pytest --cov=app --cov-report=term-missing` → 1561 passed, **99%** on `app/` (>90% ✓); `uv run ruff check .` → All checks passed; `uv run pyright` → 0 errors, 0 warnings.
- `rg "bg-glow" frontend/ tests/ app/` → zero hits; themes carry no keyframes/animation.
- **Commit/phase-move:** per the pipeline override (harness makes the one atomic `--no-gpg-sign` commit and moves the phase dir), I did **not** run `git add`/`git commit` or touch `.agents/phases/`; all changes left in the working tree.
- **Confirmed deviation (from task 01, validated here):** `test_dark_tech_theme.py` could not stay literally UNCHANGED — it pinned the deleted `bg-glow-a/b/c` fades, so it could never be green unchanged. Minimal contract-preserving update only: `test_animated_background` → `test_static_background` + the reduced-motion test; all four palette/emoji/behavior/no-CDN tests byte-identical.
- Next pending: `.agents/phases/todo/79_api_tokens/` (phase 78's remaining files go to `complete/` with the harness commit).
@@ -0,0 +1,78 @@
........................................................................ [ 4%]
........................................................................ [ 9%]
........................................................................ [ 13%]
........................................................................ [ 18%]
........................................................................ [ 23%]
........................................................................ [ 27%]
........................................................................ [ 32%]
........................................................................ [ 36%]
........................................................................ [ 41%]
........................................................................ [ 46%]
........................................................................ [ 50%]
........................................................................ [ 55%]
........................................................................ [ 59%]
........................................................................ [ 64%]
........................................................................ [ 69%]
........................................................................ [ 73%]
........................................................................ [ 78%]
........................................................................ [ 83%]
........................................................................ [ 87%]
........................................................................ [ 92%]
........................................................................ [ 96%]
................................................. [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 22 0 100%
app/api/chat.py 177 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 8 0 100%
app/api/sync.py 101 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 20 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/db.py 21 0 100%
app/main.py 60 0 100%
app/models.py 86 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 210 0 100%
-----------------------------------------------
TOTAL 2997 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
@@ -0,0 +1,12 @@
Everything is green. This was a verification pass only — no code changes were needed. Final report:
**Phase 79 (API tokens) — final verification pass**
- Verified tasks 01–08 already complete in `complete/79_api_tokens/`; confirmed implementation matches the locked design (A3–A5): `api_tokens` model + migration `0012`, hash-only token service, admin tokens router, `POST /api/token-auth` + 3-role whoami, live-revoking `require_user` on chat/suggestions/document-content, frontend gate + localStorage cache, Tokens admin view.
- Test results:
- `uv run pytest tests/e2e/test_api_tokens.py -v --no-cov` → **9 passed** (all owner-sentence clauses pinned: anon lockout, shared open, UI generate, token-user chat+doc, reload cache, admin walls 403, sign-out clears key, revocation refusal, no-enumeration error)
- `uv run pytest --cov=app --cov-report=term-missing` → **1639 passed, 0 failed, coverage 99%** (>90% floor)
- `uv run ruff check . && uv run pyright` → **clean (0 errors)**
- E2E spot-checks green: `test_admin_auth`, `test_share_chat` (anonymous), `test_chat_rag`, `test_nav_switch_keeps_stream`, `test_smoke` + all 10 token-migrated chat suites
- Completion criteria: all met — story suite green in isolation; token-user E2E chat + cited doc / anon 401 + gate / shared open / admin surfaces 403 (asserted in the passing suite); cache-survives-reload, sign-out clears, revocation refused on next request + fresh login; full suite + coverage + lint green.
- Notable: commit + phase-dir move are the harness's (per this pass's rules); README "API tokens" section verified present; `.env.example` correctly untouched. No defects found.
- Next pending phase: **80_history_suggestion_chips**.
@@ -0,0 +1,81 @@
........................................................................ [ 4%]
........................................................................ [ 8%]
........................................................................ [ 13%]
........................................................................ [ 17%]
........................................................................ [ 22%]
........................................................................ [ 26%]
........................................................................ [ 30%]
........................................................................ [ 35%]
........................................................................ [ 39%]
........................................................................ [ 44%]
........................................................................ [ 48%]
........................................................................ [ 53%]
........................................................................ [ 57%]
........................................................................ [ 61%]
........................................................................ [ 66%]
........................................................................ [ 70%]
........................................................................ [ 75%]
........................................................................ [ 79%]
........................................................................ [ 84%]
........................................................................ [ 88%]
........................................................................ [ 92%]
........................................................................ [ 97%]
........................................... [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 40 0 100%
app/api/chat.py 178 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 11 0 100%
app/api/sync.py 101 0 100%
app/api/tokens.py 28 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 45 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/core/tokens.py 33 0 100%
app/db.py 21 0 100%
app/main.py 62 0 100%
app/models.py 94 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 231 0 100%
-----------------------------------------------
TOTAL 3136 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
@@ -0,0 +1,13 @@
All green. Task 01 is complete.
**Report**
- `app/models.py`: `ApiToken` model after `SavedChat` — `id` UUID PK (uuid4 default), `label` String(120) NOT NULL (display-only), `token_hash` String(64) NOT NULL unique (sha256 of the full `bor_…` token), `created_at` server-default now(), `last_used_at`/`revoked_at` TIMESTAMPTZ NULL; docstring documents the hashed-credential trust model; module docstring gains the `api_tokens` bullet.
- `alembic/versions/0012_api_tokens.py` (revision `0012` → `0011`): `op.create_table` + explicit unique `op.create_index("ix_api_tokens_token_hash", …)` (the 0009/0011 shape); downgrade drops index then table.
- `tests/unit/test_api_tokens_model.py` (new, schema-level per house precedent): table name, column set/nullability/widths, `token_hash` unique, `label` not unique — 4 tests.
- `tests/integration/test_migration_0012.py` (new, mirrors `test_migration_0011.py`): upgrade column contract, lifecycle round-trip, duplicate-hash rejection + repeated-label OK, downgrade/round-trip — 5 tests.
- Migration: `uv run alembic upgrade head` → 0012 applied; `downgrade -1` + `upgrade head` round-trips cleanly; dev DB at `0012 (head)` so tasks 02/03 pick the table up.
- Full suite: `uv run pytest --cov=app --cov-report=term` → **1570 passed**, coverage **99%** (>90%; `app/models.py` 100%).
- Lint/types: `uv run ruff check .` → All checks passed; `uv run pyright` → 0 errors.
- Deviation (minor): model column uses `unique=True` alone (all four house precedents — `doc_drafts.token`, `saved_chats.share_token`, etc.) rather than `unique=True, index=True`; the unique index requirement is met by the migration's explicit `op.create_index(..., unique=True)`, exactly the 0009 shape the task pointed at.
- Next pending task: `.agents/phases/todo/79_api_tokens/02_token_admin_api.md` (token service + admin create/list/revoke).
@@ -0,0 +1,78 @@
........................................................................ [ 4%]
........................................................................ [ 9%]
........................................................................ [ 13%]
........................................................................ [ 18%]
........................................................................ [ 22%]
........................................................................ [ 27%]
........................................................................ [ 32%]
........................................................................ [ 36%]
........................................................................ [ 41%]
........................................................................ [ 45%]
........................................................................ [ 50%]
........................................................................ [ 55%]
........................................................................ [ 59%]
........................................................................ [ 64%]
........................................................................ [ 68%]
........................................................................ [ 73%]
........................................................................ [ 77%]
........................................................................ [ 82%]
........................................................................ [ 87%]
........................................................................ [ 91%]
........................................................................ [ 96%]
.......................................................... [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 22 0 100%
app/api/chat.py 177 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 8 0 100%
app/api/sync.py 101 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 20 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/db.py 21 0 100%
app/main.py 60 0 100%
app/models.py 94 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 210 0 100%
-----------------------------------------------
TOTAL 3005 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
@@ -0,0 +1,15 @@
Task 02 complete — all gates green.
**Implemented**
- `app/core/tokens.py` — `generate_token` / `hash_token` (sha256 of FULL string) / `create_token` / `find_active_by_token` (hash → unique-index hit → `revoked_at IS NULL`, generic miss) / `mark_used` / `revoke` (stamp-once, False only for missing id); hash-lookup-vs-constant-time-contrast documented; house flush-not-commit split
- `app/schemas.py` — `TokenCreateRequest` (strip-then 1–120, 422 fail-loud), `TokenCreated` (only schema carrying plaintext), `TokenListItem` / `TokenList` (secret-free by construction), `TokenAuthRequest` (deliberately no min-length → task-03 401 contract)
- `app/api/tokens.py` — router-wide `require_admin`; `POST /tokens` → 201 (plaintext once), `GET /tokens` newest-first (`created_at desc, id desc`), `POST /tokens/{id}/revoke` → 204 idempotent / 404 `token not found`; registered in `app/main.py` before the static mount
- `tests/unit/test_tokens.py` (11) + `tests/integration/test_tokens_api.py` (7): shape, hash, round-trip, all miss paths, revoke idempotency + `last_used_at` preservation, anonymous 403s, plaintext/hash never in list, duplicate labels, 422s, 404, ordering
**Results**
- `uv run pytest --cov=app --cov-report=term-missing` → 1588 passed; TOTAL 99% (`core/tokens.py` 100%, `api/tokens.py` 100%)
- `uv run ruff check .` → clean (fixed `datetime.UTC` alias + import sort); `uv run pyright` → 0 errors
**Decisions** — service flushes, endpoint commits (sources_meta house pattern); used `datetime.UTC` per ruff (identical to spec's `timezone.utc`).
**Next pending task:** `03_token_auth_enforcement.md`
@@ -0,0 +1,81 @@
........................................................................ [ 4%]
........................................................................ [ 9%]
........................................................................ [ 13%]
........................................................................ [ 18%]
........................................................................ [ 22%]
........................................................................ [ 27%]
........................................................................ [ 31%]
........................................................................ [ 36%]
........................................................................ [ 40%]
........................................................................ [ 45%]
........................................................................ [ 49%]
........................................................................ [ 54%]
........................................................................ [ 58%]
........................................................................ [ 63%]
........................................................................ [ 68%]
........................................................................ [ 72%]
........................................................................ [ 77%]
........................................................................ [ 81%]
........................................................................ [ 86%]
........................................................................ [ 90%]
........................................................................ [ 95%]
........................................................................ [ 99%]
.... [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 22 0 100%
app/api/chat.py 177 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 8 0 100%
app/api/sync.py 101 0 100%
app/api/tokens.py 28 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 20 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/core/tokens.py 33 0 100%
app/db.py 21 0 100%
app/main.py 62 0 100%
app/models.py 94 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 231 0 100%
-----------------------------------------------
TOTAL 3089 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
@@ -0,0 +1,15 @@
All green. Task 03 complete.
**Implemented**
- `app/core/auth.py`: `USER_SESSION_KEY`/`USER_TOKEN_ID_KEY` + `require_user` — admin always passes (no DB touch); token session live-checks the `api_tokens` row per request (PK lookup = revocation check); dead session (revoked/missing/corrupt id) → both user keys popped + 401 `authentication required`; anonymous → same 401
- `app/api/auth.py`: public `POST /api/token-auth` (valid → `mark_used`+commit+`user`/`user_token_id` session keys → 204; malformed/unknown/revoked/empty → ONE generic 401 `invalid token`); `whoami` now reports `admin`|`user`|`anonymous` (admin wins on coexistence); `WhoamiResponse.role` comment updated; logout unchanged (wipes both roles)
- Enforcement: `_user: None = Depends(require_user)` on `POST /api/chat`, `GET /api/suggestions` (+ `db` dep), `GET /api/documents/content` (docstring: phase-16 soft rule SUPERSEDED); fixed stale "viewer/chat public" docstrings in `docs.py`, `steering.py`, `models.py`, `tokens.py`
- Tests: `require_user` matrix + 3-role whoami in `tests/unit/test_auth.py`; `test_auth_api.py` rewritten pins (token-auth flows, mid-session revocation → chat 401 + whoami anonymous + fresh login refused, token-user surface matrix incl. 403s on all admin surfaces); admin sign-in wired into `test_api.py`, `test_chat_api.py`, `test_kb_overview_api.py`, `test_document_content.py`, `test_document_viewer.py`, `test_chat_gate.py`, `test_chat_cancel.py` (raw ASGI scope carries a signed admin cookie)
**Results**
- `uv run pytest --cov=app --cov-report=term-missing` → 1605 passed; TOTAL coverage **99%** (>90% bar; all modified files 100%)
- `uv run ruff check .` → All checks passed; `uv run pyright` → 0 errors
**Decisions**: `last_used_at` stamped only on `token-auth` (per task-03 spec — the only request presenting the token; the gate re-sends it per page load); prior tasks' docstrings aligned accordingly. E2E suites (10 anonymous chat suites, `test_admin_auth.py`) intentionally untouched — that's task 04.
**Next pending task**: `.agents/phases/todo/79_api_tokens/04_migrate_anonymous_e2e.md`
@@ -0,0 +1,81 @@
........................................................................ [ 4%]
........................................................................ [ 8%]
........................................................................ [ 13%]
........................................................................ [ 17%]
........................................................................ [ 22%]
........................................................................ [ 26%]
........................................................................ [ 31%]
........................................................................ [ 35%]
........................................................................ [ 40%]
........................................................................ [ 44%]
........................................................................ [ 49%]
........................................................................ [ 53%]
........................................................................ [ 58%]
........................................................................ [ 62%]
........................................................................ [ 67%]
........................................................................ [ 71%]
........................................................................ [ 76%]
........................................................................ [ 80%]
........................................................................ [ 85%]
........................................................................ [ 89%]
........................................................................ [ 94%]
........................................................................ [ 98%]
..................... [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 40 0 100%
app/api/chat.py 178 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 11 0 100%
app/api/sync.py 101 0 100%
app/api/tokens.py 28 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 45 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/core/tokens.py 33 0 100%
app/db.py 21 0 100%
app/main.py 62 0 100%
app/models.py 94 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 231 0 100%
-----------------------------------------------
TOTAL 3136 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
@@ -0,0 +1,17 @@
All gates pass. Final state verified:
**Task 04 report — E2E suites meet the new auth contract**
**Implemented**
- `auth_helpers.py`: added `login_with_token` (+ `WRONG_TOKEN` sentinel, wrong/revoked branch via `#auth-gate-error` role=alert) for task 07; made `login()` idempotent — an already-signed-in context boot-redirects past the form (login.js whoami), which was breaking double sign-in.
- Signed in the 10 named chat suites **plus ~40 more** the audit widened (viewer/suggestion/upload/sync suites) — page `login()` or httpx client login; shared-chat + deliberate anonymous-surface suites untouched.
- `test_admin_auth.py`: anonymous pins moved to the gated contract (401 `{"detail":"authentication required"}` on `/api/chat` + `/api/documents/content`, whoami anonymous, viewer not-found card); password flow unchanged.
- **Defects fixed**: (a) app bug — `sources.js` `loadDocs` race (boot re-attach + boot load interleave → 2× catalog rows, 108-row table) got a monotonic seq guard; (b) test-infra bug — 8 module-scoped `app_server` overrides collided with the session app on port 8123 in combined runs (silently driving the wrong server) → dedicated ports 8124–8134; (c) sync suites' TRUNCATE now clears `git_sources` (shared-DB hygiene); (d) anonymous viewer-content asserts made role-conditional (not-found card = anonymous settled state); (e) anonymous chip/contrast tests sign in (suggestions gated).
**Gates**
- `uv run pytest tests/e2e -v --no-cov` → **308 passed, 0 failed** (single session; baseline was 174 failed)
- `uv run pytest tests/e2e/test_share_chat.py test_smoke.py test_nav_switch_keeps_stream.py test_archive_upload_sources.py test_admin_auth.py --no-cov` (isolation spot-check) → 22 passed
- `uv run pytest --cov=app --cov-report=term-missing` → **1605 passed, app/ 99%**
- `uv run ruff check . && uv run pyright` → clean
**Decisions**: port fix + loadDocs guard are test-infra/app defects (no story semantics changed), required to make the combined inventory green — the full inventory had never been run as one session before. Not committed (per instructions). Next pending: **05_frontend_token_gate**.
@@ -0,0 +1,81 @@
........................................................................ [ 4%]
........................................................................ [ 8%]
........................................................................ [ 13%]
........................................................................ [ 17%]
........................................................................ [ 22%]
........................................................................ [ 26%]
........................................................................ [ 31%]
........................................................................ [ 35%]
........................................................................ [ 40%]
........................................................................ [ 44%]
........................................................................ [ 49%]
........................................................................ [ 53%]
........................................................................ [ 58%]
........................................................................ [ 62%]
........................................................................ [ 67%]
........................................................................ [ 71%]
........................................................................ [ 76%]
........................................................................ [ 80%]
........................................................................ [ 85%]
........................................................................ [ 89%]
........................................................................ [ 94%]
........................................................................ [ 98%]
..................... [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 40 0 100%
app/api/chat.py 178 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 11 0 100%
app/api/sync.py 101 0 100%
app/api/tokens.py 28 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 45 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/core/tokens.py 33 0 100%
app/db.py 21 0 100%
app/main.py 62 0 100%
app/models.py 94 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 231 0 100%
-----------------------------------------------
TOTAL 3136 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
@@ -0,0 +1,16 @@
All work is complete and verified. Final report:
**Implemented** — In-app token gate + browser caching (phase 79 task 05):
- `frontend/assets/token-gate.js` (new): mounts `#auth-gate` (shell) / `#doc-auth-gate` (viewer). Silent re-auth of cached `bor.token` via `POST /api/token-auth` runs **before** the whoami check (no gate flash, race-free vs header boot); dead/revoked cached tokens are dropped. Anonymous → gate revealed, `#main` made `inert`, focus lands on the token input. Success → cache token, invalidate whoami cache, unlock **without reload**, re-boot header, fire `onAuthed`.
- `header.js`: Sign in/out pair keys off `authenticated` (a token user sees Sign out; sign-out clears `bor.token` too); admin-only nav links + steering panel still key off `role === "admin"`.
- Gate markup/CSS in `index.html` + `document.html`; `Containerfile` esbuild line; unit pins `tests/unit/test_token_gate.py` (+ updated header/chat-UI pins); migrated 10 anonymous E2E suites to the gate contract.
**Test/lint/coverage**
- `uv run pytest --cov=app` → **1622 passed**, TOTAL coverage **99%** (gate >90%)
- `uv run ruff check .` → clean; `uv run pyright` → 0 errors
- E2E full inventory run: 4 anonymous-flow failures found → fixed → green; gate-related batch re-ran **61 passed**
- Real-server manual pass (9 flows: anonymous, wrong token, right token no-reload, cached reload, viewer, sign-out, revoked-login, revoked-cached, anonymous viewer) — all green
**Decisions** — Gate locks the whole `#main` (all views, incl. tuning/sources), not just chat; header stays interactive (admin form login remains the second entry). Token `user` = authenticated for the sign-in pair but gets the anonymous UI surface (no admin links, no steering panel). `test_global_tuning`'s 403-form contract now runs as a non-admin token user (the anonymous path is unreachable by design); `test_responsive_polish` checks the gate's focus contract anonymous + original AC3 contract signed-in. No git ops; no `.agents/` files touched; throwaway debug scripts removed.
**Next task** — `.agents/phases/todo/79_api_tokens/06_tokens_admin_view.md`
@@ -0,0 +1,81 @@
........................................................................ [ 4%]
........................................................................ [ 8%]
........................................................................ [ 13%]
........................................................................ [ 17%]
........................................................................ [ 22%]
........................................................................ [ 26%]
........................................................................ [ 31%]
........................................................................ [ 35%]
........................................................................ [ 39%]
........................................................................ [ 44%]
........................................................................ [ 48%]
........................................................................ [ 53%]
........................................................................ [ 57%]
........................................................................ [ 62%]
........................................................................ [ 66%]
........................................................................ [ 71%]
........................................................................ [ 75%]
........................................................................ [ 79%]
........................................................................ [ 84%]
........................................................................ [ 88%]
........................................................................ [ 93%]
........................................................................ [ 97%]
...................................... [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 40 0 100%
app/api/chat.py 178 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 11 0 100%
app/api/sync.py 101 0 100%
app/api/tokens.py 28 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 45 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/core/tokens.py 33 0 100%
app/db.py 21 0 100%
app/main.py 62 0 100%
app/models.py 94 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 231 0 100%
-----------------------------------------------
TOTAL 3136 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
@@ -0,0 +1,18 @@
Everything is green and the tree contains only expected changes (plus the phase's earlier uncommitted tasks 01–05, which the harness commits atomically).
## Report — Task 06: admin Tokens view (generate · list · revoke)
**Implemented**
- `#view-tokens` section in the shell (`index.html`): page-head, `#tokens-gate` (history-gate pattern, `?next=/tokens.html`), `#tokens-status` live region, create row (label + Generate, ship-hidden), `#token-once` block (mono read-only field + Copy, ship-hidden), full-width table Label | Created | Last used | Status (Active em-dash / rose Revoked pill, cell aria-labels) | Actions (visually-hidden header), hidden empty row
- `frontend/assets/tokens.js`: `mount(root)` with admin gate (anonymous: gate in, controls out, **no** `/api/tokens` fetch), re-entrant `loadTokens()` (replaces rows, hides+wipes the once-block), generate (blank label → `"token"`, "Generating…" lifecycle, plaintext revealed only after the re-render load — never stored client-side), copy (clipboard + inline select-fallback, own ~10-line copy), inline two-step Revoke (`history-confirm-*`, row re-renders Revoked), `bor:view-refresh` listener armed in admin branch, all cells textContent (zero innerHTML)
- Router entries (`VIEW`/`VIEW_PATH`/`VIEW_MODULES`/`TITLES`/`DESCRIPTIONS`, replaceAll brand contract), `header.js` reveals `#nav-tokens` for admin
- `/tokens.html` in `app/main.py` `_shell_routes` + `caching.py` `HTML_PAGES`; styles.css Tokens section (history-table/brand-pill/stale-pill language, focus-visible, AA pairs) + ≤640px stacking
- Tests: `test_frontend_router.py` view-map/module/title pins extended + 2 new tokens contract tests; `test_api.py` (3 lists), `test_caching_revalidation.py` `SHELL_BACKED_PAGES`, `test_caching.py` `HTML_PAGES`; `test_nav_rename_sources.py` `NAV_TAIL` gains "Tokens"
- **Deviation (required):** `#nav-tokens` also ships on `document.html`/`login.html`/`shared.html` (task named only `index.html`) — the phase-34 one-bar contract and `test_nav_consistency.py`'s cross-page inventory parity (History precedent, 4 pages) make the shell-only link a real failure; null-safe header.js is unaffected
**Results**
- `uv run pytest --cov=app --cov-report=term-missing` → exit 0, TOTAL 99% (>90%; `main.py`/`caching.py`/`tokens.py` all 100%)
- `uv run ruff check . && uv run pyright` → clean
- E2E (isolated): temp manual pass (anon gate/no-fetch; admin generate→once+copy→revoke two-step→Revoked; re-show hides once-block; revoked token refused; active token unlocks; user gets gate) + `test_cache_busting`, `test_asset_cache_revalidation`, `test_nav_switch_keeps_stream`, `test_nav_consistency`, `test_admin_auth`, `test_nav_rename_sources`, `test_header_consistency`, `test_mobile_hamburger_nav`, `test_shared_header`, `test_navbar_refresh`, `test_tuning_nav_link`, `test_sticky_navbar`, `test_smoke`, `test_document_viewer`, `test_global_tuning`, `test_git_sources_admin`, `test_kb_overview`, `test_chat_history`, `test_history_copy`, `test_share_chat`, `test_stale_saved_chats` — all green (temp file deleted)
**Next pending task:** `07_e2e_story_suite.md`
@@ -0,0 +1,81 @@
........................................................................ [ 4%]
........................................................................ [ 8%]
........................................................................ [ 13%]
........................................................................ [ 17%]
........................................................................ [ 22%]
........................................................................ [ 26%]
........................................................................ [ 30%]
........................................................................ [ 35%]
........................................................................ [ 39%]
........................................................................ [ 44%]
........................................................................ [ 48%]
........................................................................ [ 53%]
........................................................................ [ 57%]
........................................................................ [ 61%]
........................................................................ [ 66%]
........................................................................ [ 70%]
........................................................................ [ 75%]
........................................................................ [ 79%]
........................................................................ [ 84%]
........................................................................ [ 88%]
........................................................................ [ 92%]
........................................................................ [ 97%]
........................................... [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 40 0 100%
app/api/chat.py 178 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 11 0 100%
app/api/sync.py 101 0 100%
app/api/tokens.py 28 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 45 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/core/tokens.py 33 0 100%
app/db.py 21 0 100%
app/main.py 62 0 100%
app/models.py 94 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 231 0 100%
-----------------------------------------------
TOTAL 3136 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
@@ -0,0 +1,17 @@
All gates green. Task complete.
**Report — Phase 79, task 07 (story E2E suite)**
- Implemented `tests/e2e/test_api_tokens.py` (new, 9 tests) pinning every clause of TODO.md L5 in a real browser:
- anonymous lockout: `#auth-gate` visible, `#main` inert, composer unreachable via inverted tab walk; chat/suggestions/document-content 401 from empty cookies
- shared chats stay open: admin creates+shares via API; fresh context renders `/shared/<token>` with zero gate markup
- admin UI generate: `#token-once-value` matches `bor_[0-9a-f]{32}`, Active row in table, once-block wiped on re-show (router re-show refresh)
- token flow: real-gate login (task-04 helper) → grounded mock-LLM turn → cited chip opens same-page `#doc-modal`; all 5 admin nav links absent, Sign out visible, whoami role `user`
- caching: `localStorage["bor.token"]` survives reload with silent re-auth (no gate, no re-entry)
- admin-only walls 403 on tokens/chats/docs/steering/git-sources; sign out clears session + cached token
- revocation: UI two-step; chain pinned in the holder's own browser cookies — whoami still `user` (lazy) → next gated 401 clears the session → whoami anonymous; fresh login refused at the gate
- wrong token: role=alert stays, malformed and well-formed-unknown 401 bodies byte-equal (no enumeration)
- Results: `uv run pytest tests/e2e/test_api_tokens.py -v --no-cov` → **9 passed** (isolation); spot-check `test_admin_auth.py` + `test_share_chat.py` → 10 passed; `uv run pytest --cov=app` → green, `app/` coverage **99%** (>90%); `uv run ruff check .` clean; `uv run pyright` 0 errors
- Notable: no app defects found — tasks 01–06 hold. One harness quirk worked around: httpx's cookie jar ignores the expired `Set-Cookie` the 401 sends, so the revocation chain is driven through the user context's real browser fetch (also matches the task's "context cookies" wording; app contract itself verified via TestClient)
- DB hygiene: autouse fixture deletes only `e2e-`-labeled token rows (never TRUNCATE — shared dev DB); shared-chat row cleaned in `finally`
- Next pending task: `08_regression_sweep_commit.md` (full pipeline + README auth section + atomic commit)
@@ -0,0 +1,81 @@
........................................................................ [ 4%]
........................................................................ [ 8%]
........................................................................ [ 13%]
........................................................................ [ 17%]
........................................................................ [ 22%]
........................................................................ [ 26%]
........................................................................ [ 30%]
........................................................................ [ 35%]
........................................................................ [ 39%]
........................................................................ [ 44%]
........................................................................ [ 48%]
........................................................................ [ 53%]
........................................................................ [ 57%]
........................................................................ [ 61%]
........................................................................ [ 66%]
........................................................................ [ 70%]
........................................................................ [ 75%]
........................................................................ [ 79%]
........................................................................ [ 84%]
........................................................................ [ 88%]
........................................................................ [ 92%]
........................................................................ [ 97%]
........................................... [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 40 0 100%
app/api/chat.py 178 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 11 0 100%
app/api/sync.py 101 0 100%
app/api/tokens.py 28 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 45 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/core/tokens.py 33 0 100%
app/db.py 21 0 100%
app/main.py 62 0 100%
app/models.py 94 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 231 0 100%
-----------------------------------------------
TOTAL 3136 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
@@ -0,0 +1,13 @@
All green. Final report:
**Phase 79, Task 08 — Regression sweep (commit owned by harness per pipeline rules)**
- Full suite: `uv run pytest --cov=app --cov-report=term` → **1627 passed, 99% coverage** (>90%; token deltas at 100%)
- `uv run ruff check . && uv run pyright` → clean (0 errors)
- E2E spot-checks, each in isolation: `test_api_tokens.py` 9✓, `test_admin_auth.py` 6✓, `test_share_chat.py` 4✓ (anonymous), `test_chat_rag.py` 3✓, `test_nav_switch_keeps_stream.py` 4✓, `test_smoke.py` 3✓
- Real-browser + real-LLM verification (3 fresh windows, live server on :8085): admin generated a token in the Tokens view (plaintext shown once, gone after reload); private window passed the gate, chatted, opened a cited doc, **reloaded without re-entry** (cached token); anonymous window met the gate yet opened a shared chat; admin revoked → user's next question 401'd live and a fresh login got the generic invalid-token error; token user 403'd on `/api/docs`, `/api/chats`
- README: new **API tokens** subsection under Admin & sign-in (generate/use/scope/revoke) + fixed two stale "viewer stays public" statements and the whoami/who-can-do-what tables
- `.env.example` unchanged — no new settings added (verified); dev-DB verification artifacts cleaned (tokens + 2 test chats removed)
- No defects found in prior tasks; no code changes needed
- Per harness rules: no `git add`/`commit`, phase dir left in `todo/` for the harness
- Next pending: phase 80 (`.agents/phases/todo/80_*/`), which builds on the now-gated `/api/suggestions`
@@ -0,0 +1,81 @@
........................................................................ [ 4%]
........................................................................ [ 8%]
........................................................................ [ 13%]
........................................................................ [ 17%]
........................................................................ [ 22%]
........................................................................ [ 26%]
........................................................................ [ 30%]
........................................................................ [ 35%]
........................................................................ [ 39%]
........................................................................ [ 44%]
........................................................................ [ 48%]
........................................................................ [ 53%]
........................................................................ [ 57%]
........................................................................ [ 61%]
........................................................................ [ 66%]
........................................................................ [ 70%]
........................................................................ [ 75%]
........................................................................ [ 79%]
........................................................................ [ 84%]
........................................................................ [ 88%]
........................................................................ [ 92%]
........................................................................ [ 97%]
........................................... [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 40 0 100%
app/api/chat.py 178 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 11 0 100%
app/api/sync.py 101 0 100%
app/api/tokens.py 28 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 45 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/core/tokens.py 33 0 100%
app/db.py 21 0 100%
app/main.py 62 0 100%
app/models.py 94 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 231 0 100%
-----------------------------------------------
TOTAL 3136 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
@@ -0,0 +1,13 @@
All completion criteria verified green — no defects found, no fixes needed. Final report:
**Phase 80 (history_suggestion_chips) — final verification pass: ALL GREEN**
- Verified implementation: `last_questions()` + seed-fallback in `app/api/suggestions.py` (100% cov); `startNewChat()` refetch via `loadSuggestions()` in `app.js`; seed-semantics docs in `config.py` docstring, `.env.example:55`, README L114/L823; integration state matrix in `test_suggestions_api.py`; E2E suite rewritten to the 4 states + carried-over story behavior.
- `uv run pytest tests/e2e/test_suggestion_chips.py -v --no-cov` → **7 passed** (isolated).
- `uv run pytest --cov=app --cov-report=term-missing` → **1637 passed**, TOTAL **99%** (>90%).
- `uv run ruff check . && uv run pyright` → **All checks passed / 0 errors, 0 warnings**.
- Criterion: E2E isolated green → ✅.
- Criterion: deflection unchanged → ✅ (`app/rag/suggestions.py` no diff vs HEAD; `test_honest_deflection.py` 3 passed; 100% cov).
- Criterion: full suite + coverage + lint/types → ✅ (above).
- Criterion: atomic commit + phase dir move → deferred per harness override: all changes left uncommitted in the working tree; task files already in `complete/80_history_suggestion_chips/`; harness commits and moves the phase dir.
- Notable: none — no defects found in prior work.
- Next pending phase: **none** (phase 80 is the last in the roadmap; `todo/` contains only this phase).
@@ -0,0 +1,81 @@
........................................................................ [ 4%]
........................................................................ [ 8%]
........................................................................ [ 13%]
........................................................................ [ 17%]
........................................................................ [ 21%]
........................................................................ [ 26%]
........................................................................ [ 30%]
........................................................................ [ 35%]
........................................................................ [ 39%]
........................................................................ [ 43%]
........................................................................ [ 48%]
........................................................................ [ 52%]
........................................................................ [ 57%]
........................................................................ [ 61%]
........................................................................ [ 65%]
........................................................................ [ 70%]
........................................................................ [ 74%]
........................................................................ [ 79%]
........................................................................ [ 83%]
........................................................................ [ 87%]
........................................................................ [ 92%]
........................................................................ [ 96%]
..................................................... [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 40 0 100%
app/api/chat.py 178 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 29 0 100%
app/api/sync.py 101 0 100%
app/api/tokens.py 28 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 45 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/core/tokens.py 33 0 100%
app/db.py 21 0 100%
app/main.py 62 0 100%
app/models.py 94 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 231 0 100%
-----------------------------------------------
TOTAL 3154 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
@@ -0,0 +1,10 @@
**Task 01 complete** — `/api/suggestions` now returns the last 3 questions asked, seed before the first.
- `app/api/suggestions.py`: module-level `last_questions(db, limit=3)` — `saved_chats` walked `updated_at DESC, created_at DESC`, each `bor.chat.v1` message list walked in reverse, `who=="user"` non-blank trimmed texts, exact case-sensitive dedup, cap 3; endpoint returns `qs if qs else get_settings().suggestions` (phase-79 `require_user` gate kept, `db` dep now used).
- New `tests/integration/test_suggestions_api.py` (10 tests): empty DB → seed; 4-q chat → `[q4,q3,q2]`; distinct-`updated_at` chat order; verbatim cross-chat dedup; case-sensitive dedup; exactly-2 and exactly-1 chips (no seed top-up); brain/blank-skip + all-brain → seed; anonymous → 401.
- Fixed stale pre-change pins in `tests/integration/test_api.py` (shape pin no longer assumes ≥3; env-override pin now conditioned on empty `saved_chats`, documented as the module's DB exception).
- `app/rag/suggestions.py` (deflection "Maybe try") untouched; its suites green.
Results: `uv run pytest --cov=app --cov-report=term` → 1637 passed, app/ coverage 99% (suggestions.py 100%); `uv run ruff check .` clean; `uv run pyright` → 0 errors.
No deviations. Dev DB left as found (saved_chats empty). Next pending task: `02_seed_semantics_docs.md`.
@@ -0,0 +1,81 @@
........................................................................ [ 4%]
........................................................................ [ 8%]
........................................................................ [ 13%]
........................................................................ [ 17%]
........................................................................ [ 21%]
........................................................................ [ 26%]
........................................................................ [ 30%]
........................................................................ [ 35%]
........................................................................ [ 39%]
........................................................................ [ 43%]
........................................................................ [ 48%]
........................................................................ [ 52%]
........................................................................ [ 57%]
........................................................................ [ 61%]
........................................................................ [ 65%]
........................................................................ [ 70%]
........................................................................ [ 74%]
........................................................................ [ 79%]
........................................................................ [ 83%]
........................................................................ [ 87%]
........................................................................ [ 92%]
........................................................................ [ 96%]
..................................................... [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 40 0 100%
app/api/chat.py 178 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 29 0 100%
app/api/sync.py 101 0 100%
app/api/tokens.py 28 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 45 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/core/tokens.py 33 0 100%
app/db.py 21 0 100%
app/main.py 62 0 100%
app/models.py 94 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 231 0 100%
-----------------------------------------------
TOTAL 3154 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
@@ -0,0 +1,15 @@
Task 02 complete. All four doc sites updated (docs-only, no behavior change):
- **`app/config.py`** — comment block above `suggestions: list[str]` now documents the SEED semantics: shown only while no saved chat has ever asked a question; afterwards `GET /api/suggestions` serves the last 3 questions (deployment-wide, newest first); `BOR_SUGGESTIONS` overrides the seed. Default list untouched.
- **`.env.example:55`** — comment now reads "JSON seed chips — shown only before any question has been saved (phase 80)".
- **`README.md` env-table row** — "JSON seed for the onboarding chips — shown only before the first saved question; afterwards the chips are the last 3 questions asked (phase 80)".
- **`README.md` chat feature description** — one sentence added: onboarding chips follow the last 3 questions asked, seeding from `BOR_SUGGESTIONS` on a fresh deployment. Deflection note and access-table row left as-is (unchanged contracts).
**Verification:**
- `uv run pytest --cov=app --cov-report=term-missing` → 1637 passed, `app/` coverage **99%** (>90% gate)
- `uv run ruff check .` → All checks passed
- `uv run pyright` → 0 errors, 0 warnings
**Notable:** no defects found; deflection contract (`app/rag/suggestions.py` + suites) green and untouched. The e2e story-suite rewrite for the new chip semantics remains deferred to task 04 per the phase plan (phase-76 precedent), so the old-contract e2e pins are expected to go until then.
**Next pending task:** `03_chips_new_chat_refetch.md` (app.js refetch on New chat + source pin).
@@ -0,0 +1,81 @@
........................................................................ [ 4%]
........................................................................ [ 8%]
........................................................................ [ 13%]
........................................................................ [ 17%]
........................................................................ [ 21%]
........................................................................ [ 26%]
........................................................................ [ 30%]
........................................................................ [ 35%]
........................................................................ [ 39%]
........................................................................ [ 43%]
........................................................................ [ 48%]
........................................................................ [ 52%]
........................................................................ [ 57%]
........................................................................ [ 61%]
........................................................................ [ 65%]
........................................................................ [ 70%]
........................................................................ [ 74%]
........................................................................ [ 79%]
........................................................................ [ 83%]
........................................................................ [ 87%]
........................................................................ [ 92%]
........................................................................ [ 96%]
..................................................... [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 40 0 100%
app/api/chat.py 178 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 29 0 100%
app/api/sync.py 101 0 100%
app/api/tokens.py 28 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 45 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/core/tokens.py 33 0 100%
app/db.py 21 0 100%
app/main.py 62 0 100%
app/models.py 94 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 231 0 100%
-----------------------------------------------
TOTAL 3154 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
@@ -0,0 +1,19 @@
Task 03 complete — all gates green.
**Implemented**
- `frontend/assets/app.js`: `startNewChat()` (the `bor:new-chat` handler) now calls `loadSuggestions()` right after the empty state is restored — the onboarding chips refetch `/api/suggestions` so the row reflects the current last-3 state, never the stale boot fetch. Progressive enhancement unchanged (swallows 401/network failures); boot path untouched; in-flight-turn guard still exempts a live turn.
- `tests/unit/test_chat_persistence.py`: source pin in `test_new_chat_clears_key_and_ui` — containment assertion that `loadSuggestions()` appears in the `startNewChat` function-body slice (house style, two literals).
**Test / lint / coverage**
- `node --check frontend/assets/app.js` — valid JS
- `uv run pytest -q` (unit + integration) — exit 0, all passed
- `uv run pytest --cov=app --cov-report=term-missing` — TOTAL **99%** (>90% floor)
- `uv run ruff check .` — All checks passed; `uv run pyright` — 0 errors
- `uv run pytest tests/unit/test_chat_persistence.py::test_new_chat_clears_key_and_ui` — passed
**Decisions / notes**
- Pin placed in `test_chat_persistence.py` (the existing `startNewChat` body-slice pin — the file the task names first).
- `.agents/PLAN.md` is absent from the repo; the phase files carry the binding decisions (no deviation).
- E2E refetch assertion belongs to task 04's suite (per task file); no other files touched. My diff is only the 2 files above (working tree also carries uncommitted harness work from phases 77–79, left as-is).
**Next pending task:** `.agents/phases/todo/80_history_suggestion_chips/04_e2e_suite_commit.md`
@@ -0,0 +1,215 @@
........................................................................ [ 4%]
........................................................................ [ 8%]
........................................................................ [ 13%]
........................................................................ [ 17%]
........................................................................ [ 21%]
........................................................................ [ 26%]
........................................................................ [ 30%]
........................................................................ [ 35%]
........................................................................ [ 39%]
........................................................................ [ 43%]
........................................................................ [ 48%]
........................................................................ [ 52%]
........................................................................ [ 57%]
........................................................................ [ 61%]
........................................................................ [ 65%]
........................................................................ [ 70%]
........................................................................ [ 74%]
........................................................................ [ 79%]
........................................................................ [ 83%]
........................................................................ [ 87%]
........................................................................ [ 92%]
........................................................................ [ 96%]
..................................................... [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 40 0 100%
app/api/chat.py 178 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 29 0 100%
app/api/sync.py 101 0 100%
app/api/tokens.py 28 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 45 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/core/tokens.py 33 0 100%
app/db.py 21 0 100%
app/main.py 62 0 100%
app/models.py 94 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 231 0 100%
-----------------------------------------------
TOTAL 3154 12 99%
coverage gate: app/ 99% (>90%) OK
I001 [*] Import block is un-sorted or un-formatted
--> tests/e2e/test_toggle_after_nav.py:2:1
|
1 | """Quick diagnostic: does the toggle still work after SPA navigation?"""
2 | / import pytest
3 | | from playwright.sync_api import Browser, expect
| |_______________________________________________^
4 |
5 | def test_toggle_after_spa_nav(browser, app_url, db_ready):
|
help: Organize imports
|
4 |
5 +
6 | def test_toggle_after_spa_nav(browser, app_url, db_ready):
|
F401 [*] `pytest` imported but unused
--> tests/e2e/test_toggle_after_nav.py:2:8
|
1 | """Quick diagnostic: does the toggle still work after SPA navigation?"""
2 | import pytest
| ^^^^^^
3 | from playwright.sync_api import Browser, expect
|
help: Remove unused import: `pytest`
|
1 | """Quick diagnostic: does the toggle still work after SPA navigation?"""
- import pytest
2 | from playwright.sync_api import Browser, expect
|
F401 [*] `playwright.sync_api.Browser` imported but unused
--> tests/e2e/test_toggle_after_nav.py:3:33
|
1 | """Quick diagnostic: does the toggle still work after SPA navigation?"""
2 | import pytest
3 | from playwright.sync_api import Browser, expect
| ^^^^^^^
4 |
5 | def test_toggle_after_spa_nav(browser, app_url, db_ready):
|
help: Remove unused import: `playwright.sync_api.Browser`
|
2 | import pytest
- from playwright.sync_api import Browser, expect
3 + from playwright.sync_api import expect
4 |
|
W293 [*] Blank line contains whitespace
--> tests/e2e/test_toggle_after_nav.py:15:1
|
13 | timeout=10_000,
14 | )
15 |
| ^^^^^^^^
16 | # Open the menu
17 | page.click("#nav-toggle")
|
help: Remove whitespace from blank line
|
14 | )
-
15 +
16 | # Open the menu
|
W293 [*] Blank line contains whitespace
--> tests/e2e/test_toggle_after_nav.py:19:1
|
17 | page.click("#nav-toggle")
18 | expect(page.locator("#nav-toggle")).to_have_attribute("aria-expanded", "true")
19 |
| ^^^^^^^^
20 | # Navigate to a different view via SPA
21 | page.click("#nav-sources")
|
help: Remove whitespace from blank line
|
18 | expect(page.locator("#nav-toggle")).to_have_attribute("aria-expanded", "true")
-
19 +
20 | # Navigate to a different view via SPA
|
W293 [*] Blank line contains whitespace
--> tests/e2e/test_toggle_after_nav.py:23:1
|
21 | page.click("#nav-sources")
22 | expect(page).to_have_url(app_url + "/sources.html", timeout=15_000)
23 |
| ^^^^^^^^
24 | # Menu should be closed
25 | expect(page.locator("#nav-toggle")).to_have_attribute("aria-expanded", "false")
|
help: Remove whitespace from blank line
|
22 | expect(page).to_have_url(app_url + "/sources.html", timeout=15_000)
-
23 +
24 | # Menu should be closed
|
W293 [*] Blank line contains whitespace
--> tests/e2e/test_toggle_after_nav.py:26:1
|
24 | # Menu should be closed
25 | expect(page.locator("#nav-toggle")).to_have_attribute("aria-expanded", "false")
26 |
| ^^^^^^^^
27 | # NOW try to open the menu again - THIS IS THE BUG CHECK
28 | page.click("#nav-toggle")
|
help: Remove whitespace from blank line
|
25 | expect(page.locator("#nav-toggle")).to_have_attribute("aria-expanded", "false")
-
26 +
27 | # NOW try to open the menu again - THIS IS THE BUG CHECK
|
E501 Line too long (104 > 100)
--> tests/e2e/test_toggle_after_nav.py:30:101
|
28 | page.click("#nav-toggle")
29 | try:
30 | expect(page.locator("#nav-toggle")).to_have_attribute("aria-expanded", "true", timeout=3000)
| ^^^^
31 | print("PASS: Toggle still works after SPA navigation")
32 | except Exception as e:
|
Found 8 errors.
[*] 7 fixable with the `--fix` option.
0 errors, 0 warnings, 0 informations
validation FAILED (see output above)
@@ -0,0 +1,15 @@
Everything is green — the only "error" match is pyright's "0 errors" line.
**Fixed**
- `tests/e2e/test_toggle_after_nav.py` (stray diagnostic file, pre-existing lint debt that failed the harness gate): removed unused imports (`pytest`, `Browser`), fixed the isort order (third-party `playwright` block, then first-party `e2e.auth_helpers` — house style), stripped whitespace-only lines, wrapped the >100-char `expect(...)` line. Preserved the file's current semantics (phase-79 `login(page, app_url, next="/")` admin wait — the variant present on disk).
**Test / lint / coverage**
- `bash .agents/validate.sh` (the harness gate) — **exit 0, "validation OK"**
- Inside it: `uv run pytest --cov=app` — all unit + integration passed; TOTAL **99%** (>90% floor)
- `uv run ruff check .` — All checks passed; `uv run pyright` — 0 errors
**Decisions / notes**
- The diagnostic file was being updated concurrently (a parallel process overwrote my first fix with the `login`-helper variant); I lint-cleaned that on-disk variant instead and re-validated immediately — final state verified green.
- Task-03 work (app.js `loadSuggestions()` in `startNewChat()` + the source pin in `test_chat_persistence.py`) remains in the tree, untouched and green.
**Next pending task:** `.agents/phases/todo/80_history_suggestion_chips/04_e2e_suite_commit.md`
@@ -0,0 +1,81 @@
........................................................................ [ 4%]
........................................................................ [ 8%]
........................................................................ [ 13%]
........................................................................ [ 17%]
........................................................................ [ 21%]
........................................................................ [ 26%]
........................................................................ [ 30%]
........................................................................ [ 35%]
........................................................................ [ 39%]
........................................................................ [ 43%]
........................................................................ [ 48%]
........................................................................ [ 52%]
........................................................................ [ 57%]
........................................................................ [ 61%]
........................................................................ [ 65%]
........................................................................ [ 70%]
........................................................................ [ 74%]
........................................................................ [ 79%]
........................................................................ [ 83%]
........................................................................ [ 87%]
........................................................................ [ 92%]
........................................................................ [ 96%]
..................................................... [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 40 0 100%
app/api/chat.py 178 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 29 0 100%
app/api/sync.py 101 0 100%
app/api/tokens.py 28 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 45 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/core/tokens.py 33 0 100%
app/db.py 21 0 100%
app/main.py 62 0 100%
app/models.py 94 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 231 0 100%
-----------------------------------------------
TOTAL 3154 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
@@ -0,0 +1,17 @@
Task 04 complete — all gates green. Report:
**Implemented**
- Rewrote `tests/e2e/test_suggestion_chips.py` (7 tests) to the phase-80 contract: **seed** state (chips == exact `SEED` literal copied from the `Settings.suggestions` default, role=list/buttons/style pins), **last-3** (2 chats via `POST /api/chats`, 5 questions, older saved first → fresh load shows exactly the 3 newest, newest-first), **partial** (2 saved questions → exactly 2 chips, no seed top-up), **new-chat refetch** (click `#new-chat-btn` → chips become Q + a second `GET /api/suggestions` in the request log, timestamped after the click) — plus carried-over one-tap submit, Tab+Enter keyboard walk, and the mobile single-scroll-row contract.
- Added an autouse `clean_chats` fixture truncating `saved_chats` before/after every test — this also fixed pre-existing defects I found: the old suite (unmodified by prior tasks) was already failing 2/4 because auto-saved chat rows leaked between tests and the new DB-derived contract turned them into 1 deduped chip.
**Results**
- `uv run pytest tests/e2e/test_suggestion_chips.py -v --no-cov` → 7 passed (green in isolation, repeated runs stable)
- `uv run pytest --cov=app --cov-report=term-missing` → 1637 passed, **99%** on `app/` (`app/api/suggestions.py` 100%)
- `uv run ruff check .` → clean; `uv run pyright` → 0 errors
- Deflection untouched: `app/rag/suggestions.py` has no diff; `uv run pytest tests/e2e/test_honest_deflection.py --no-cov` → 3 passed
**Notes**
- No `git add`/`commit` and no phase-dir move performed — the harness owns the atomic commit and the move (overrides the task's commit step).
- Playwright's `APIRequestContext.post` in this version has no `json=` kwarg — JSON is sent via `data=` + `Content-Type: application/json`.
**Next pending task:** none — task 04 was the final task; phase 80 is complete, awaiting the harness commit.
@@ -0,0 +1,81 @@
........................................................................ [ 4%]
........................................................................ [ 8%]
........................................................................ [ 13%]
........................................................................ [ 17%]
........................................................................ [ 21%]
........................................................................ [ 26%]
........................................................................ [ 30%]
........................................................................ [ 35%]
........................................................................ [ 39%]
........................................................................ [ 43%]
........................................................................ [ 48%]
........................................................................ [ 52%]
........................................................................ [ 57%]
........................................................................ [ 61%]
........................................................................ [ 65%]
........................................................................ [ 70%]
........................................................................ [ 74%]
........................................................................ [ 79%]
........................................................................ [ 83%]
........................................................................ [ 87%]
........................................................................ [ 92%]
........................................................................ [ 96%]
..................................................... [100%]
=============================== warnings summary ===============================
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
from starlette.testclient import TestClient as TestClient # noqa
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
Name Stmts Miss Cover
-----------------------------------------------
app/__init__.py 1 0 100%
app/api/__init__.py 0 0 100%
app/api/auth.py 40 0 100%
app/api/chat.py 178 0 100%
app/api/chats.py 110 0 100%
app/api/config.py 7 0 100%
app/api/doc_drafts.py 93 0 100%
app/api/docs.py 50 0 100%
app/api/git_sources.py 212 0 100%
app/api/health.py 10 0 100%
app/api/steering.py 42 0 100%
app/api/suggestions.py 29 0 100%
app/api/sync.py 101 0 100%
app/api/tokens.py 28 0 100%
app/config.py 140 0 100%
app/core/__init__.py 0 0 100%
app/core/auth.py 45 0 100%
app/core/caching.py 108 0 100%
app/core/debugging.py 29 2 93%
app/core/docs_push.py 39 0 100%
app/core/logging.py 13 0 100%
app/core/tokens.py 33 0 100%
app/db.py 21 0 100%
app/main.py 62 0 100%
app/models.py 94 0 100%
app/rag/__init__.py 0 0 100%
app/rag/agent.py 222 0 100%
app/rag/archive_upload.py 128 0 100%
app/rag/chunker.py 206 4 98%
app/rag/git_sources.py 14 0 100%
app/rag/importer.py 180 3 98%
app/rag/llm.py 216 0 100%
app/rag/overview.py 71 0 100%
app/rag/prompts.py 88 0 100%
app/rag/retriever.py 150 3 98%
app/rag/scaffolding.py 55 0 100%
app/rag/source_removal.py 41 0 100%
app/rag/sources_meta.py 16 0 100%
app/rag/suggestions.py 27 0 100%
app/rag/summarizer.py 24 0 100%
app/schemas.py 231 0 100%
-----------------------------------------------
TOTAL 3154 12 99%
coverage gate: app/ 99% (>90%) OK
All checks passed!
0 errors, 0 warnings, 0 informations
validation OK
+134
View File
@@ -0,0 +1,134 @@
---
name: restore-test-db
description: Restores the large 1,000-document test knowledge base (the live-KB replica snapshot at data/bor_live_kb_replica.dump.sql) into the app's Postgres database in one atomic transaction — wipes the database's current contents, no re-embedding, no app code changes. Use when the user asks to restore the test database, the 1,000-document KB, or the big/live replica KB (e.g. "restore the test database", "put the 1000-doc KB back"). Do not confuse with the 8-document tool-calling fixture KB, which scripts/restore_test_kb.py restores.
---
# Restore the 1,000-Document Test KB
Load `data/bor_live_kb_replica.dump.sql` (a full 99 MB `pg_dump` —
schema **and** data — of the live KB replica) into the app database
(`brain_of_reese`, per `BOR_DATABASE_URL` in `.env`). The embeddings come
with the dump, so there is **no re-embedding, no git clone, no app code
change** — the whole known state (documents, chunks + embeddings, source
registry, KB overview, alembic version) lands in a few seconds.
The dump is a local artifact: `data/` is gitignored and the dump is
**never committed**. It is not in git — if it is missing on a fresh
clone, stop and ask the user for it (it can only be made by `pg_dump`ing
the live/`bor_eval` KB).
## Rules (non-negotiable)
- **The restore is destructive** — it wipes the current contents of the
app database. Always take the disposable backup in step 2 first, even
when the database looks empty.
- **Do not edit app code** (`app/`, `tests/`, `alembic/`). This skill
swaps database contents, nothing else.
- **Run `psql` inside the db container** (`brain-of-reese_db_1`), not
with a host `psql` — the container's psql is 17.x, matching the dump
(newer dumps use the `\restrict` metacommand that old host psqls
choke on).
- This KB and the 8-document tool-calling fixture KB
(`scripts/restore_test_kb.py`) share the same database and overwrite
each other. Say which one is loaded in the summary.
## Procedure
All commands run from the repo root.
### 1. Preconditions (fast fail)
```bash
podman compose up -d db
ls -lh data/bor_live_kb_replica.dump.sql # must exist, ~99 MB
```
Check the dump's schema version against the current head:
```bash
grep -A1 '^COPY public.alembic_version' data/bor_live_kb_replica.dump.sql | tail -1
uv run alembic heads
```
The two must match (the 2026-09-05 snapshot carries `0011`). If the dump
is **older** than head, proceed and run `uv run alembic upgrade head`
after step 3. If the dump is **newer** than head, the local code is
behind — stop and flag it to the user.
### 2. Disposable safety backup
Seconds, in `/tmp` (never committed):
```bash
podman exec brain-of-reese_db_1 pg_dump -U reese brain_of_reese -Fc \
> /tmp/pre_restore_$(date +%Y%m%d_%H%M%S).dump
```
### 3. Reset the schema, load the dump (one transaction)
```bash
podman exec brain-of-reese_db_1 psql -U reese -d brain_of_reese -c \
"DROP SCHEMA public CASCADE; CREATE SCHEMA public; GRANT ALL ON SCHEMA public TO PUBLIC;"
podman exec -i brain-of-reese_db_1 psql -U reese -d brain_of_reese \
-v ON_ERROR_STOP=1 --single-transaction -q -f /dev/stdin \
< data/bor_live_kb_replica.dump.sql
```
Why the schema reset: the dump contains plain `CREATE TABLE` (no
`IF NOT EXISTS`, no `--clean`), so it can only load into an empty
`public` schema. Why `--single-transaction` + `ON_ERROR_STOP`: a failed
load rolls back instead of leaving a half-loaded KB. Known failure mode:
the `DROP SCHEMA` is its own committed statement, so a failed load leaves
an **empty** schema — that is fine, fix the cause and re-run step 3
(the reset is idempotent).
### 4. Verify the fingerprint
```bash
podman exec brain-of-reese_db_1 psql -U reese -d brain_of_reese -tAc \
"select 'docs='||count(*) from documents
union all select 'chunks='||count(*) from chunks
union all select 'sources='||string_agg(distinct source, ',' order by source) from documents
union all select 'alembic='||(select version_num from alembic_version);"
```
Expected for the 2026-09-05 snapshot: **docs=1000, chunks=8866,
sources=deploy,homelab,homepage,ServMon,skills,vibe-bot, alembic=0011**.
If any number differs, the dump file is a different snapshot — report the
actual numbers to the user; do not guess.
### 5. Dev-server check (only if it is running)
```bash
pgrep -af "uvicorn app.main" && curl -s http://localhost:8000/api/health
```
Expect `"db":"up"` — the SQLAlchemy pool reconnects fine across the
schema reset, so no restart is normally needed. If the server misbehaves
after the restore, restart it (`uv run uvicorn app.main:app --reload`).
### 6. Summary
One line per verified number (docs / chunks / sources / alembic), the
wall time, and a reminder of which KB is loaded: this is the
**1,000-document live replica**; the 8-document tool-calling fixture is
brought back separately with `uv run python -m scripts.restore_test_kb`.
## Troubleshooting
- **`database unreachable` / connection refused** — the db container is
down; `podman compose up -d db`, then re-run from step 1.
- **`relation "public.<table>" already exists`** — the schema was not
reset (or a previous load failed after the reset rolled back); re-run
the `DROP SCHEMA` command from step 3.
- **`psql: /dev/stdin:N: unknown command '\restrict'`** — you ran a host
`psql`; use the container's, per the rules.
- **load succeeds but counts are wrong** — compare the dump's
`alembic_version` (step 1) and `documents` row count
(`sed -n '/^COPY public.documents/,/^\\./p' data/bor_live_kb_replica.dump.sql | wc -l`,
minus 2) against the loaded numbers; a mismatch means the dump was
truncated in transfer — ask the user for the original file.
- **`schema not applied`-style errors from the app afterwards** — run
`uv run alembic current`; if the dump was older than head, step 1's
`alembic upgrade head` was the missing piece.
+1 -1
View File
@@ -52,7 +52,7 @@ BOR_RRF_K=60 # Reciprocal Rank Fusion damping constant
# your own (e.g. md,sh,toml) or narrow it (e.g. md). A blank list or a
# malformed token (e.g. md,sh!) fails startup loudly, naming the value.
BOR_IMPORT_EXTENSIONS=md,markdown,txt,yaml,yml,json,py,container,network,volume,image,pod,kube,swap,os,endpoint,j2
# BOR_SUGGESTIONS=["How is my Kubernetes cluster set up?"] # JSON list of onboarding chips
# BOR_SUGGESTIONS=["How is my Kubernetes cluster set up?"] # JSON seed chips — shown only before any question has been saved (phase 80)
# --- Import sources (git; phase 28, admin-managed since phase 35) ---
# Comma-separated git repo URLs; import_docs clones each (first run) or
+1
View File
@@ -16,6 +16,7 @@ COPY frontend ./
RUN mkdir -p /out/assets \
&& esbuild ./assets/app.js --bundle --minify --format=esm --target=es2022 --outfile=/out/assets/app.js \
&& esbuild ./assets/router.js --bundle --minify --format=esm --target=es2022 --outfile=/out/assets/router.js \
&& esbuild ./assets/token-gate.js --bundle --minify --format=esm --target=es2022 --outfile=/out/assets/token-gate.js \
&& esbuild ./assets/document.js --bundle --minify --format=esm --target=es2022 --outfile=/out/assets/document.js \
&& esbuild ./assets/login.js --bundle --minify --format=esm --target=es2022 --outfile=/out/assets/login.js \
&& esbuild ./assets/shared.js --bundle --minify --format=esm --target=es2022 --outfile=/out/assets/shared.js \
+56 -15
View File
@@ -108,7 +108,10 @@ uv run uvicorn app.main:app --reload
- **Chat** (`/`) — ask questions; answers stream in with **source chips**
that cite the exact documents used. Clicking a chip opens that document
in an **almost-fullscreen modal** on the same page (no new tab).
in an **almost-fullscreen modal** on the same page (no new tab). The
onboarding suggestion chips follow the last 3 questions asked — on a
fresh deployment (no saved questions yet) they seed from
`BOR_SUGGESTIONS`.
- **Document viewer** — the modal above *is* the viewer; the full text of
any indexed document is served from the database (no filesystem access):
markdown is rendered, every other format (`yaml`, `json`, `py`, `txt`, …)
@@ -119,7 +122,8 @@ uv run uvicorn app.main:app --reload
- **Sources** (`/sources.html`) — the indexed document list; the *Path*
column opens each document in the same **almost-fullscreen modal** (no
new tab). **Admin-only** — anonymous visitors see a sign-in gate instead
(the catalog is what the login locks; the document viewer itself stays
(the catalog is what the admin login locks; the document viewer asks
for an access token — see [API tokens](#api-tokens). Shared chats stay
open to everyone).
- **Git sources** (`/git-sources.html`) — the admin-managed source
registry: the git repositories the **Sync sources** button clones and
@@ -214,10 +218,12 @@ unchanged.
## Admin & sign-in
Brain of Reese has exactly **one account: the admin (you)**. Signing in
unlocks the **full Sources catalog** and the **answer-tuning** controls;
everyone else stays anonymous and keeps **chat** and the **document
viewer** (any document an answer cites can be opened by its direct URL —
the catalog is gated, not the viewer).
unlocks the **full Sources catalog**, the **answer-tuning** controls, and
**token management**. **Shared chats** are the only content that stays
open to anonymous visitors; everyone in between gets an **API token** —
the admin generates one in the **Tokens** view and hands it out, and the
holder enters it at the in-app gate to use chat and open the documents
answers cite (see [API tokens](#api-tokens) below).
### Setup (one-time)
@@ -247,8 +253,14 @@ Set the missing variable(s): BOR_ADMIN_PASSWORD, BOR_SESSION_SECRET …
message — no user enumeration, there is only one user).
- `POST /api/logout` → `204` (session cleared and cookie expired;
idempotent for anonymous callers).
- `GET /api/whoami` → `{"authenticated": bool, "role": "admin"|"anonymous"}`
— the single source of truth for every UI gating decision.
- `GET /api/whoami` → `{"authenticated": bool, "role": "admin"|"user"|"anonymous"}`
— the single source of truth for every UI gating decision
(`authenticated` is true for **both** admin and token users; the UI
gates on `role === "admin"`).
- `POST /api/token-auth {"token": "bor_…"}` → `204` + the **same signed
cookie** (role `user`); malformed / unknown / revoked all get one
generic `401` `{"detail": "invalid token"}` — no enumeration. The two
roles coexist in one session; **Sign out** clears both at once.
- Cookie flags: `same_site="lax"`, `https_only` off — **no HTTPS
enforcement on purpose** (homelab HTTP; the cookie is single-admin
convenience, not a cloud boundary). Max age `BOR_SESSION_MAX_AGE`
@@ -258,12 +270,41 @@ Set the missing variable(s): BOR_ADMIN_PASSWORD, BOR_SESSION_SECRET …
### Who can do what
| Capability | Anonymous | Admin (signed in) |
|---|---|---|
| Chat (`/`) + suggestion chips | yes | yes |
| Document viewer (`/document.html?source=…&path=…`) | yes — any indexed doc by direct URL | yes |
| Sources catalog (`/sources.html`, `GET /api/docs`) | sign-in gate | full catalog |
| Tuning (Tune button, Tuning panel, `/api/steering`) | UI hidden | full |
| Capability | Anonymous | Token user | Admin (signed in) |
|---|---|---|---|
| Shared chats (`/shared/<token>`) | yes | yes | yes |
| Chat (`/`) + suggestion chips | token gate | yes | yes |
| Document viewer (`/document.html?source=…&path=…`) | token gate | yes | yes |
| Sources catalog (`/sources.html`, `GET /api/docs`) | sign-in gate | 403 | full catalog |
| Tuning (Tune button, Tuning panel, `/api/steering`) | UI hidden | 403 | full |
| Saved-chat history (`/history.html`) | sign-in gate | no History view | full |
### API tokens
The admin can hand out access without sharing the admin password.
- **Generate:** sign in as admin → **Tokens** in the navbar
(`/tokens.html`). Type a label (e.g. `alice`) and hit **Generate** — a
`bor_` + 32-hex token appears in the *shown once* block. Copy it now:
only its SHA-256 hash is stored, so the plaintext can never be
retrieved again.
- **Use:** the holder opens the app (or a direct document URL) and gets
the **token gate** instead of content — they enter the token, and the
browser caches it in `localStorage["bor.token"]`, silently re-sending
it on every page load (no re-entry on reloads or new tabs). **Sign
out** clears the cache; a failed silent re-auth (e.g. a revoked token)
drops the cached copy and shows the gate again. Private-mode browsers
still work — the gate just can't cache.
- **Scope:** a token user can chat (with suggestion chips) and open the
documents answers cite — nothing else. The Sources catalog, git
sources, tuning, document drafts, and the saved-chat history stay
admin-only (`403` on their APIs; no History view). **Shared chats
stay open to everyone** — the only anonymous content.
- **Revoke:** **Revoke** on the token's row (inline two-step confirm).
Revocation is **immediate**: the token's next request — including a
fresh login attempt — is refused with the same generic `401` as a bad
token. Revoked tokens stay in the list, marked **Revoked**, with their
last used time.
The public API endpoints stay stateless — the signed cookie is the only
session state in the system.
@@ -779,7 +820,7 @@ served locally (no CDN), `BOR_ENVIRONMENT=production`.
| `BOR_SUMMARY_MAX_CHARS` | `12000` | cap on document content sent to the `lite` summary model at import (see *Document summaries*) |
| `BOR_KB_OVERVIEW_MAX_CHARS` | `4000` | char budget for the `<knowledge_base>` (KB overview) prompt section |
| `BOR_OVERVIEW_INPUT_MAX_CHARS` | `40000` | cap on the document list sent to the `lite` model when generating the KB overview |
| `BOR_SUGGESTIONS` | built-in list | JSON list of onboarding chips |
| `BOR_SUGGESTIONS` | built-in list | JSON seed for the onboarding chips — shown only before the first saved question; afterwards the chips are the last 3 questions asked (phase 80) |
| `BOR_ADMIN_PASSWORD` | *(required)* | the single admin's password (plaintext, `.env`); app refuses to start when empty |
| `BOR_SESSION_SECRET` | *(required)* | signing key for the `bor_session` cookie; `python -c 'import secrets;print(secrets.token_hex(32))'` |
| `BOR_SESSION_MAX_AGE` | `43200` | session-cookie lifetime in seconds (12 h, sliding) |
+73
View File
@@ -0,0 +1,73 @@
"""api_tokens: admin-issued access tokens (phase 79)
Revision ID: 0012
Revises: 0011
Create Date: 2026-09-07
Phase 79 (API tokens: the admin generates named tokens and hands them
out so people can sign in to the app — the ONLY anonymous content left
is the shared chats; extends the phase-16 single-admin auth, one
additive, reversible table, no other schema change, A13):
* ``api_tokens`` — one row per admin-generated token (``bor_`` + 32 hex
chars — ``secrets.token_hex(16)``, service in task 02). The
**plaintext exists only in the 201 create response, returned exactly
once**; the row never carries it.
* ``token_hash`` — the stored credential: the SHA-256 hex digest of the
**full** token string (String(64) — the ``documents.content_hash``
precedent). Hashing the full string, not the suffix, so a stripped
prefix can never collide. Lineage of ``saved_chats.share_token`` /
``doc_drafts.token`` — but HASHED: unlike those unguessable
``uuid4`` link tokens these are long-lived hand-out credentials, and
a leaked database must not hand anyone working tokens.
* ``ix_api_tokens_token_hash`` — UNIQUE index on ``token_hash``: the
lookup key (a unique-index hit — sha256's pre-image resistance means
there is no enumeration surface beyond the lookup). The explicit
``op.create_index`` shape of ``ix_saved_chats_share_token`` (0009)
and ``ix_doc_drafts_token`` (0011).
* ``label`` — String(120) NOT NULL: the hand-out name (e.g. "alice") —
display-only: no index, not unique (two tokens may share a label).
* ``created_at`` — TIMESTAMPTZ NOT NULL, server default now().
* ``last_used_at`` / ``revoked_at`` — TIMESTAMPTZ NULL: bumped/set by
the service (tasks 02/03); NULL until the token is first used /
revoked.
"""
from __future__ import annotations
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
from alembic import op
revision = "0012"
down_revision = "0011"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.create_table(
"api_tokens",
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
sa.Column("label", sa.String(length=120), nullable=False),
sa.Column("token_hash", sa.String(length=64), nullable=False),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
server_default=sa.func.now(),
nullable=False,
),
sa.Column("last_used_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("revoked_at", sa.DateTime(timezone=True), nullable=True),
)
# The hash is the stored credential — the unique lookup handle (the
# saved_chats.share_token / doc_drafts.token unique-index precedent,
# phases 51/59).
op.create_index("ix_api_tokens_token_hash", "api_tokens", ["token_hash"], unique=True)
def downgrade() -> None:
# Safe order: drop the token-hash index first, then the table (A13 —
# fully reversible, no other schema change).
op.drop_index("ix_api_tokens_token_hash", table_name="api_tokens")
op.drop_table("api_tokens")
+86 -19
View File
@@ -1,24 +1,42 @@
"""Auth API — single-admin sign-in (phase 16; A10 revised 2026-08-22).
"""Auth API — admin sign-in (phase 16) + token login (phase 79).
* ``POST /api/login`` — 204 + signed session cookie on success; 401
``invalid password`` on any mismatch (constant-time, one generic
message, no session set).
* ``POST /api/logout`` — 204; clears the session and expires the cookie
(idempotent for anonymous callers).
* ``POST /api/token-auth`` — 204 + signed session cookie for a valid,
unrevoked API token (PUBLIC — it is the token holders' login route);
every failure shape (malformed / unknown / revoked / empty) is ONE
generic 401 ``invalid token`` (no enumeration, the phase-16 pattern).
* ``POST /api/logout`` — 204; clears the session and expires the
cookie (idempotent for anonymous callers — one logout wipes BOTH
roles, the session is one dict).
* ``GET /api/whoami`` — ``{"authenticated": bool, "role":
"admin"|"anonymous"}``; the single source of truth for all UI gating.
"admin"|"user"|"anonymous"}``; the single source of truth for all UI
gating (phase 79: the third role; the UI's admin-only surfaces key
off ``role === "admin"`` specifically).
The public API otherwise stays stateless (A10): chat, the document
content endpoint (soft rule — anonymous may open any document by direct
URL), suggestions, and health never require the cookie.
The rest of the public surface is the phase-79 contract: health,
config, whoami, login, token-auth, and the shared chats stay
anonymous — chat, suggestions, and the document viewer content are
user-gated (``require_user``).
"""
from __future__ import annotations
from fastapi import APIRouter, HTTPException, Request, Response
from fastapi import APIRouter, Depends, HTTPException, Request, Response
from sqlalchemy.orm import Session
from app.config import get_settings
from app.core.auth import ADMIN_SESSION_KEY, check_password, sign_in, sign_out
from app.schemas import LoginRequest, WhoamiResponse
from app.core import tokens as token_service
from app.core.auth import (
ADMIN_SESSION_KEY,
USER_SESSION_KEY,
USER_TOKEN_ID_KEY,
check_password,
sign_in,
sign_out,
)
from app.db import get_db
from app.schemas import LoginRequest, TokenAuthRequest, WhoamiResponse
router = APIRouter(tags=["auth"])
@@ -39,14 +57,48 @@ def login(payload: LoginRequest, request: Request) -> Response:
raise HTTPException(status_code=401, detail="invalid password")
@router.post("/token-auth", status_code=204)
def token_auth(
payload: TokenAuthRequest,
request: Request,
db: Session = Depends(get_db), # noqa: B008
) -> Response:
"""Sign a token holder in (phase 79 — PUBLIC: this IS the login).
A valid, unrevoked token → 204 + the signed ``bor_session`` cookie
(the same SessionMiddleware mechanism as ``/api/login``): the
session carries the ``user`` key plus the token's row id
(``user_token_id``), and ``require_user``'s live row check enforces
revocation from the holder's very next request. The token's
``last_used_at`` is stamped here (the admin's Tokens view shows the
login as the last use).
EVERY failure shape — malformed, unknown, revoked, empty — is ONE
generic 401 ``invalid token``: the lookup is by hash (the service
returns ``None`` for anything that is not an active row), so there
is no enumeration surface (the phase-16 pattern).
"""
token = payload.token.strip()
row = token_service.find_active_by_token(db, token) if token else None
if row is None:
raise HTTPException(status_code=401, detail="invalid token")
token_service.mark_used(row)
db.commit()
request.session[USER_SESSION_KEY] = True
request.session[USER_TOKEN_ID_KEY] = str(row.id)
return Response(status_code=204)
@router.post("/logout", status_code=204)
def logout(request: Request, response: Response) -> Response:
"""Sign out: clear the session AND expire the browser cookie.
``sign_out`` empties the session dict (which the middleware does not
re-persist — an empty session has nothing to sign), so this route also
sends ``delete_cookie`` to make the browser drop the signed cookie
right now. Idempotent: an anonymous logout is still a 204.
re-persist — an empty session has nothing to sign), so this route
also sends ``delete_cookie`` to make the browser drop the signed
cookie right now. One logout wipes BOTH roles (admin and token —
the session is one dict). Idempotent: an anonymous logout is still
a 204.
"""
sign_out(request.session)
response.delete_cookie(get_settings().session_cookie, path="/")
@@ -55,9 +107,24 @@ def logout(request: Request, response: Response) -> Response:
@router.get("/whoami", response_model=WhoamiResponse)
def whoami(request: Request) -> WhoamiResponse:
"""Who is the caller? Drives every UI gating decision (phase 16)."""
authenticated = bool(request.session.get(ADMIN_SESSION_KEY))
return WhoamiResponse(
authenticated=authenticated,
role="admin" if authenticated else "anonymous",
)
"""Who is the caller? Drives every UI gating decision.
Three roles (phase 79): ``admin`` (the signed-in admin — wins when
the browser holds BOTH an admin and a token session), ``user`` (a
token holder), ``anonymous``. ``authenticated`` is true for admin
AND user; the UI's admin-only surfaces key off ``role === "admin"``
specifically, not off ``authenticated``.
This endpoint only reads the session keys — it does NOT live-check
the token row. A token session whose row was just revoked still
reports ``user`` here until the next gated request: ``require_user``
then pops the dead keys, after which this endpoint reports
anonymous (the phase-79 live-enforcement contract).
"""
if request.session.get(ADMIN_SESSION_KEY):
role = "admin"
elif request.session.get(USER_SESSION_KEY):
role = "user"
else:
role = "anonymous"
return WhoamiResponse(authenticated=role != "anonymous", role=role)
+8 -1
View File
@@ -149,6 +149,7 @@ from sqlalchemy.orm import Session
from app.api.steering import load_steering_notes
from app.config import Settings, get_settings
from app.core.auth import require_user
from app.db import db_available, get_db
from app.models import Document, QueryLog
from app.rag.agent import (
@@ -296,10 +297,16 @@ def plan_turn(
@router.post("/chat")
async def chat(
request: ChatRequest,
_user: None = Depends(require_user), # noqa: B008 # phase 79: admin or live token
db: Session = Depends(get_db), # noqa: B008
llm: LLMClient = Depends(get_llm), # noqa: B008
):
"""One chat turn: SSE stream of ``delta`` events + a final ``done``."""
"""One chat turn: SSE stream of ``delta`` events + a final ``done``.
User-gated (phase 79): anonymous callers get 401 ``authentication
required`` before any streaming — the ONLY anonymous content is the
shared chats.
"""
if not db_available():
return JSONResponse(
status_code=503,
+17 -11
View File
@@ -19,7 +19,7 @@ from sqlalchemy import func, select
from sqlalchemy.orm import Session
from app.api.sync import _sanitize_error
from app.core.auth import require_admin
from app.core.auth import require_admin, require_user
from app.db import get_db
from app.models import Chunk, Document
from app.rag.llm import EmbeddingError, LLMClient
@@ -42,11 +42,12 @@ def list_indexed_documents(
) -> DocList:
"""All indexed documents with per-document chunk counts.
Admin-only (phase 16 — the catalog is what the sign-in gates; the
document viewer itself stays public, see below). Anonymous callers
get 403 ``admin only`` and the Sources page renders its sign-in gate
instead. An empty list means the knowledge base has not been imported
yet — the Sources page renders its designed empty state in that case.
Admin-only (phase 16 — the catalog is what the admin sign-in gates;
the document viewer below is user-gated since phase 79, the shared
chats being the only anonymous surface). Anonymous callers get 403
``admin only`` and the Sources page renders its sign-in gate instead.
An empty list means the knowledge base has not been imported yet —
the Sources page renders its designed empty state in that case.
"""
rows = db.execute(
select(
@@ -78,7 +79,10 @@ def list_indexed_documents(
@router.get("/documents/content", response_model=DocContent)
def get_document_content(
source: str, path: str, db: Session = Depends(get_db) # noqa: B008
source: str,
path: str,
db: Session = Depends(get_db), # noqa: B008
_user: None = Depends(require_user), # noqa: B008 # phase 79: admin or live token
) -> DocContent:
"""Full content of one indexed document, looked up by ``(source, path)``.
@@ -86,10 +90,12 @@ def get_document_content(
strings such as ``../../etc/passwd`` — are just non-existent rows and
map to 404 ``{detail: "document not found"}``.
Deliberately PUBLIC for anonymous callers (phase 16 soft rule, owner
decision 2026-08-22): the *catalog* (``GET /api/docs``) is what the
sign-in gates, not the viewer — chat cites documents and anyone may
open a cited document by direct URL.
User-gated (phase 79 — SUPERSEDES the phase-16 "deliberately PUBLIC
(soft rule)" note, owner decision 2026-08-22): the viewer content is
token-or-admin like the rest of the app surface — chat cites
documents and a signed-in user (admin or token holder) opens a cited
document by direct URL. The ONLY anonymous content left is the
shared chats.
"""
row = db.execute(
select(Document, func.count(Chunk.id).label("chunks"))
+3 -2
View File
@@ -10,8 +10,9 @@ into the system prompt of **every** chat turn as the ``<tuning>`` section
replacement of ``note``, ``created_at`` preserved), ``DELETE /{note_id}``.
The whole router sits behind :func:`app.core.auth.require_admin` —
anonymous callers get 403 on every steering route (the chat turn itself
reads the table in-process and stays public); the PUT route adds no auth
surface of its own, it reuses that router-level dependency.
reads the table in-process; since phase 79 that turn is user-gated
through ``require_user``); the PUT route adds no auth surface of its
own, it reuses that router-level dependency.
"""
from __future__ import annotations
+78 -4
View File
@@ -1,14 +1,88 @@
"""Suggested-question endpoint (drives the onboarding chips in the UI)."""
"""Suggested-question endpoint (drives the onboarding chips in the UI).
Phase 80: the chips are the **last 3 questions asked** — the three
most recent user questions across ALL saved chats (chats walked
newest-``updated_at`` first, each chat's messages walked newest-first,
exact de-duplicated, cap 3 — see :func:`last_questions`). A fresh
deployment — zero saved questions — gets the seed list instead
(``BOR_SUGGESTIONS`` override, or the built-in default).
Phase 79: user-gated (``require_user``) — the chips are part of the app
surface (chat, suggestions, cited documents); the ONLY anonymous
content is the shared chats (the phase-16 "everything but the admin
surface is open" stance is superseded).
The deflection "Maybe try" chips (``app.rag.suggestions.
derive_suggestions``) are a SEPARATE contract (title-derived, carried
in the chat response) and are untouched by this endpoint.
"""
from __future__ import annotations
from fastapi import APIRouter
from fastapi import APIRouter, Depends
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.config import get_settings
from app.core.auth import require_user
from app.db import get_db
from app.models import SavedChat
from app.schemas import SuggestionList
router = APIRouter(tags=["chat"])
def last_questions(db: Session, limit: int = 3) -> list[str]:
"""The ``limit`` most recent user questions, across all saved chats.
Chats are walked ``updated_at DESC, created_at DESC`` (the
tiebreak keeps the order deterministic when timestamps collide);
each chat's ``messages`` (a JSONB column that deserializes to a
plain Python list of ``bor.chat.v1`` dicts — NO SQL JSON ops
needed, the record shape is the ``SavedChat.messages`` model
docstring) is walked in REVERSE (conversational order is
oldest→newest), collecting the whitespace-trimmed ``text`` of
every entry with ``who == "user"``. Blank texts are skipped.
De-duplication is EXACT (case-sensitive) against the collected
window: a verbatim re-ask counts once, while a legitimately
differently-cased re-ask is kept (case-insensitive dedup would
drop it). The walk stops once ``limit`` UNIQUE texts are
collected; the result is in encounter order (newest first).
Pure-DB helper (unit-testable without the endpoint); returns
``[]`` when no saved question exists (the caller then falls back
to the seed list).
"""
result: list[str] = []
seen: set[str] = set()
for chat in db.scalars(
select(SavedChat).order_by(
SavedChat.updated_at.desc(), SavedChat.created_at.desc()
)
):
for m in reversed(chat.messages or []):
if m.get("who") != "user":
continue
question = str(m.get("text", "")).strip()
if not question or question in seen:
continue
seen.add(question)
result.append(question)
if len(result) >= limit:
return result
return result
@router.get("/suggestions", response_model=SuggestionList)
def suggestions() -> SuggestionList:
return SuggestionList(suggestions=get_settings().suggestions)
def suggestions(
_user: None = Depends(require_user), # noqa: B008 # phase 79: admin or live token
db: Session = Depends(get_db), # noqa: B008
) -> SuggestionList:
"""The onboarding chips (admin OR token user, else 401): the last 3
questions asked across saved chats — or, before any question has
ever been saved, the seed list (``BOR_SUGGESTIONS`` / the
built-in default). The deflection "Maybe try" chips are a separate
contract (``app.rag.suggestions.derive_suggestions``), untouched.
"""
qs = last_questions(db)
return SuggestionList(suggestions=qs if qs else get_settings().suggestions)
+112
View File
@@ -0,0 +1,112 @@
"""Tokens admin API (phase 79, task 02).
The admin surface for the issued access tokens (owner-locked A4):
generate a named token (the plaintext is shown **exactly once**, in the
201 body), list tokens (display fields only — no plaintext, no hashes),
and revoke one (idempotent). The whole router sits behind
:func:`app.core.auth.require_admin` (router-wide ``dependencies`` — the
:mod:`app.api.doc_drafts` pattern): tokens are admin-only, so anonymous
callers get 403 on every route — and once the token-auth login lands
(task 03), a token *user* stays 403 here too (only the admin manages
tokens).
Routes (all under ``/api`` via the ``main`` registration):
``POST /api/tokens`` (201 ``TokenCreated`` — the ONE response shape
that carries the plaintext ``token``), ``GET /api/tokens``
(``TokenList`` — newest first, secret-free), ``POST
/api/tokens/{token_id}/revoke`` (204, idempotent; unknown id → 404
``token not found``).
"""
from __future__ import annotations
import uuid
from fastapi import APIRouter, Depends, HTTPException, Response
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.core import tokens as token_service
from app.core.auth import require_admin
from app.db import get_db
from app.models import ApiToken
from app.schemas import TokenCreated, TokenCreateRequest, TokenList, TokenListItem
router = APIRouter(
prefix="/tokens",
tags=["tokens"],
dependencies=[Depends(require_admin)], # phase 79: the token admin surface is admin-only
)
@router.post("", response_model=TokenCreated, status_code=201)
def create_token(
payload: TokenCreateRequest,
db: Session = Depends(get_db), # noqa: B008
) -> TokenCreated:
"""Generate one named token (201).
The ``token`` field of this response is the ONE AND ONLY moment the
plaintext exists on the wire (A4): the row stores the SHA-256 hash
of the full token string, and no other response shape — in
particular the list — ever carries it. ``label`` is the hand-out
name, display-only and NOT unique (two tokens may share a label).
Blank/over-long labels are a 422 from the schema (the house
``ValueError`` pattern — fail loud at the boundary).
"""
row, plaintext = token_service.create_token(db, payload.label)
db.commit() # the service flushes; the endpoint owns the commit
db.refresh(row) # pulls the server-default created_at
return TokenCreated(
id=row.id, label=row.label, token=plaintext, created_at=row.created_at
)
@router.get("", response_model=TokenList)
def list_tokens(
db: Session = Depends(get_db), # noqa: B008
) -> TokenList:
"""All tokens, newest first (``created_at desc, id desc`` tiebreak).
Secret-free by construction: :class:`~app.schemas.TokenListItem` has
no ``token`` and no ``token_hash`` field — the list never carries a
credential in either form. ``revoked`` is derived from
``revoked_at is not None``; ``last_used_at`` stays null until the
token is first used (task 03 stamps it on ``POST /api/token-auth``)
"""
rows = (
db.execute(
select(ApiToken).order_by(ApiToken.created_at.desc(), ApiToken.id.desc())
)
.scalars()
.all()
)
items = [
TokenListItem(
id=row.id,
label=row.label,
created_at=row.created_at,
last_used_at=row.last_used_at,
revoked=row.revoked_at is not None,
)
for row in rows
]
return TokenList(tokens=items)
@router.post("/{token_id}/revoke", status_code=204)
def revoke_token(
token_id: uuid.UUID,
db: Session = Depends(get_db), # noqa: B008
) -> Response:
"""Revoke one token (204) — idempotent.
Already-revoked → still 204 with NO re-stamp (the original
``revoked_at`` — the revocation time — is preserved; the service
only stamps when unset). Unknown id → 404 ``token not found`` (one
message for every unknown id). Revocation takes effect immediately:
the holder's next request is refused (the task-03 live check).
"""
if not token_service.revoke(db, token_id):
raise HTTPException(status_code=404, detail="token not found")
db.commit()
return Response(status_code=204)

Some files were not shown because too many files have changed in this diff Show More