Documentation
¶
Overview ¶
GET /catalog/graph — the whole catalog as a minimal graph for the admin model picker.
Returns providers, hosts, and models (with their host bindings) in one call so the UI can render and cross-link them without three separate list fetches + client-side flattening. Only the fields the picker and the policy model view actually read are included — identity (id/name/ displayName), the enabled flag, host icon path, per-model deprecation, capabilities, context window, and the (hostId, adapter, enabled) bindings. Heavy spec (snapshots, pricing refs, modalities, …) is deliberately omitted.
Reads the in-memory snapshot via the same index as /catalog/resolve, so it returns exactly what the data plane can route to right now: disabled providers/hosts/models are already absent, and disabled bindings are pruned here. The picker only offers routable targets; full-list / detail views use the CRUD APIs (PG), which show disabled rows.
Package control is the admin-plane HTTP API: /auth/*, CRUD across the eight catalog kinds, /version, /master-key/*, /reload.
Mount(r, deps) wires huma+chi onto an existing chi router and returns the huma.API. main.go constructs Deps and calls Mount.
GET /debug/snapshot — render the current data-plane snapshot for operator inspection. The snapshot is what the hot path actually sees; diffing it against PG (via the regular admin list endpoints) is the fastest way to spot what sanitize dropped.
detail=counts (default): per-kind row counts + per-policy reverse-join
sizes. Tiny payload, scans nothing.
detail=full: every entity in stable slug order, sanitized
spec included. Bigger; admin-only, no SLO.
list_filter.go wires the pure pkg/filter engine into the generic CRUD list handler. huma binds typed structs, but the filter allowlist is per-resource dynamic, so a small middleware stashes the raw url.Values on the request context and the list resolver reads them back to feed filterSchema.Parse. See registerKind in crud.go and the per-resource schemas in list_schemas.go.
list_schemas.go declares the per-resource filter allowlists consumed by the generic list handler (registerKind). Each schema is the single source of truth for that resource's filterable/sortable params: the accessors are typed closures, so renaming an underlying spec field is a compile error here. The query-param name, the allowlist entry, and the match logic all derive from one Field literal.
Covers all eight catalog kinds. Time filters (created/updated, release/ deprecation dates) read Metadata timestamps + spec date strings; ?label= k=v works on every kind via the Labels hook. The host-key circuit-breaker state filter (?health=) is intentionally absent here — it needs a snapshot+kv join the pure store-slice engine can't express; see docs/filtering.md F3.
Logs read-side endpoints: the full per-request lifecycle view that drives the frontend Logs page. A "log" is what happened on a request (routing, status, timing, tokens, errors, identity) — i.e. the full usage.Event — with the captured request/response bodies attached when payload logging was opted in for that request ("(not logged)" otherwise).
GET /logs full lifecycle records, newest first, filterable
GET /logs/{request_id} one record + its captured bodies (if any)
Distinction from /usage: /usage is the narrow metrics projection (billing); /logs is the full record + optional body. Both read the same log event stream via usagelog.Reader; the body joins by request_id via payload.Reader.
Policy model-grant normalization + enabled-resolution guard.
At policy create/update the catalog-ref strings in Spec.Models (and each RLBinding's Models) are:
- slugified to canonical form — operators may paste real-world names ("openai/GPT-4o", "anthropic/claude-3.5") and they're rewritten to the stored slug form ("openai/gpt-4o", "anthropic/claude-3-5"). This keeps PG, the data-plane snapshot, and the picker agreeing on one string.
- resolved against the catalog and required to match at least one *enabled* binding (model enabled, host enabled, binding enabled). Host-only "@host" refs only require the host to exist + be enabled — they grant every present and future binding on that host.
Refs that resolve to nothing enabled are rejected with 400. This is the cross-entity check the per-row policy.Validate() (grammar only, no catalog access) can't perform. Host-key / relay-key existence is deliberately NOT checked here — the inference path handles those at request time.
Attach / detach RelayKey ↔ Policy from the policy side. The authoritative field is RelayKey.Spec.PolicyID (1:N — one policy per key, many keys per policy); these endpoints mutate it so the policy form can manage its key roster without round-tripping through the relay-key form.
POST /policies/by-id/{id}/relay-keys/{relayKeyId} — attach
DELETE /policies/by-id/{id}/relay-keys/{relayKeyId} — detach
Attach overwrites any existing PolicyID on the relay key (no confirmation): moving a key from policy A to policy B is the common case. Detach succeeds when the key currently points at this policy; mismatched detach returns 409 to surface the drift instead of silently no-op'ing.
GET /{kind}/by-id/{id}/references — generic reverse-ref lookup.
Returns every PG row that references the target entity. Used by the admin UI for blast-radius confirmation dialogs ("deleting this rate limit will affect N policies") and for inline "in use by" lists.
Walks the relevant stores per kind; admin-only, no SLO. Indices come later if scan time matters. Reading PG (not the catalog snapshot) so disabled / soft-dropped refs are still visible — they exist in PG even when the data plane has filtered them out.
Custom POST /relay-keys: generates the bearer plaintext server-side via relaykey.Generate, persists only the hash + prefix, and returns the plaintext exactly once on the create response. The generic CRUD POST in registerKind is skipped for this kind (skipCreate=true) so callers can't sneak a precomputed keyHash through.
subresources.go binds the catalogview read projections to chi/huma as resource-navigation endpoints — "API UX":
GET /models/{ref}/hosts hosts serving this model (+ binding + pricing)
GET /models/{ref}/pricing pricing per host for this model
GET /models/{ref}/policies policies granting this model (+ per-model limits)
GET /hosts/{ref}/models models this host serves (+ binding + pricing)
GET /hosts/{ref}/keys upstream credentials this host owns (secret-free)
GET /hosts/{ref}/policies host-tier serving policies this host owns
GET /policies/{ref}/models models this policy grants (+ per-model limits)
GET /policies/{ref}/hosts hosts this policy reaches (+ host-keys)
GET /policies/{ref}/rate-limits rate-limit rule sets this policy references
All composition lives in app/catalogview (PG-backed, full state incl. disabled rows). These handlers are thin: build the Service from the stores, call the projection, return its rows. {ref} is a slug or UUID id.
Usage read-side endpoints: surface the JSONL stream the post-flight observer writes via filtered + aggregated queries. Backed by a usagelog.Reader, so the store can swap (file today, ClickHouse later) without touching this layer.
GET /usage/events raw events, newest first, filterable GET /usage/summary per-group aggregates over the filtered set
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AdminTokenMiddleware ¶
AdminTokenMiddleware accepts the request iff it carries a valid bearer admin token in the Authorization header. On success it injects an Actor with AdminToken=true into context so downstream handlers + authz see a valid caller. On absent/mismatched token it does nothing — the next middleware in line (session) may still authenticate.
adminToken is the cleartext token from RELAY_ADMIN_TOKEN. Empty disables the bypass entirely.
func CORS ¶
CORS returns a middleware that handles preflight (OPTIONS) and adds the headers needed for a credentialed browser fetch from one of the allowed origins. Required because the control API is consumed by a frontend on a separate hostname (e.g. https://relay.wyolet.dev).
allowedOrigins is matched exactly against the Origin header — wildcard origins are not allowed when Access-Control-Allow-Credentials is true, per the CORS spec.
func Mount ¶
Mount installs the control-plane huma API on r and registers all operations. Returns the huma.API so the caller can attach test-only ops.
Middleware order on r (caller is responsible for wiring these onto the chi router before any operations are matched):
- session.Manager.Middleware — loads cookie session, sets Actor in ctx
- AdminTokenMiddleware — sets Actor in ctx when bearer matches
Per-operation Middlewares attached by Mount enforce RequireActor on every protected route. Public ops (/auth/login, /version) omit it.
Types ¶
type Deps ¶
type Deps struct {
// Identity is the YAML-backed user store used by /auth/login.
Identity *identity.Store
// Sessions is the cookie-backed session manager. Login/Logout write to
// it; the session middleware (installed by Mount) reads from it.
Sessions *session.Manager
// AdminToken is the cleartext break-glass bearer. Empty disables the
// bypass. Validated by AdminTokenMiddleware; not used directly by
// handlers.
AdminToken string
// Authz is the policy-decision interface. Handlers call
// d.Authz.Authorize before mutations; today's impl is permissive for
// any authenticated caller.
Authz authz.Authorizer
// Catalog is the in-memory snapshot used for slug→id resolution on
// reads. Writes go through Stores.
Catalog *appcatalog.Catalog
// Stores is the bundle of eight typed stores used by CRUD writes.
Stores *appcatalog.Stores
// CookieSecure controls the Secure attribute on the session cookie.
// Surfaced here so the OpenAPI doc can reflect deployment posture.
CookieSecure bool
// UsageReader serves /usage/* read-side endpoints. nil disables
// them — useful for deployments where usage events are consumed
// from a separate store.
UsageReader usagelog.Reader
// PayloadReader serves /payloads/* read-side endpoints (the Logs view).
// nil disables them — e.g. minimal builds or deployments where captured
// bodies are consumed from a separate store.
PayloadReader payloadlog.Reader
// Selector is the keypool circuit-breaker owner, shared with the data
// plane. The host-key health endpoint reads per-key breaker state through
// it. nil disables that endpoint.
Selector *keypool.Selector
}
Deps is the typed dependency bundle for the admin plane.
type UsageFilterInput ¶
type UsageFilterInput struct {
Since string `query:"since" doc:"Relative window (e.g. \"1h\", \"24h\", \"7d\"). Default \"1h\". Ignored when from is set."`
From string `query:"from" doc:"Absolute lower bound (RFC3339). Overrides since."`
To string `query:"to" doc:"Absolute upper bound (RFC3339)."`
RequestID string `query:"request_id" doc:"Exact match on a single request id (deep-link one event)."`
RelayKeyHash []string `query:"relay_key_hash" doc:"Match any of the given sha256 hashes of the inbound bearer."`
PolicyID []string `query:"policy_id" doc:"Match any of the given Policy.metadata.id values."`
ModelID []string `query:"model_id" doc:"Match any of the given Model.metadata.id values."`
HostID []string `query:"host_id" doc:"Match any of the given Host.metadata.id values."`
Source []string `query:"source" doc:"Match any of \"pipeline\" | \"proxy\" | \"ws\" | \"batch\"."`
FinishReason []string `query:"finish_reason" doc:"Match any of \"stop\" | \"length\" | \"tool_calls\" | \"content_filter\" | \"refusal\"."`
ErrorKind []string `query:"error_kind" doc:"Match any of the given error_kind values."`
StatusMin int `query:"status_min" doc:"Minimum HTTP status to include."`
StatusMax int `query:"status_max" doc:"Maximum HTTP status to include."`
HostKeyID []string `query:"host_key_id" doc:"Match any of the given HostKey.metadata.id values."`
RequestedModel []string `query:"requested_model" doc:"Match any of the model strings as the caller sent them."`
Status []int `query:"status" doc:"Match any of these exact HTTP status codes."`
StatusClass string `query:"status_class" doc:"Convenience status band: \"2xx\" | \"4xx\" | \"5xx\". Sets status_min/max."`
Streamed string `query:"streamed" enum:"true,false" doc:"true = only streamed responses, false = only non-streamed."`
Error string `query:"error" enum:"true,false" doc:"true = only errors (status>=400 or error_kind set), false = only successes."`
AttemptsMin int `query:"attempts_min" doc:"Minimum upstream try count (failover) — finds retried requests."`
DurationMsMin int64 `query:"duration_ms_min" doc:"Minimum total duration in ms (slow-request filter)."`
DurationMsMax int64 `query:"duration_ms_max" doc:"Maximum total duration in ms."`
TTFTMsMin int64 `query:"ttft_ms_min" doc:"Minimum upstream time-to-first-byte (ms); excludes requests with no upstream timing."`
TTFTMsMax int64 `query:"ttft_ms_max" doc:"Maximum upstream time-to-first-byte (ms)."`
Q string `query:"q" doc:"Free-text substring across request_id, model_id, requested_model, source."`
}