Skip to main content

SPEC-0013: Operator Board

SPEC · SPEC-0013 · Status · implemented · Date · 2026-07-10 · Implements · ADR-0016, ADR-0001 · Requires · SPEC-0003, SPEC-0007, SPEC-0012

Overview

The operator board is switchboard's five-view human surface — Board, Todos, Endpoints, Personas, and Friends — realizing the high-fidelity "Operator" designs adopted in ADR-0016 on top of the web stack of ADR-0001. It extends the SPEC-0012 baseline (embedded templates, OIDC sessions, CSRF, security headers, SSE plumbing, error handling, WCAG 2.1 AA) and supersedes SPEC-0012's "Screen Set and Routes" and "Semantic HTML and Classless Styling" requirements: the dashboard/agent/vended screens are replaced by the Board/Todos/Endpoints views, and styling moves from classless Pico to the owned tokens.css + .sb-* component layer. The Personas and Friends views ship with their backing capabilities (SPEC-0009, SPEC-0010) and MUST NOT block the other three views.

Requirements

Requirement: Information Architecture and Navigation

The UI MUST present a persistent top bar (brand mark + wordmark, a live throughput pill, the authenticated human's initials) and a left navigation rail with entries Board, Todos (with a live todo count), Endpoints, Personas, and Friends (with a pending-incoming-request count badge when nonzero), plus a footer connectivity indicator (postgres · connected) reflecting the health of the database pool. GET / MUST render the Board view. Below 820px viewport width the rail MUST collapse to icons-only. Views whose backing capability is not yet enabled (Personas, Friends) MUST be hidden from the rail rather than rendered broken.

Scenario: Board is the landing view

  • WHEN an authenticated human requests GET /
  • THEN the Board view MUST render with the rail marking Board active and the top bar showing the live events-per-minute rate

Scenario: Database connectivity is reflected

  • WHEN the database pool fails a health ping
  • THEN the rail footer indicator MUST change from the connected state to a visually and textually distinct disconnected state

Requirement: Board View — Live Incoming Lines

The Board view MUST show: a trust legend for the four trust modes (signed, token, open, queueADR-0003); summary tiles for throughput (events/min with a recent-activity bar chart), todos in flight, todos awaiting claim, and percentage of events from verified (signed) sources; and a newest-first live feed of incoming events. Each feed row MUST show the provider tag and name, event type, a trust badge, and its lifecycle stage (verifying…patched → todoclaimed · <agent>done ✓). Feed and tiles MUST update live via the SSE stream without a page reload, and the awaiting-claim tile MUST be visually emphasized when its count is nonzero.

Scenario: New event appears in the feed live

  • WHEN a webhook is verified and enqueued as a todo while the Board is open
  • THEN a new row MUST appear at the top of the feed via SSE with its trust badge and lifecycle stage, without a page reload

Scenario: Lifecycle stage advances

  • WHEN an agent claims the todo behind a feed row
  • THEN the row's stage MUST update to show claimed · <agent name> live

Requirement: Todos View — Durable Queue

The Todos view MUST list todos in a table with filter pills (All / Pending / Claimed / Done / Failed, each with a live count), a text search over id, source, and event type, and columns: id, source · event (with a dedup ×N badge when the idempotency key collapsed N > 1 deliveries), trust, status (with a sub-line showing remaining lease seconds for claimed todos or retry countdown for failed todos), agent, age, and a contextual action (Claim for pending, Complete for claimed, Retry for failed). Rows re-surfaced by the lease reaper MUST be visually highlighted briefly, and the view MUST surface that the reaper is active. All status transitions MUST reflect the todo-queue semantics of SPEC-0003; the UI MUST NOT implement its own lifecycle rules.

Scenario: Filter pills scope the table

  • WHEN the human activates the Failed filter
  • THEN only todos in the failed state MUST be listed and the pill MUST show the failed count

Scenario: Reaper re-surface is visible

  • WHEN a claimed todo's lease expires and the reaper returns it to pending
  • THEN the row MUST update to pending via SSE, be briefly highlighted, and a toast MUST announce the re-surface

Requirement: Todo Detail Drawer

Selecting a todo MUST open a right-hand drawer showing: id and status; for claimed todos a lease card with a live countdown, progress bar, and Extend lease / Release actions; for failed todos the attempt count and retry-backoff countdown; the trust mode, agent, attempts, and age; the idempotency key with a dedup summary; the event payload rendered as formatted JSON (HTML-escaped); a lifecycle timeline (received/verified → todo created → claimed under lease → completed/failed); and footer actions appropriate to the state (Claim under lease, Complete · ack + Fail, Retry now). Drawer actions MUST be state-changing POSTs protected per the SPEC-0012 baseline (session + CSRF), and the drawer MUST close on Escape and on overlay click.

Scenario: Lease controls act on the store

  • WHEN the human activates Extend lease on a claimed todo
  • THEN the server MUST extend the todo's visibility lease (heartbeat semantics per SPEC-0003) and the countdown MUST reflect the new expiry

Scenario: Payload is rendered safely

  • WHEN a todo's payload contains HTML-special characters or script content
  • THEN the drawer MUST render it inert as escaped text within the formatted block

Requirement: Endpoints View and Vend Modal

The Endpoints view MUST list vended MCP endpoints as cards showing agent name, bound persona (when personas are enabled), status (active/revoked), scoped queues, allowed verbs, the credential display prefix (never the full credential), and last-seen time, with Edit and Revoke actions on active endpoints. A Vend endpoint action MUST open a modal that collects agent name, optional persona, scoped queues, and allowed verbs via toggle chips, and MUST refuse submission until name, at least one queue, and at least one verb are chosen. On successful vend the modal MUST show, exactly once, the minted endpoint URL and plaintext credential together with ready-to-paste HTTP .mcp.json wiring per SPEC-0014, with a warning that the credential is shown once. Vend/revoke semantics (hashing at rest, immutability, revoke-kills-endpoint) MUST follow SPEC-0007; revoked cards MUST be visually dimmed and state when the endpoint was killed.

Scenario: Vend modal validates scope

  • WHEN the vend form is submitted with no queues or no verbs selected
  • THEN the submission MUST be rejected and no credential MUST be minted

Scenario: Credential reveal is one-time

  • WHEN the human closes the minted-credential modal
  • THEN the plaintext credential MUST NOT be retrievable from any subsequent page, card, or endpoint of the UI

Requirement: Personas View

When the personas capability (SPEC-0009) is enabled, the Personas view MUST list personas as cards showing name, backing agent, published/draft state, the human-authored system prompt, the advertised verb subset, and the agent-card URL (/a/{persona_id}/.well-known/agent-card.json, the per-persona well-known path of SPEC-0009), with Edit and Publish/Unpublish actions. A create/edit modal MUST collect name (with a live agent-card URL preview), backing agent, system prompt, verb subset (constrained to the backing endpoint's vended verbs), and a published toggle; deleting MUST be available only from the edit modal. Publish state changes MUST take effect on the well-known endpoint immediately.

Scenario: Verb subset is constrained

  • WHEN the human edits a persona whose backing agent's endpoint lacks a verb
  • THEN that verb MUST NOT be selectable for the persona

Requirement: Friends View

When the friending capability (SPEC-0010) is enabled, the Friends view MUST present friendships in two switchable layouts — cards and a grouped ledger (Incoming / Outgoing / Active / Blocked) — with filter pills and counts. Each friendship MUST show the local agent, the remote agent (handle and board host), status, negotiated or requested intents, and status-appropriate actions: Approve/Decline for incoming, Withdraw for outgoing, Revoke for active, Unblock for blocked. An Add friend modal MUST collect the local agent, remote handle (with live resolution preview), requested intents, and a message to the remote operator, and MUST state that links are mutual and non-transitive and remain pending until the remote operator approves.

Scenario: Incoming request is approved

  • WHEN the human approves an incoming friendship request
  • THEN the friendship MUST become active via the SPEC-0010 approval flow and move to the Active group live

Requirement: Design Language Conformance

All views MUST be styled exclusively by the design-token layer (static/tokens.css) and the .sb-* component layer (static/switchboard.css) defined by ADR-0016, using the vendored Zilla Slab / IBM Plex Sans / IBM Plex Mono woff2 faces served from static/fonts/. Templates MUST NOT reference external stylesheets, font CDNs, or utility-class frameworks. Trust and status colors MUST come from the named tokens (signed/token/open/queue; pending/claimed/done/failed) and MUST NOT be hard-coded per-template. Pico.css MUST NOT be served.

Scenario: No external origins

  • WHEN any view renders under the baseline CSP (default-src 'self')
  • THEN all styles, fonts, scripts, and icons MUST load from same-origin embedded assets with no CSP violations

Requirement: Live Updates and Toasts

Live regions across all views MUST subscribe to the authenticated UI SSE stream (SPEC-0012 baseline) carrying typed events for at minimum: event received, todo created, todo claimed, todo completed, todo failed, todo re-surfaced (reaper), and endpoint last-seen updates. Queue lifecycle transitions initiated by others (agents, the reaper) MUST surface as transient toast notifications. SSE remains best-effort presentation: a dropped event MUST NOT corrupt view state and a reload MUST render authoritative database state.

Scenario: Toast on background transition

  • WHEN an agent completes a todo while the human has the Todos view open
  • THEN the row MUST update to done and a toast MUST announce the completion with the todo id

Requirement: Error Handling Standards

All error-producing operations MUST follow structured error handling:

  • Errors MUST be wrapped with contextual information at each layer boundary
  • Sentinel errors MUST be defined for domain-specific failure modes callers distinguish programmatically (e.g. not-found vs conflict on claim)
  • Silent error swallowing MUST NOT occur — every error MUST be returned, logged with context, or explicitly suppressed with a documented reason
  • Structured logging MUST be used for error reporting (key-value pairs, not string interpolation)
  • User-facing failures MUST follow the SPEC-0012 baseline: generic status responses, no internal detail leakage

Scenario: Action failure is generic to the user, specific in the log

  • WHEN a drawer action fails against the store
  • THEN the user MUST receive a generic error response while the server log records the handler, todo id, and wrapped error

Endpoints

MethodPathAuthDescription
GET/RequiredBoard view
GET/todosRequiredTodos view (query params: filter, q)
GET/todos/{id}RequiredTodo detail drawer fragment
POST/todos/{id}/claimRequiredClaim under lease (as the operator)
POST/todos/{id}/completeRequiredComplete · ack
POST/todos/{id}/failRequiredMark failed (schedules retry)
POST/todos/{id}/retryRequiredRe-queue a failed todo now
POST/todos/{id}/extendRequiredExtend lease (heartbeat)
POST/todos/{id}/releaseRequiredRelease lease back to pending
GET/endpointsRequiredEndpoints view
GET/endpoints/vendRequiredVend-endpoint modal fragment
POST/endpoints/vendRequiredVend a scoped endpoint (modal submit)
POST/endpoints/{id}/revokeRequiredRevoke (kill) an endpoint
GET/agents, /agents/{id}RequiredLegacy SPEC-0012 paths; redirect to /endpoints
GET/personasRequiredPersonas view (capability-gated)
POST/personasRequiredCreate persona
POST/personas/{id}RequiredUpdate persona (incl. publish toggle)
POST/personas/{id}/deleteRequiredDelete persona
GET/friendsRequiredFriends view (capability-gated)
GET/friends/newRequiredAdd-friend modal fragment
GET/friends/resolveRequiredResolve a friend handle (live card preview fragment)
POST/friendsRequiredSend friendship request
POST/friends/{id}/approveRequiredApprove incoming request
POST/friends/{id}/declineRequiredDecline incoming request
POST/friends/{id}/withdrawRequiredWithdraw outgoing request
POST/friends/{id}/revokeRequiredRevoke (block) an active link
POST/friends/{id}/unblockRequiredRemove a blocked entry
GET/eventsRequiredUI SSE stream (session-authenticated)

All previously specified public routes (login, auth, static, healthz) retain their SPEC-0012 designations; this spec introduces no new public endpoints.

Security Requirements

This capability inherits the full SPEC-0012 security baseline and re-affirms it for the new surface:

Authentication

Every route in the table above MUST require an authenticated human session (RequireHuman); the SSE stream (GET /events) MUST be session-authenticated and scoped to the human's own data. No new public endpoints are introduced.

Rate Limiting

State-changing POST routes SHOULD share the human-surface rate limiter; the SSE endpoint MUST cap concurrent streams per session to a small fixed number to bound resource use.

Security Headers

All HTML and SSE responses MUST carry the SPEC-0012 header set (CSP default-src 'self' with same-origin connect-src for SSE, X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin). The design-language work MUST NOT introduce any external origin.

Request Body Size Limits

All POST handlers MUST bound bodies with a 1 MiB limit before parsing (forms carry only names, prompts, and chip selections).

CSRF Protection

All state-changing POSTs (todo actions, vend, revoke, persona and friend mutations) MUST carry the per-session synchronizer token per the SPEC-0012 baseline, including requests issued by HTMX; the CSRF token MUST be injected into HTMX request headers or hidden fields uniformly.

Redirect Validation

Post-action redirects MUST target fixed same-origin paths (the originating view); no user-supplied redirect targets are accepted.

Accessibility Requirements

This spec involves user-facing UI. The following accessibility requirements are MANDATORY per WCAG 2.1 AA and extend the SPEC-0012 baseline to the new components.

WCAG 2.1 AA Compliance

All five views, the drawer, modals, and toasts MUST meet WCAG 2.1 Level AA, including contrast under both the operator-cream (light) and bakelite (dark) themes.

ARIA Landmarks

The top bar MUST be banner, the rail navigation, each view's content main (exactly one per page), and any footer contentinfo. The rail MUST expose the active view via aria-current="page".

Icon-Only Controls

Icon-only controls (the brand mark, drawer close, rail icons when collapsed) MUST have accessible names via aria-label or SVG <title> + role="img". Trust and status MUST NOT be conveyed by color alone — badges always carry text.

Dynamic Content Regions

The Board feed, Todos table, count pills, and toast container MUST be aria-live="polite" regions (toast container MAY be assertive for failure toasts) existing in the DOM before first update.

Keyboard Navigation

All interactive elements — filter pills, chip toggles, table rows' actions, drawer and modal controls — MUST be reachable in logical tab order and operable with Enter/Space; Escape MUST dismiss the drawer and any modal; the lease countdown MUST NOT trap focus or steal it on tick.

Focus Management

The drawer and every modal MUST trap focus while open, set initial focus to the first meaningful control, close on Escape, and return focus to the triggering control on close.