feat(P03): live surfaces — read-models, Reading Room fidelity, standalone client #19
Reference in New Issue
Block a user
Delete Branch "phase/03-live-surfaces"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
REQ-76..82 + REQ-75 delegation-end. Disk read-models (library/connections/graph/tasks/suggestions — registry-independent, P-8 full closure), report.json/evidence.json artifacts at DONE (marker normalization [p-N]→[^pN] at the boundary, report.md raw), enriched report endpoint with graceful pre-artifact fallback, refine on the per-user planner chain (honest 503), profile routes. Frontend: GeneratedApiClient STANDALONE (extends MockApiClient dropped — D-112), every surface live-bound with zero component rewrites, G-109 display-honest inbox, Reading Room parity with real hover quotes, P-12 export fabrication dead. Verify: PASS-WITH-FIXES (CSRF test pin P0 fixed), 314 backend + 164 frontend tests. Ships as v0.3.3.
Structured artifacts (T-3.1, D-113): engine/artifacts.py builds report.json {v, run_id, owner_id, title, question, methodology_slug, generated_at, sections:[{heading, body}], citations:[{passage_id, url, title, quote, health, authority_tier}]} and evidence.json {v, run_id, passages:[{passage_id, url, title, quote, text}]} from the run's EvidenceStore + ledger, persisted in _verify_phase right after ledger/checklist — the only point where the per-actor store still exists (it dies with the actor; D-114/D-115 disk read-models can never rebuild it). Marker normalization at the persistence boundary ONLY (C3-10): section bodies [p-N] -> [^pN] and ids p-N -> pN (the Reading Room parses [A-Za-z0-9]+ only — [^p-1] does not match). report.md keeps the raw [p-N] form: trust-loop regexes (anchoring, ledger, _cited_urls) untouched, pinned by test. Citations carry real stored quotes (<=200 chars); evidence carries full passage texts. ---ci--- phase: 3 milestone: v0.3 status: execute persona: backend-engineer task: T-3.1 requirements: [REQ-82] ---/ci---api/read_models.py (T-3.2, D-114/D-115 — REQ-76..79): pure functions over runs/<owner_id>/ disk artifacts, NEVER the in-memory RunRegistry. list_runs() scans the owner partition (state.json + report.json/ledger.json where present) skipping dotfiles/.tmp defensively; corrupt records are skipped, never fatal. Payloads shaped EXACTLY to the frontend types (types.ts): - library(sort) -> {folders:[{id,name,description}], reports:[ReportSummary camelCase]} — auto-categorization is deterministic + dumb: methodology folder + first topic token of the plan sub-questions (deep-report-battery); only DONE runs are filed. - connections() -> shared-source edges (>=1 ledger URL overlap) + related-topic edges (same methodology + shared category), older -> newer. - graph() -> ProjectGraph {nodes,edges} with linkedSources from real ledger URLs. - tasks() -> InboxItem[] with '<run_id>#<idx>' ids, done IFF the run reached DONE (D-114 — post-sweep CANCELLED stays open). - suggestions() -> SuggestedQuery[] — open items joined with source run titles, NEVER synthetic, empty for fresh users. 22 unit tests over a two-user fixture tree (DONE/CANCELLED/FAILED runs, dotfile/tmp/orphan leftovers): categorization, sorting, edge rules, answered-iff-DONE, cross-user isolation at the read-model level. ---ci--- phase: 3 milestone: v0.3 status: execute persona: backend-engineer task: T-3.2 requirements: [REQ-76, REQ-77, REQ-78, REQ-79] ---/ci---T-3.3 (REQ-76..82) on the protected router, all owner-scoped via the guard: - GET /api/runs/{id}/report reworked: DISK-READ registry-independent (P-8 full closure — the owner's run dir is the existence check, no RunRegistry) -> {run_id, markdown, report, evidence}; pre-artifact DONE runs fall back to markdown + nulls, never a 500. /ledger likewise disk-read. - GET /api/runs: RunSummary[] from disk state files, recent first — post-sweep CANCELLED runs surface cancelled, NOT open. - GET /api/library?sort=recent|title, /api/library/connections, /api/graph, /api/tasks, /api/suggestions: read-model routes (disk only, D-115), cross-user 404/empty by owner-scoping. - POST /api/refine/questions {draft} -> {questions:[2-3]} and /api/refine/compose {draft, answers} -> {question, context} via the planner on the SAME per-user credential chain (P2 factory); AdapterError -> honest 503, never a hang; CSRF header applies. - GET/PUT /api/profile -> {user_id, display_name, email} (users store update_profile: email unique across other users, 409 on conflict; no deletion). 15 route tests incl. cross-user 404, pre-artifact fallback, refine 503, profile round-trip, and the FRESH-APP-OVER-EXISTING-DATA-ROOT test (second create_app, zero in-memory actors — every read route correct: P-8 full-closure proof). ---ci--- phase: 3 milestone: v0.3 status: execute persona: backend-engineer task: T-3.3 requirements: [REQ-76, REQ-77, REQ-78, REQ-79, REQ-80, REQ-81, REQ-82] ---/ci------ci---: phase: 3 milestone: v0.3 status: execute persona: frontend-engineer task: T-3.7 requirements: [G-005 snapshot discipline] notes: > Regenerated frontend/openapi.json from create_app().openapi() — the P3 live-surface routes (/api/library, /api/library/connections, /api/graph, /api/tasks, /api/suggestions, /api/refine/questions, /api/refine/compose, /api/profile, enriched /api/runs/{id}/report) are all in the snapshot; openapi-typescript regenerated src/lib/api/generated/schema.d.ts from the committed snapshot only (never a live server); check:api-drift green; typecheck + full vitest suite (131) green on the regenerated client.---ci---: phase: 3 milestone: v0.3 status: execute persona: frontend-engineer task: T-3.8 requirements: [REQ-75 delegation-end half, REQ-26, REQ-54] notes: > Drop "extends MockApiClient" — a standalone class implementing ApiClient + RunLifecycle; every previously-delegated surface now hits its real endpoint: library/connections/graph/tasks/suggestions (read-model payloads mapped 1:1 through the same frontend types), refine questions/compose (real POSTs with honest 503 error states), profile (GET /api/profile -> DisplayProfile), connection status (GET /api/config -> five ConnectionSlot shape — closes the P2-verify flag), v0.1 Settings composed live from config+catalog reads, core loop (createRun/getRun/approve/cancel/pause/resume), session surface. Request discipline enforced at ONE injection point: private #live wrapper injects credentials:"include" on EVERY call + X-ResearchX-Request on EVERY mutation — closes the P1-verify P2 flag about core-loop mutations missing credentials/CSRF. EventSource constructed with {withCredentials: true}. getReport maps report.json -> Report (structured sections with [^pN] markers — zero report-body.ts changes; real-quote citations, tier map official->1/reference->2/else->3, site=URL host) with the markdown-only fallback for pre-artifact DONE runs. getLedger joins ledger entries with evidence.json/report.json passages for REAL quotes. exportReport builds from the served markdown + real ledger entries (P-12 fabrication killed: real claims_supported/health/tier/quotes per entry). snake->camel dial mapping for methodology cards (the one live-client contract-test assertion updated to the honest full DialValues shape — no component test touched). MockApiClient UNCHANGED (standalone dev fixture). Compile-time assertion: const _satisfies: ApiClient & RunLifecycle = new GeneratedApiClient(). All 131 tests + tsc + lint green.---ci---: phase: 3 milestone: v0.3 status: execute persona: frontend-engineer task: T-3.9 requirements: [REQ-76, REQ-77, REQ-78, REQ-79, REQ-80, REQ-81, REQ-54, G-109] notes: > Surface swaps with ZERO component rewrites: library-view feeds (folders/sorting), connections panel, project graph, task inbox, ask suggestions, refine dialogue (real POSTs with honest 503 error state), profile, and the connection-status panel (getConnectionStatus live override mapping GET /api/config through the five-slot ConnectionSlot shape — the P2-verify flag closes). G-109: the task-inbox checkbox becomes DISPLAY-HONEST in live mode — status is derived (answered-iff run DONE), the checkbox is disabled + annotated, a click never flips it; mock keeps the free toggle byte-for-byte (the unchanged library-view suite proves it). Live-mode component suite pins: G-109 (4 cases incl. the mock-parity toggle), refine 503 honesty, the real model-driven exchange end-to-end, live suggestions click-fills-never- auto-starts, honest fresh-user empty state. Live-surface boundary suite (15 cases) pins every swap against verbatim read-model payloads with cookie+CSRF discipline assertions. All 154 tests green (131 pre-existing UNCHANGED), tsc + lint green.---ci---: phase: 3 milestone: v0.3 status: execute persona: frontend-engineer task: T-3.10 requirements: [REQ-82, REQ-59, REQ-34, P-12 closure] notes: > Parity suite: the REAL ReadingRoom component over a fixture LIVE payload (the enriched GET /api/runs/{id}/report envelope + ledger.json, verbatim shapes) — structured sections render with [^pN] markers (zero report-body.ts changes, C3-10 pinned); marker focus reveals the EXACT stored quoted passage with title/site/tier (official->1); the ledger panel lists real entries with real quotes + health; the markdown-only fallback for pre-artifact DONE runs renders honestly (no fabricated sections/citations, never a 500). Export pins: markdown carries the served report.md verbatim (raw [p-N] intact) + a real-quote citation block; JSON carries REAL claims_supported (2 and 1 — not the fabricated all-1s), real health/tier per entry, real stored quotes, the real checklist — and a test pins the ABSENCE of the fabricated claims_supported:1 dump pattern and of empty quotes (P-12 closure). Boundary contract pins: report.json -> Report mapping with the tier map and site=URL host; ledger p-N -> pN id normalization joined with stored quotes; the DONE envelope memoized per run (report+ledger+export share one network read). All 164 tests green (131 pre-existing UNCHANGED), tsc + lint green.---ci--- phase: 3 milestone: v0.3 status: verify ---ci--- The P1-verify flag ('live-client core-loop mutations lack credentials include + CSRF header') was closed in code by T-3.8's #live wrapper but no test PINNED it on the core loop itself — session/refine/settings boundary suites carried the pins, createRun and the four lifecycle mutations did not. The stub upgrade (body+headers+credentials capture, mirroring live-surfaces.test.ts) makes the discipline assertable at every mutation site: createRun and approve/cancel/pause/resume now each fail if credentials:'include' or X-ResearchX-Request goes missing.