docs: design workflow console foundation

This commit is contained in:
lda
2026-07-01 14:53:26 +07:00 Verified
parent a61426dce7
commit b41d64e7f7
3 changed files with 458 additions and 32 deletions
@@ -0,0 +1,429 @@
# Workflow Console Foundation Design
Date: 2026-07-01
Status: Approved design for the first web implementation slice.
Related:
- [Workflow console, agent demo, and defense presentation](2026-07-01-workflow-console-agent-demo.md)
- [Self-describing interrupt contracts](2026-07-01-self-describing-interrupt-contracts.md)
- [Workflow API architecture](../../wf_api_architecture.md)
- [Current roadmap](../../current_roadmap.md)
## Goal
Create the first executable `web/` slice for lda.chat: a local React console
that connects to a loopback `wf-rpc-server` through a small Node proxy, proves
typed JSON-RPC decoding with Effect, and presents both interpreted connection
state and raw protocol evidence.
This slice establishes durable package and protocol boundaries. It does not
build lifecycle browsing, workflow graphs, autoplay, replay, the demo agent, or
the defense presentation.
## Architecture Decision
Use an app-first React architecture rather than Astro as the primary console
shell:
- Vite builds and serves the React development application.
- Hono provides the local Node HTTP server and production static-file host.
- Effect owns URL validation, JSON-RPC schemas, typed errors, and upstream RPC
execution.
- React consumes plain view models and never runs Effect programs directly.
- Astro remains available for a later static presentation application and is
not a dependency of the console.
The Hono server is not a second workflow API. It is a narrow browser-facing
adapter over the existing public JSON-RPC endpoint.
```mermaid
flowchart LR
Browser[React + Vite console]
Hono[Hono local server]
Policy[Loopback target policy]
Effect[Effect WorkflowRpc service]
Rpc[wf-rpc-server]
Browser -->|POST /api/connect| Hono
Browser -->|POST /api/rpc| Hono
Hono --> Policy
Hono --> Effect
Effect -->|JSON-RPC HTTP| Rpc
```
## Workspace Layout
Create a pnpm workspace under the existing repository root:
```text
web/
package.json
pnpm-lock.yaml
pnpm-workspace.yaml
tsconfig.base.json
apps/
console/
package.json
index.html
vite.config.ts
src/
app/
components/
connection/
rpc/
styles/
main.tsx
server/
package.json
src/
app.ts
index.ts
static.ts
packages/
rpc/
package.json
src/
errors.ts
method-registry.ts
protocol.ts
service.ts
target-policy.ts
index.ts
```
The first slice creates only `console`, `server`, and `rpc`. Later slices may
add:
```text
web/apps/presentation/ # Astro presentation and appendix routes
web/packages/ui/ # shared components after real reuse exists
```
Do not create empty placeholder packages for future work.
The root package pins `[email protected]`. The supported runtime is Node.js 22 or
newer; the current development environment uses Node.js 26.
## Process Model
Development uses two processes started through one root command:
1. Vite serves the React application with hot reload.
2. Hono serves `/api/*` and proxies validated JSON-RPC calls.
Vite forwards `/api/*` to Hono during development. The browser therefore uses
one apparent origin and does not depend on Python CORS configuration.
Production uses one Hono process:
1. Vite builds static assets.
2. Hono serves the Vite output.
3. Hono retains the same `/api/*` routes.
4. Unknown non-API paths fall back to `index.html` for client routing.
The first slice does not start or supervise `wf-rpc-server`. Operators run it
separately and paste its loopback JSON-RPC URL into the console.
## Connection Contract
The connection form accepts a URL such as:
```text
http://127.0.0.1:8765/rpc
```
Accepted targets must satisfy all rules:
- scheme is `http`;
- hostname is exactly `127.0.0.1`, `localhost`, or `[::1]`/`::1` after URL
parsing;
- username and password are absent;
- port is explicit and in the valid TCP port range;
- URL has no query string or fragment;
- redirects are not followed by the upstream fetch;
- the normalized URL is validated again for every proxy request.
`POST /api/connect` accepts:
```json
{
"target": "http://127.0.0.1:8765/rpc"
}
```
It sends `workflow.health` with an empty params object and returns a view model:
```json
{
"ok": true,
"connection": {
"status": "connected",
"target": "http://127.0.0.1:8765/rpc",
"serverStatus": "ok",
"durationMs": 12
},
"exchange": {
"request": {},
"response": {}
}
}
```
The exact raw JSON-RPC request and response are retained inside `exchange`.
They are omitted from the compact connection card but available in the raw
drawer.
The browser stores only the normalized target URL in `sessionStorage`. The
server remains stateless: every `/api/rpc` request carries the target and is
validated again. No cookies, server sessions, authentication tokens, or target
registry are introduced in this slice.
## JSON-RPC Contract
The RPC package defines Effect schemas for:
- JSON-RPC request envelopes;
- success response envelopes;
- error response envelopes;
- `workflow.health` results;
- `workflow.sources.list` parameters and the selected result fields required by
the first console screen.
The request id is generated by the server adapter and represented as a string.
Responses must have `jsonrpc: "2.0"`, the matching id, and exactly one of
`result` or `error`.
The first method registry contains two entries:
```text
workflow.health
workflow.sources.list
```
Each registry entry includes:
- method name;
- parameter schema;
- result schema;
- human label and explanation;
- idempotency classification;
- equivalent CLI formatter;
- result-to-view-model interpreter.
Unknown methods are rejected by the Hono adapter. This is a mapped console
surface, not an unrestricted arbitrary JSON-RPC relay.
## Server API
The first Hono application exposes:
- `GET /api/health`: confirms the web server itself is running;
- `POST /api/connect`: validates a target and calls `workflow.health`;
- `POST /api/rpc`: invokes one registered read-only JSON-RPC operation;
- static Vite assets and SPA fallback in production.
`POST /api/rpc` accepts:
```json
{
"target": "http://127.0.0.1:8765/rpc",
"operation": "workflow.sources.list",
"params": {}
}
```
It returns:
```json
{
"ok": true,
"operation": "workflow.sources.list",
"label": "List sources",
"interpreted": {},
"exchange": {
"request": {},
"response": {}
},
"equivalentCli": "uv run wf source list",
"durationMs": 8
}
```
The server accepts only registry operations. It does not accept a raw method
string supplied directly by the browser.
Failures use one browser-facing shape:
```json
{
"ok": false,
"error": {
"code": "upstream_unreachable",
"message": "Could not connect to the workflow RPC server."
},
"exchange": {
"request": {},
"response": null
}
}
```
HTTP status mapping is fixed:
- `400`: invalid target, unknown operation, or invalid browser request;
- `502`: upstream connection, JSON-RPC remote, protocol, or decode failure;
- `504`: upstream timeout.
An upstream JSON-RPC error remains visible in the raw exchange but is mapped to
a safe browser-facing code and message.
## Effect Boundary
Effect is confined to `web/packages/rpc` and the Hono handler boundary.
Services:
- `TargetPolicy`: parse and validate loopback RPC targets;
- `WorkflowRpc`: execute one registered JSON-RPC call;
- `MethodRegistry`: resolve operation metadata and schemas;
- `Clock` through Effect for measured duration and deterministic tests.
Tagged errors:
- `InvalidTargetError`;
- `UnknownOperationError`;
- `UpstreamConnectionError`;
- `UpstreamTimeoutError`;
- `RpcProtocolError`;
- `RpcRemoteError`;
- `RpcDecodeError`.
Hono handlers call `Effect.runPromise` once per request and map tagged errors to
stable HTTP responses. React receives JSON DTOs and ordinary discriminated
unions, not Effect values, causes, layers, or services.
No retries are applied in this slice. Even health and list operations should
report the first real failure so connection diagnostics remain honest.
## Console UI
The first page has three regions:
1. **Connection header**: target URL, connect/reconnect action, connection
status, server status, and measured duration.
2. **Source inventory**: a compact read-only list produced by
`workflow.sources.list` after connection succeeds.
3. **Protocol evidence**: a collapsible drawer showing the selected operation,
equivalent CLI, raw request, raw response, and any typed error.
The UI must distinguish:
- not configured;
- connecting;
- connected;
- invalid target;
- unreachable server;
- JSON-RPC remote error;
- malformed response.
Connection failures keep the entered URL editable. Reconnect replaces the
session target only after a successful health call. Reload restores the last
successful target from `sessionStorage` but does not silently call it until the
user reconnects.
The first slice uses React local state and reducers. Do not add Redux, Zustand,
Pinia, XState, TanStack Query, or a client Effect runtime before lifecycle data
creates a demonstrated need.
## Visual Direction
The console should look like an operational instrument, not a generic admin
template:
- warm off-white workspace background with a subtle technical grid;
- ink, slate, signal green, amber, and red tokens;
- expressive condensed heading face paired with a readable technical body
face;
- dense but calm source rows;
- raw evidence presented as an intentional inspection surface, not a debug
afterthought;
- desktop-first layout that remains usable on a narrow laptop or tablet.
Avoid dark-mode-first styling, purple gradients, interchangeable dashboard
cards, and motion without semantic purpose. The first meaningful animation is
the connection transition and staggered source reveal.
## Error And Security Boundaries
- The proxy never forwards non-loopback URLs.
- Upstream redirects are rejected rather than followed.
- Browser request bodies are limited to 256 KiB.
- Upstream response bodies are limited to 4 MiB.
- Upstream calls have a five-second timeout and abort signal.
- Raw evidence is JSON-serialized and rendered as text, never inserted as HTML.
- Hono error responses contain a stable error code and safe message; stack
traces remain server-side.
- The app binds to loopback by default.
- This slice does not claim authentication or protection from other local
processes running as the same user.
## Testing Strategy
Use Vitest for all TypeScript tests and React Testing Library for component
behavior.
Required tests:
- target policy accepts the three loopback host forms and rejects remote,
credentialed, query, fragment, implicit-port, and redirect targets;
- JSON-RPC schemas accept valid success/error envelopes and reject mismatched
ids or malformed envelopes;
- method registry exposes only health and source-list operations;
- Hono connect route returns interpreted and raw health data through an injected
fake upstream fetch;
- Hono RPC route rejects unknown operations before any fetch;
- Hono maps timeout, connection, remote RPC, and decode failures to stable error
DTOs;
- React connection form preserves failed input and persists only successful
normalized targets;
- source inventory renders interpreted source rows;
- raw evidence drawer exposes request, response, and equivalent CLI;
- production build completes and the Hono static fallback serves the console.
Do not require a live Python server in the default frontend test suite. Add a
documented optional smoke command against:
```powershell
uv run wf-rpc-server --config wf.config.json --host 127.0.0.1 --port 8765
```
## First-Slice Success Criteria
The foundation slice is complete when:
1. `pnpm --dir web install`, test, typecheck, and build commands are documented
and pass;
2. one root development command starts Vite and Hono;
3. a user can enter a loopback RPC URL and receive interpreted
`workflow.health` state;
4. a connected user can list workflow sources;
5. every displayed operation has raw request/response evidence and an
equivalent CLI string;
6. unknown methods and non-loopback targets fail before upstream fetch;
7. the production Hono process serves the built React application;
8. no Python CORS change, direct store access, workflow mutation, graph UI,
autoplay, replay, or agent integration is included.
## Deferred Slices
After this foundation:
1. read-only lifecycle inspectors for drafts, artifacts, deployments, runs,
traces, and graph rendering;
2. prepared lifecycle job, autoplay, typed interrupt form, issue-board output,
and replay;
3. constrained demo agent and recipe macro;
4. Astro presentation and appendix app consuming shared React components only
where interactivity is useful.