Expand description
kranz-server — axum REST + WebSocket layer over a repo’s mission data (docs/protocol.md is authoritative for every route and frame shape).
The read/steer routes NEVER write events.jsonl (single-writer rule
§4.3): their only write path is the control inbox
(POST /api/missions/:id/control → kranz_engine::control::enqueue).
Every read handler re-reads from disk on each request — the engine owns
truth and requests are localhost-cheap at human timescales, so there is
no in-memory cache to invalidate.
Missions created via POST /api/missions are HOSTED (M2.5): for those,
this process holds the MissionEngine —
and therefore the single-writer lock — in MissionHost, which is the
engine writing events.jsonl. See [host].
Structs§
- ApiError
- Embedded
File - A single dashboard file embedded into a caller’s binary.
- Host
Config - The
hostobject in the operator’s global~/.kranz/config.json. - Invalid
Mutation Authority - Error returned when a mutation token cannot be represented safely in an HTTP header.
- Mission
Host - Registry of missions this server process hosts (see module docs).
- Multi
Repo Host - Static process-lifetime catalog plus one existing single-repo host per healthy root.
- Mutation
Authority - Validated mutation authority required by every published router and serve
constructor. Keeping the unauthenticated state unrepresentable prevents an
embedder from accidentally exposing money-spending
POST /api/...routes. - Repo
Activity - At-a-glance pipeline counts for the repository picker. Counts follow the dashboard’s work-item projection: ticket-backed missions count once under the ticket state; only ticketless missions are folded independently.
- Repo
Config - One operator-owned repository catalog row.
- Repo
Context - A resolved catalog entry. The root is fixed at startup and requests only ever resolve this entry by its validated id.
- Repo
Slack Config - Per-repository Slack routing and authorization owned by global config.
- Repo
Summary - Public
GET /api/reposrow. - Server
State - Shared handler state: the repo root for the read-only routes (all mission data is re-read from disk per request) plus the hosted-engine registry.
- Slack
Channel Route - Exact Slack workspace/channel route.
Enums§
- ApiError
Code - Stable recovery hints for clients. Human-readable messages remain separate.
- Dashboard
Static - Static dashboard source for the catch-all frontend routes.
- Pending
Approval - What
MissionHost::try_approve_pending_matchingdid.
Constants§
- TOKEN_
HEADER - Header carrying the per-serve mutation token (docs/protocol.md “Authority: mutation token”).
Functions§
- bind_
listener - Bind
bind:portand return the listener. Callers that need the REAL bound address before serving —--port 0picks an ephemeral port, and the CLI prints/opens the URL — bind first and hand the listener toserve_on_listener. - generate_
token - A fresh mutation token: uuid v4 as simple hex. Exposed so embedding shells (the CLI, the Tauri app) mint tokens without their own uuid dep.
- load_
host_ config - Load only the operator-owned
hostblock from a global config file. Missing files are equivalent to an empty catalog; malformed files fail startup instead of silently selecting another repository. - router
- Build a read-only convenience router with a fresh, undisclosed mutation
authority. GETs work normally; every mutation is refused because callers
cannot present that authority. Embedders that need mutations must call
router_with_tokenwith an explicitMutationAuthority. - router_
with_ host - The real router constructor: an explicit
MissionHost(tests inject a mock agent backend viaMissionHost::with_backend) plus mandatory mutation authority. - router_
with_ multi_ repo_ host_ and_ addr - Build one process router around a static catalog of per-repository hosts.
Each configured id is mounted at
/api/repos/{id}; the historical unscoped routes are mounted only when the catalog has an explicit default or exactly one healthy repository. - router_
with_ read_ authority_ and_ addr router_with_multi_repo_host_and_addrplus a distinct READ-ONLY token (docs/protocol.md “Authority: mutation token”).read_authorityauthenticates GET/HEAD (and the WS upgrade) wherever the read gate is armed, but is never accepted on a mutating route — it is the token safe to hand to dashboards and agents. If absent, empty, or equal to mutation authority, a distinct read token is generated. Clients obtain it from/api/read-tokenusing either valid token in the header; mutation tokens remain header-only.- router_
with_ shared_ host router_with_hostover an already-shared registry — thekranz serve --slackpath, where the Slack bridge holds a clone of the same host.- router_
with_ shared_ host_ and_ addr - The full router constructor: the REAL bound address (when known) scopes
CORS / WS origin approval to that exact ip:port plus the dev-server
ports,
bind_is_loopbackselects the strict loopback Host/origin allowlist vs the LAN one, andrequire_read_tokenindependently arms the read/WS token gate. - router_
with_ shared_ host_ and_ bind - Router constructor that threads the serve bind port into CORS / WS origin
checks and optionally requires the mutation token on GET + WS upgrade
(
require_read_token, independent ofbind_is_loopback—--read-authcan arm it on a loopback bind without relaxing the loopback Host/origin allowlist). - router_
with_ static - Build the read-only convenience router with either filesystem or embedded
dashboard assets. Use
router_with_tokenfor authenticated mutations. - router_
with_ token - Build the full router with mutation authority gating every
POST /api/.... CORS allows any localhost/127.0.0.1 port (and Tauri), and GETs stay tokenless. - serve
- Bind
127.0.0.1:<port>and serve the router until the process exits.authoritygates everyPOST /api/.... - serve_
multi_ on_ listener - Serve a static multi-repository catalog on an already-bound listener.
read_authforces the read-token gate (GETs and the WS upgrade) even on a loopback bind — off-loopback binds always require it regardless.read_authority, when set, is the read-only token accepted on those gated reads (never on mutations); the mutationauthoritykeeps working for reads too. - serve_
on_ listener - Serve the router on an already-bound listener. The router is built from
the listener’s REAL local address, so
--port 0scopes the origin allowlist to the actual ephemeral port and a non-loopback bind gets the read-token gate. - serve_
with_ shared_ host serve_with_staticover an already-shared registry (seerouter_with_shared_host).bindwidens reachability beyond loopback (e.g. for the glasses app on the same LAN / tailnet). Every POST stays mutation-token-gated; whenbindis not loopback, GETs and WS upgrades require the token too. The CLI prints a loud warning for non-loopback binds.- serve_
with_ shutdown - Same as
serve_with_shared_host, but takes an explicit shutdown signal instead of always waiting on Ctrl-C — the testable seam that lets callers (and tests) make the serve future return deterministically. - serve_
with_ static - Bind
127.0.0.1:<port>and serve the router until the process exits.