mcp: project-plan — petal-dispatch Phase 3 brief
This commit is contained in:
parent
8d10ce23dc
commit
86a423fe60
109
Sources/Dev/petal-dispatch-phase3-brief.md
Normal file
109
Sources/Dev/petal-dispatch-phase3-brief.md
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
---
|
||||||
|
created: '2026-06-05'
|
||||||
|
path: Sources/Dev
|
||||||
|
project: petal-dispatch-phase3-brief
|
||||||
|
tags:
|
||||||
|
- petal-dispatch
|
||||||
|
- lovebug
|
||||||
|
- phase-3
|
||||||
|
- web-client
|
||||||
|
- fastapi
|
||||||
|
- sse
|
||||||
|
- trellis
|
||||||
|
- postgres
|
||||||
|
- p4
|
||||||
|
- execution
|
||||||
|
type: project-plan
|
||||||
|
---
|
||||||
|
|
||||||
|
# petal-dispatch — Phase 3 — Web Client — Worker Brief
|
||||||
|
|
||||||
|
**Project:** petal-dispatch (p4) · **Thread:** 588 · **Repo:** `petal-power/petal-dispatch` (Gitea)
|
||||||
|
**Plan ref:** p4 Phase 3 · Satisfies FR-001, FR-003, FR-005, FR-006, FR-007, FR-012
|
||||||
|
**Structure:** Three sequential gated spawns (3a → 3b → 3c). Each lands a handoff to thread 588 before the next spawns.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
A functional web client for petal-dispatch — the first real client, deliberately before Android (p4 D-1), to prove **store → API → client → render, both directions**, on the web stack before committing to Kotlin. The load-bearing half is the **write loop** (client → orchestrator → echo back through the store), because that is the path Android copies; a read-only client would not prove what Phase 3 exists to prove.
|
||||||
|
|
||||||
|
**Phase 3 success bar:** Travis sends a message and gets a conversational response from the orchestrator, round-trip through the store (FR-005), rendered. **Orchestrator only — no worker spawn, no worker-completion dependency.** Worker-task surfaces and stall detection (SC-7) are out of Phase 3; no workers exist this phase.
|
||||||
|
|
||||||
|
The existing `static/index.html` (Phase 2 poll-based cards) is **reference-only, not a base.** Mine it for markup/render logic; ship fresh. Standard worker discipline (zero-check, a-review per handoff) applies per env instructions.
|
||||||
|
|
||||||
|
## Locked Decisions
|
||||||
|
|
||||||
|
**Store boundary (p4 D-22) — governs the whole phase.** Two stores by kind of truth. Journal = execution truth (events, handoffs). Trellis = context truth (threads, working picture). 3b is journal-only and survives Trellis-down. 3c is the only Trellis-backed surface. petal-dispatch's Phase 3 *code* reads Trellis; it does not write it (writes are synthesis, FR-013, Stage C — out of scope this phase).
|
||||||
|
|
||||||
|
**Transport (p4 D-6, with shipped-WS drift).** The Phase 3 web client targets SSE over the harness `/events/stream`. The app's shipped WebSocket (`/api/tasks/events`) is left in place untouched; Android repoints to REST+SSE in Phase 4. The existing v0 `/ws` chat endpoint is a separate question handled in 3a recon (see below).
|
||||||
|
|
||||||
|
**Auth.** `OPEN_MODE=1` for v1 — no auth, tailnet-only (D-14). No login wall. New routes follow the same `_check_auth` + `OPEN_MODE` pattern as Phase 1/2: when `OPEN_MODE=1`, auth no-ops; otherwise the existing identity-header check applies.
|
||||||
|
|
||||||
|
**Trellis read mechanism (3c).** petal-dispatch gets its **own dedicated DB role** on `petalbrain.trellis` — separate from `dispatch_journal`, which has no business reading Trellis. The role is provisioned **read/write** (forward-looking: synthesis at Stage C will need write to land context keyed by `thread_id`), but **Phase 3 code uses read (SELECT) only** — FR-012/D-22 forbid petal-dispatch writing Trellis in v1. "Trellis unavailable" for the graceful-degradation test therefore means "the DB query failed" — the same failure surface as every other petal-dispatch query, wrapped in try/except. Rejected: calling trellis-mcp over its HTTP transport (adds a hard runtime dependency — trellis-mcp down would take 3c with it).
|
||||||
|
|
||||||
|
**Rendering.** Render-on-final, keyed by the reconcile key (PD-issued UUID, D-10/D-12) — the same id-keyed path optimistic render layers onto later (Phase 5); do not foreclose it.
|
||||||
|
|
||||||
|
**Tap-to-switch is render-only this phase.** No orchestrator-side "active thread" repointing is wired; switching focus changes the highlighted tile, nothing more.
|
||||||
|
|
||||||
|
## Open Items
|
||||||
|
|
||||||
|
- **3a recon, input/transport (the `/ws` fork).** `main.py` already runs a WebSocket at `/ws` → `handle_ws(websocket, proc, bus)`, where `proc` is the Dispatch claude singleton — i.e. the orchestrator-only target Chat wants, already wired to the right backend. Recon must surface, not infer: does Chat (a) use the existing `/ws`, (b) add an HTTP POST + SSE path and coexist with / retire `/ws`? This brushes the p4 D-6 "SSE not WebSocket" line — reusing `/ws` is faster but deepens the WS drift p4 wanted gone; building POST+SSE is p4-faithful but more work. **This is a Travis call, not a worker call** — recon checkpoints the tradeoff before 3b's Chat transport is decided.
|
||||||
|
- **3a recon, input-path state.** Does a working client → REST → orchestrator-stdin → echo-via-`USER_MESSAGE` round-trip exist in v1, or was it only proven in the prototype? If unbuilt, 3a absorbs the FR-005 backend write path — surface as a scope finding at the checkpoint, do not silently expand.
|
||||||
|
- **3a recon, reconcile-key population rule.** Which event tiers/types carry a non-NULL `message_id` (reconcile key, D-12)? Chat-message rendering keys on it. (Fact lives in the Phase 0 schema + writer.)
|
||||||
|
- **3a recon, harness SSE wire contract.** Envelope shape, field names, event types on `/events/stream`, `?from_seq=N` backfill semantics.
|
||||||
|
- **Synthesis-richness seam (3c).** v1 renders native Trellis threads. When synthesis (Stage C) later writes a distilled working-context picture to Trellis (keyed by `thread_id`, migration-003 seam), the tiles should get richer with no client change. Build the render path to accommodate; do not build synthesis.
|
||||||
|
|
||||||
|
## Phases
|
||||||
|
|
||||||
|
### 3a — Backend: event bus + recon
|
||||||
|
|
||||||
|
**Recon first, then wire. Checkpoint findings before building if anything changes scope.** Lovebug indicates most of 3a is already in place — recon likely confirms the input path exists and returns the contract, leaving 3a light. Verify, don't assume.
|
||||||
|
|
||||||
|
**Drift paid down here:** Phase 1 shipped the extractor trigger in-process (fire-and-forget asyncio from `TaskManager`), not via LISTEN/NOTIFY as p4 D-6 specifies. 3a gets task/handoff events onto the bus so the SSE endpoint carries them. Without this, a client wired to `/events/stream` receives no task events.
|
||||||
|
|
||||||
|
Recon deliverables (answer before wiring):
|
||||||
|
- [ ] Input/transport `/ws` fork (see Open Items) — surfaced + checkpointed for Travis's transport call.
|
||||||
|
- [ ] Input → stdin path state (exists / built-in-3a / scope flag).
|
||||||
|
- [ ] Reconcile-key population rule across event types.
|
||||||
|
- [ ] Harness SSE wire contract (envelope, field names, event types, `?from_seq` backfill).
|
||||||
|
|
||||||
|
Wiring:
|
||||||
|
- [ ] Publish task lifecycle + handoff events onto LISTEN/NOTIFY → SSE so `/events/stream` carries real task events.
|
||||||
|
|
||||||
|
Handoff outputs (3b depends on these):
|
||||||
|
- [ ] Documented SSE wire contract · reconcile-key rule · input-path + `/ws` transport finding.
|
||||||
|
|
||||||
|
**3a done =** recon answered · task/handoff events on `/events/stream` with `?from_seq` backfill proven · input→stdin path confirmed-or-built · SSE contract + reconcile rule + transport decision in handoff.
|
||||||
|
|
||||||
|
### 3b — Web client (fresh)
|
||||||
|
|
||||||
|
Fresh client, `static/` reference-only. Served from `/static`, same Traefik route. `OPEN_MODE=1` — no login wall.
|
||||||
|
|
||||||
|
Two surfaces, both journal-backed — zero Trellis dependency (this is why 3b survives Trellis-down):
|
||||||
|
- [ ] **Chat** *(success-bar surface)* — source: journal round-trip. Send input → orchestrator conversational response → echo via `USER_MESSAGE` through the store (FR-005), rendered. **Orchestrator only, no worker spawn.** Input-during-run queued client-side with visible pending state (FR-007). This is what "done" is measured on. Transport per 3a's decided fork.
|
||||||
|
- [ ] **Activity** — source: journal event feed via SSE. Chronological event stream.
|
||||||
|
|
||||||
|
Rendering:
|
||||||
|
- [ ] Render-on-final, keyed by reconcile key (D-10/D-12) — same path optimistic render layers onto later (Phase 5).
|
||||||
|
- [ ] Consumes 3a's SSE contract over `/events/stream`. Survives reconnect (re-subscribe, re-render from store).
|
||||||
|
- [ ] Layout: logical, function over form. Design iteration happens later with Lovebug — iterate-shaped surface (p4), not a spec gate. No home-tab/default-tab lock.
|
||||||
|
|
||||||
|
**3b done =** message sent → orchestrator conversational response → round-trip through store (FR-005), rendered, orchestrator only no worker · Activity renders from SSE, survives reconnect · layout logical.
|
||||||
|
|
||||||
|
### 3c — Trellis threads/context surface
|
||||||
|
|
||||||
|
Completes FR-012 (the backend read surface p4 assigned to Phase 1, which under-delivered) and renders it. The only Trellis-backed surface in Phase 3.
|
||||||
|
|
||||||
|
- [ ] **Backend:** read-only `/api/trellis/...` over Trellis thread/context state, via the dedicated DB role on `petalbrain.trellis` (provisioned read/write, code uses SELECT only — see Locked Decisions). petal-dispatch reads Trellis; does not write it this phase.
|
||||||
|
- [ ] **Frontend:** thread/context tiles rendering native Trellis state — thread list, live thread highlighted, recent events, tap-to-switch-focus (render-only, SC-3 context switch ≤1 action). Lovebug's mockups are the starting point. Iterate-shaped.
|
||||||
|
- [ ] **Synthesis seam (accommodate, do not build):** render path built so Stage-C synthesized context drops in without a rewrite.
|
||||||
|
- [ ] **Graceful degradation (required test):** Trellis DB query fails → tiles show unavailable state; journal-backed surfaces (Chat, Activity) unaffected (p4 edge case).
|
||||||
|
|
||||||
|
**3c done =** tiles render native Trellis threads (list, live thread, recent events, tap-to-switch render-only) · degrade gracefully when Trellis-role query fails · journal surfaces unaffected by Trellis outage.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
**Drift touched by Phase 3** (existing 7 items in checkpoint 1088 untouched): in-process-trigger → bus, paid down in 3a; FR-012 backend, completed in 3c (Phase-1 execution under-delivery, not Phase-3 scope creep); the repo-rename-not-done and shipped-WS items remain documented divergences, not corrected here.
|
||||||
|
|
||||||
|
**Why 3b is journal-only.** Splitting Chat+Activity (journal) from the Trellis surface (3c) makes the journal/Trellis boundary map one-to-one onto the 3b/3c split. 3b cannot break when Trellis is down because it never touches Trellis — the graceful-degradation edge case becomes near-free.
|
||||||
|
|
||||||
|
**Gated structure.** 3a recon → checkpoint (Travis decides Chat transport on the `/ws` fork) → 3a wires → 3b builds against the decided contract → 3c. Sequential spawns, handoff to 588 between each.
|
||||||
Loading…
Reference in New Issue
Block a user