---ci---
phase: 3
milestone: v0.3
status: verify
verdict: PASS-WITH-FIXES
layers:
structural: PASS
behavioral: PASS
security: PASS
quality: PASS-WITH-FLAGS
p0_fixed: "b803be9 — core-loop mutations (createRun/approve/cancel/pause/resume) credentials+CSRF pinned by test (code had it via the #live wrapper; tests now assert it)"
requirements:
covered: [REQ-75, REQ-76, REQ-77, REQ-78, REQ-79, REQ-80, REQ-81, REQ-82]
partial: []
flags_for_p5:
- "P1: split_sections not fence-aware — ATX heading inside a code block splits a section (v0.4 fidelity)"
- "P2: library/connections/graph re-scan the tree independently (3x per Library load — fine at scale, memoize in v0.4)"
- "P2: ruff format --check fails tree-wide (pre-existing, not a gate) — adopt or record as not-a-gate"
- "P3: PLAN T-1.11 wording backend/openapi.json vs frontend/openapi.json convention — reconcile"
- "P3: in-flight run questions appear as open suggestions until refetch (accepted per D-114)"
---/ci---
Final gates: backend 314 passed + 3 skipped; frontend 164 passed;
XSS-clean (no dangerouslySetInnerHTML); Reading Room parity pinned;
P-8 full closure proven (fresh app over existing data root); P-12
pinned absent; G-109 display-honest inbox.
---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.
---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: 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.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.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.
Read-model suite now covers the full T-3.11 list: the fixture tree
gains an in-flight (SEARCHING) run — tasks/suggestions surface it open
alongside CANCELLED/FAILED (D-114: done-iff-DONE only); a new route
test drives a run to DONE over HTTP and pins the marker contract end
to end (report.json bodies [^pN], markdown raw [p-N], citations carry
real quotes, evidence full texts, library/tasks derive from the same
disk artifacts).
Gates: 314 passed + ruff + mypy strict, all green.
---ci---
phase: 3
milestone: v0.3
status: execute
persona: backend-engineer
task: T-3.11
requirements: [REQ-76, REQ-77, REQ-78, REQ-79, REQ-82]
---/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---
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---
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---