Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions gateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,12 @@ runs the operator dashboard. Source lives under
(in CI / docker) the `plugin-ui-builder` stage in the Dockerfile, then
embedded into the plugin .so via `//go:embed all:ui/dist`.

Pages in v1: Login, Dashboard, Agents, AgentDetail, RunDetail. All
read-only; phase 9 adds the kill switches, budget editors, and live
Redis-blended panels.
Pages: Login, Canvas (landing), Dashboard, People, UserDetail, Agents,
AgentDetail (Overview / Prompts / Tools / Skills / Evals tabs), and
RunDetail (call log, cap meters, live Redis state, kill). Agent kill
switches are live too. Still open from
[phase 9](./plans/phases/phase-9-operator-ui.md): budget editing, the
Config page, Sessions, and a `/runs` list.

Local UI dev with HMR (proxies `/_plugin/*` to a running plugin on
`localhost:8181`):
Expand Down
13 changes: 5 additions & 8 deletions gateway/internal/adminapi/ui/src/api/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,13 @@ export function useMe() {
// Polled at 30s on the dashboard's rankings tables and the Agents
// page. Polling pauses automatically when the tab is hidden.

export function useSpendByAgent(window: Window, userID?: string) {
// Optional userID scopes the rollup to one person's calls —
// used by the UserDetail page to render "agents this user
// invoked". Server-side filter so we don't fan out per row.
const params = new URLSearchParams({ window });
if (userID) params.set("user_id", userID);
export function useSpendByAgent(window: Window) {
return useQuery({
queryKey: ["spend", "by-agent", window, userID ?? ""],
queryKey: ["spend", "by-agent", window],
queryFn: () =>
apiFetch<SpendByAgentResponse>(`/spend/by-agent?${params.toString()}`),
apiFetch<SpendByAgentResponse>(
`/spend/by-agent?window=${encodeURIComponent(window)}`
),
refetchInterval: 30_000,
staleTime: 10_000,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { Link, useLocation } from "wouter-preact";

import { ChevronLeftIcon } from "../icons";

// Sidebar nav. Phase 8 has Dashboard + People + Agents; phase 9
// grows Sessions and Config — each is just another row here.
// Sidebar nav. Canvas is the landing page; Dashboard, People and
// Agents are the phase-8/9 views. Phase 9's Sessions and Config
// pages are still unbuilt — when they land, each is one more row.
//
// People above Agents reflects the organising principle of the
// governance plan: "every LLM call traces to a specific human."
Expand Down
2 changes: 2 additions & 0 deletions gateway/plans/llm-governance-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
> | Trust registry storage, admin API, env-var seed | [`phases/phase-5-trust-registry.md`](./phases/phase-5-trust-registry.md) |
> | Plugin Redis schema, per-hook ops, TTL policy, failure modes | [`phases/phase-6-plugin-enforcement.md`](./phases/phase-6-plugin-enforcement.md) |
> | `/_plugin/*` observability over `logs.db` (per-dim analytics) | [`phases/phase-7-observability.md`](./phases/phase-7-observability.md) |
> | Read-only operator dashboard (embedded SPA, sessions, tygo) | [`phases/phase-8-observability-dashboard.md`](./phases/phase-8-observability-dashboard.md) |
> | Operator console: kill switches, cap meters, budget/config edits | [`phases/phase-9-operator-ui.md`](./phases/phase-9-operator-ui.md) |
> | Symmetric narrowing rule, per-realm budgets, cross-swarm spawning | [`phases/phase-11-symmetric-recursive-authorization.md`](./phases/phase-11-symmetric-recursive-authorization.md) |
> | Transparency log: Merkle leaves + Hive witness (Part 1); receipts + client enforcement (Part 2) | [`phases/phase-12-transparency-log.md`](./phases/phase-12-transparency-log.md) |
> | Agent defaults / registry | [`agent-registry.md`](./agent-registry.md) |
Expand Down
11 changes: 10 additions & 1 deletion gateway/plans/phases/phase-12-transparency-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,13 @@ Registered exactly like `/_plugin/admin-credentials`:
`cookieOrBearer`: a dashboard cookie must not be able to pull material
Hive then org-signs.

A second, dashboard-facing route, `GET /_plugin/tlog/status`
(`cookieOrBearer`), landed with the status card in PR #1690. It
returns local facts only — enabled flag, tree size, root hash, the
per-boot `log_pubkey`, leaf path, last-leaf time — never a signed STH
or leaves, so a dashboard cookie still cannot pull anything Hive would
org-sign.

`since` is a base-10 integer in `[0, tree_size]`. Anything else → 400.
`since > tree_size` → 409 with the current size in the body (see
"Failure modes"). The handler `Sync()`s the leaf file, then responds:
Expand Down Expand Up @@ -667,6 +674,8 @@ builds receives the wrapper.
- **Redis-backed receipt map** (after Part 2), so a restart mid-stream
does not 404 the poll.
- **Dashboard.** A provenance badge on `/runs/:id` ("witnessed at
head N") and, after Part 2, a per-run receipt list.
head N") and, after Part 2, a per-run receipt list. (The `TlogCard`
on the Dashboard is not this: it renders the unsigned
`/_plugin/tlog/status` facts, not witness state.)
- **Actions the gateway never sees** (git push, file writes). The
leaf format generalizes; the submit path does not exist yet.
5 changes: 4 additions & 1 deletion gateway/plans/phases/phase-3-swarm-handoff.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ gateway and own every Bifrost in the system.

The `bifrost_admin_password` is meant to be user-facing — that's the
"copy this to log in to your dashboard" experience. It's per-gateway
and only grants control over one workspace's Bifrost.
and only grants control over one workspace's Bifrost. The same pair
also signs into the plugin's own operator UI at `/_plugin/ui/`
(`POST /_plugin/login`, phases 8–9), so one credential drives both
`/api/*` and the dashboard.

## Status

Expand Down
79 changes: 42 additions & 37 deletions gateway/plans/phases/phase-8-observability-dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -774,75 +774,80 @@ served by the same SPA handler (wouter handles it client-side).

**Backend — sessions (`gateway/internal/sessions/`):**

- [ ] `store.go`: `SessionStore` interface; Redis implementation.
- [ ] `store_test.go`: miniredis-backed tests covering create, get,
- [x] `store.go`: `SessionStore` interface; Redis implementation.
- [x] `store_test.go`: miniredis-backed tests covering create, get,
refresh, delete, kick-all-for-user, TTL expiry.

**Backend — adminapi (`gateway/internal/adminapi/`):**

- [ ] `session.go`: middleware combining session-cookie and
- [x] `session.go`: middleware combining session-cookie and
bearer-token auth; `allowAnon` list.
- [ ] `login.go`: `POST /_plugin/login` (Basic-in, cookie-out),
- [x] `login.go`: `POST /_plugin/login` (Basic-in, cookie-out),
`POST /_plugin/logout`, `GET /_plugin/me`.
- [ ] `ratelimit.go`: per-IP login attempt counter.
- [ ] `logstore_client.go`: HTTP client to
- [x] `ratelimit.go`: per-IP login attempt counter.
- [x] `logstore_client.go`: HTTP client to
`http://127.0.0.1:8080/api/logs` (and rankings / histogram
sub-paths). Owns `SearchFilters` serialization, retry,
timeout. Reusable across all handlers below and any phase 9
additions.
- [ ] `spend.go`: `/_plugin/spend/by-{agent,user}` handlers.
- [ ] `histogram.go`: `/_plugin/histogram/cost` handler calling
- [x] `spend.go`: `/_plugin/spend/by-{agent,user}` handlers.
- [x] `histogram.go`: `/_plugin/histogram/cost` handler calling
`GetDimensionCostHistogram` through the client.
- [ ] `runs.go`: `/_plugin/runs/:run_id` drill-down (read-only;
phase 9 extends with `/state` and `/kill`).
- [ ] `ui.go`: `//go:embed ui/dist` + SPA fallback handler.
- [ ] Route registration in `server.go`, with `/_plugin/ui/*` and
- [x] `/_plugin/runs/:run_id` drill-down — lives in `observability.go`,
not a separate `runs.go`; phase 9 added `/state` and `/kill` in
`hotstate.go`.
- [x] `ui.go`: `//go:embed ui/dist` + SPA fallback handler.
- [x] Route registration in `server.go`, with `/_plugin/ui/*` and
every `/_plugin/spend/*`, `/_plugin/histogram/*`,
`/_plugin/runs/:id` route behind the session middleware, and
`/_plugin/health`, `/_plugin/login` in `allowAnon`.

**Backend — type codegen (`gateway/`):**

- [ ] `tygo.yaml` covering `internal/adminapi`.
- [ ] `make tygo` target invoking the codegen.
- [ ] CI step: `make tygo && git diff --exit-code internal/adminapi/ui/src/api/types.ts`.
- [x] `tygo.yaml` covering `internal/adminapi`.
- [x] `make tygo` target invoking the codegen.
- [x] CI step: `make tygo-check` in `.github/workflows/gateway-check.yml`.

**Frontend (`gateway/internal/adminapi/ui/`):**

- [ ] Vite + Preact + TS scaffold; `package.json`, `vite.config.ts`,
- [x] Vite + Preact + TS scaffold; `package.json`, `vite.config.ts`,
`tsconfig.json`, `index.html`.
- [ ] `src/api/client.ts`: typed fetch wrapper, 401 redirect.
- [ ] `src/api/queries.ts`: one hook per endpoint.
- [ ] `src/app.tsx`: wouter routes, QueryClient provider, global
- [x] `src/api/client.ts`: typed fetch wrapper, 401 redirect.
- [x] `src/api/queries.ts`: one hook per endpoint.
- [x] `src/app.tsx`: wouter routes, QueryClient provider, global
auth-error handler.
- [ ] `src/components/layout/Shell.tsx` + `Sidebar` + `Topbar`.
- [ ] `src/components/charts/UplotChart.tsx`: generic wrapper.
- [ ] `src/components/charts/CostHistogram.tsx`: stacked-area
- [x] `src/components/layout/Shell.tsx` + `Sidebar` + `Topbar`.
- [x] `src/components/charts/UplotChart.tsx`: generic wrapper.
- [x] `src/components/charts/CostHistogram.tsx`: stacked-area
cost-by-time-by-dimension chart.
- [ ] `src/components/tables/DataTable.tsx`: sortable, paginated.
- [ ] `src/components/controls/WindowPicker.tsx`.
- [ ] `src/components/EmptyState.tsx`, `ErrorBoundary.tsx`.
- [ ] `src/pages/Login.tsx`, `Dashboard.tsx`, `Agents.tsx`,
`AgentDetail.tsx`, `RunDetail.tsx`, `NotFound.tsx`.
- [ ] `src/styles/base.css` + `components.css`.
- [x] `src/components/tables/DataTable.tsx`: sortable. **Not paginated**:
it caps rows client-side, and `useRunDetail` sends no
`limit`/`offset`, so a run with more calls than the server's
50-row default is silently truncated — still open.
- [x] `src/components/controls/WindowPicker.tsx`.
- [x] `src/components/EmptyState.tsx`, `ErrorBoundary.tsx`.
- [x] `src/pages/Login.tsx`, `Dashboard.tsx`, `Agents.tsx`,
`AgentDetail.tsx`, `RunDetail.tsx`, `NotFound.tsx` — plus
`Canvas.tsx` (the landing page), `People.tsx`, `UserDetail.tsx`
and the `EvalsView` tab, all beyond this phase's scope.
- [x] `src/styles/base.css` + `components.css`.

**Dockerfile:**

- [ ] `ui-builder` stage running `npm ci && npm run build`.
- [ ] `COPY --from=ui-builder /ui/dist` into the Go build context
- [x] `plugin-ui-builder` stage running `npm ci && npm run build`.
- [x] `COPY --from=plugin-ui-builder /pui/dist` into the Go build context
before `go build`.

**Docs:**

- [ ] Update `gateway/README.md` with the observability-dashboard
- [x] Update `gateway/README.md` with the observability-dashboard
bullet (URL, default creds in dev, link to this phase).
- [ ] Update `phase-3-swarm-handoff.md` to note that admin creds
- [x] Update `phase-3-swarm-handoff.md` to note that admin creds
now drive both `/api/*` and the observability dashboard.
- [ ] Update `phase-7-observability.md` to note that the four
endpoints used by phase 8 ship in phase 8; the remainder
ships with phase 9.
- [ ] Forward-pointer from `llm-governance-v2.md` §"Plugin" to this
phase.
- [x] Update `phase-7-observability.md` — moot: every phase-7
endpoint has since shipped (its own checklist is ticked).
- [x] Forward-pointer from `llm-governance-v2.md` to this phase (in
its authoritative-spec table).

**Gate:** phase 8 ships when:

Expand Down
Loading
Loading