Two cosmetic bugs surfaced by the successful v0.8.3 pipeline run:
1. **'produced Invalid Date'** — research_outcomes.created_at was
an OffsetDateTime serialized by time's default array format
(`[y, ordinal, hh, mm, ss, ns, tz]`), which browser's
`new Date(...)` can't parse. Add `#[serde(with =
"time::serde::rfc3339")]` matching the pattern already in
threads.rs / routine_runs.rs.
2. **Stale error text on pipeline card** — the runs stage's
`latest_error` walked every run by `created_at DESC` and
returned the first non-empty error, so a topic with an earlier
failed run + a later completed run kept displaying the old
error next to '1 completed'. Now the error only surfaces when
the MOST RECENT run itself failed. Historical failures stay in
the run count but don't leak their message.
Three connected changes that turn "Start research" from a status
flip into a real pipeline that produces a reviewable artifact:
- Migration 0036: adds research_topics.topology_kind (default
'hub_spoke') and a new research_outcomes table
(id, topic_id, version DESC, body_md, produced_by_run_id, created_at)
so each run's final synthesis is versioned and persistent.
- Wizard now has a topology picker in the Outcome step —
hub_spoke / pipeline / hierarchical / star_moe — with copy that
steers users to the right shape (Pipeline for research → distill
→ analyze → implement rosters, hub_spoke for the coordinator-
and-specialists default).
- start_topic reads the chosen topology_kind, parses it into a
cm_topology::TopologyKind, and dispatches a per-shape coordinator
prompt via build_coordinator_task. Pipeline explicitly tells
stage 1 not to write the final artifact and propagates a
"final stage MUST emit a complete markdown document with
measurable acceptance criteria" instruction downstream. The
graph builder is called with the topology the user actually
picked instead of hard-coded HubSpoke.
- topology_worker::freeze_research_outcome fires after every
successful complete(). It looks up research_topic_id on the run;
if set and final_output is non-empty, it inserts a new
research_outcomes row (version auto-derived server-side via
coalesce(max(version), 0) + 1). Best-effort — a DB hiccup logs
but doesn't fail the run.
- TopicDetail now includes topology_kind and latest_outcome.
ResearchCanvas swaps in the outcome's body_md (rendered as
pre-wrap markdown, versioned header, produced-at timestamp)
whenever an outcome exists; the original prompt collapses into
an "Original prompt" <details> below so it's still one click
away. Pre-run topics still show the description as before.
Follow-ups still open: reject-with-revision loop feeding the
coordinator, publishing → published transition + real artifact
export (md / pdf), and an approvals inbox surface for reviewers.