diff --git a/.env.example b/.env.example index 096815f7..ef34df6a 100644 --- a/.env.example +++ b/.env.example @@ -193,7 +193,7 @@ # FANOUT_ROLLUP_INTERVAL= # FANOUT_ROLLUP_SKIP_TO_LATEST; boolean -# RollupSkipToLatest, set once at boot, advances the service and edge rollup watermarks to the current max ingested timestamp. Existing history is not aggregated into those analytical rollups. Completed-batch trace caches still rebuild all retained history in bounded passes; uncached files remain immediately queryable. Useful for large pre-seeded datasets (benchmarks, restores); off in normal operation. +# Set once at boot, this advances the service and edge rollup watermarks to the current max ingested timestamp. Existing history is not aggregated into those analytical rollups. Completed-batch trace caches still rebuild all retained history in bounded passes; uncached files remain immediately queryable. Useful for large pre-seeded datasets (benchmarks, restores); off in normal operation. # Default: false # FANOUT_ROLLUP_SKIP_TO_LATEST= diff --git a/cmd/fanout-docgen/main.go b/cmd/fanout-docgen/main.go index 7f8a185d..53874463 100644 --- a/cmd/fanout-docgen/main.go +++ b/cmd/fanout-docgen/main.go @@ -232,7 +232,7 @@ func renderAlertEnv(vars []field) []byte { b.WriteString("---\n") b.WriteString(`title: "Alert expression variables"` + "\n") b.WriteString(`description: "Every value an alert rule expression can read, and what it means."` + "\n") - b.WriteString(`summary: "The variables available to a CEL alert rule, generated from the evaluation environment itself."` + "\n") + b.WriteString(`summary: "The variables available to a CEL alert rule."` + "\n") b.WriteString("read_when:\n") b.WriteString(` - "You are writing an alert rule and need the variable names."` + "\n") b.WriteString(` - "A rule compiles but never fires, and you want to check what it is reading."` + "\n") @@ -243,9 +243,7 @@ func renderAlertEnv(vars []field) []byte { b.WriteString("{/* Generated by cmd/fanout-docgen from internal/alert. Edit the generator, not this page. */}\n\n") b.WriteString("An alert rule's `expression` is [CEL](https://cel.dev) evaluated\n") - b.WriteString("against one service's rollup for the current window. These are the values it\n") - b.WriteString("can read — the names below match the variables declared and bound by the\n") - b.WriteString("evaluator.\n\n") + b.WriteString("against one service's rollup for the current window. It can read the following variables.\n\n") b.WriteString("Nonempty expressions supplied when creating or replacing rules through the\n") b.WriteString("API are type-checked against these declared variables. Unknown variables,\n") b.WriteString("type mistakes, and non-boolean results are rejected before the rule is saved.\n\n") @@ -556,7 +554,6 @@ func renderPage(name string, page groupPage, fields []field) []byte { b.WriteString("Every setting below is read once at startup and requires a restart to change.\n") b.WriteString("Built-in defaults are overridden by nonempty environment values.\n") b.WriteString("Unknown `FANOUT_` names fail startup; platform-injected service names are tolerated.\n") - b.WriteString("Custom names use `FANOUT_` plus a clear name, adding the area only when it clarifies.\n") b.WriteString("Vendor credentials and endpoints keep the vendor's documented names.\n\n") if name == "ai" { b.WriteString("Fanout also recognizes `ANTHROPIC_API_KEY`, `ANTHROPIC_BASE_URL`,\n") diff --git a/cmd/fanout-docgen/mcptools.go b/cmd/fanout-docgen/mcptools.go index 62b85891..39b696ed 100644 --- a/cmd/fanout-docgen/mcptools.go +++ b/cmd/fanout-docgen/mcptools.go @@ -40,7 +40,7 @@ func renderMCPTools() ([]byte, error) { b.WriteString("title: \"MCP tools\"\n") b.WriteString("description: \"Every tool a Fanout instance exposes over MCP, with its inputs and whether it changes anything.\"\n") fmt.Fprintf(&b, - "summary: \"%s served at /mcp, each with its inputs and mutation semantics, taken from the server's Apps-enabled tools/list answer.\"\n", + "summary: \"%s served at /mcp, each with its inputs and mutation semantics.\"\n", count(len(tools), "tool"), ) b.WriteString("read_when:\n") @@ -55,7 +55,7 @@ func renderMCPTools() ([]byte, error) { b.WriteString(" this page. */}\n\n") fmt.Fprintf(&b, - "A Fanout instance serves MCP at `/mcp`. %s, listed below exactly as the\nserver reports them to an Apps-enabled client. Ordinary clients receive the %d model-visible tools; the three app-only helpers require Apps negotiation and are never offered to the model.\n\n", + "A Fanout instance serves MCP at `/mcp`. %s are available to an Apps-enabled client. Ordinary clients receive the %d model-visible tools; the three app-only helpers require Apps negotiation and are never offered to the model.\n\n", count(len(tools), "tool"), modelVisible, ) @@ -124,7 +124,7 @@ func renderMCPTools() ([]byte, error) { for _, tool := range tools { fmt.Fprintf(&b, "## %s\n\n", tool.Name) - fmt.Fprintf(&b, "**%s** — %s\n\n", mdx(tool.Title), mdx(tool.Description)) + fmt.Fprintf(&b, "**%s**: %s\n\n", mdx(tool.Title), mdx(tool.Description)) fmt.Fprintf(&b, "%s\n\n", effectProse(tool)) if tool.AppOnly { b.WriteString("**Visibility:** app only; no resource attachment.\n\n") @@ -172,7 +172,7 @@ func renderMCPTools() ([]byte, error) { b.WriteString("\n") } b.WriteString("## Authentication\n\n") - b.WriteString("MCP uses OAuth tokens bound to the instance’s resource URI.\n[Connect an agent](/docs/guides/mcp#scopes) documents protocol\nrequirements and delegated scopes. Dashboard tools operate on the authenticated\nuser’s own dashboards and require the account capability\n`dashboards:manage-own`; see [roles](/docs/reference/roles).\n\n## Other interfaces\n\nUse OTLP to write telemetry. Configuration uses environment variables, and user\nand alert management use the [HTTP API](/docs/reference/api).\n") + b.WriteString("MCP uses OAuth tokens bound to the instance's resource URI.\n[Connect an agent](/docs/guides/mcp#scopes) documents protocol\nrequirements and delegated scopes. Dashboard tools operate on the authenticated\nuser's own dashboards and require the account capability\n`dashboards:manage-own`; see [roles](/docs/reference/roles).\n\n## Other interfaces\n\nUse OTLP to write telemetry. Configuration uses environment variables, and user\nand alert management use the [HTTP API](/docs/reference/api).\n") toolCount = tools return []byte(b.String()), nil diff --git a/cmd/fanout-docgen/roles.go b/cmd/fanout-docgen/roles.go index 7b8ce2c6..2e9f8f4b 100644 --- a/cmd/fanout-docgen/roles.go +++ b/cmd/fanout-docgen/roles.go @@ -72,7 +72,7 @@ func renderRoles() ([]byte, error) { b.WriteString("---\n") b.WriteString("title: \"Roles\"\n") b.WriteString("description: \"The three Fanout roles and the capabilities each one carries.\"\n") - b.WriteString("summary: \"viewer, operator and admin, and exactly what each is permitted to do — taken from the authorization table the server enforces.\"\n") + b.WriteString("summary: \"viewer, operator and admin, and what each is permitted to do.\"\n") b.WriteString("read_when:\n") b.WriteString(" - \"You are deciding which role to give someone.\"\n") b.WriteString(" - \"You are mapping identity-provider groups onto Fanout roles.\"\n") @@ -83,7 +83,7 @@ func renderRoles() ([]byte, error) { b.WriteString("{/* Generated by cmd/fanout-docgen from internal/api. Edit the generator, not this page. */}\n\n") - b.WriteString("Each user has one fixed role: `viewer`, `operator` or `admin`. The table is\ngenerated from the capability map enforced by the authorization middleware.\n\n") + b.WriteString("Each user has one fixed role: `viewer`, `operator` or `admin`.\n\n") b.WriteString("| Capability |") for _, role := range roleOrder { @@ -109,7 +109,7 @@ func renderRoles() ([]byte, error) { b.WriteString("\n## What each capability permits\n\n") for _, capability := range caps { - fmt.Fprintf(&b, "- `%s` — %s\n", capability, capabilityNotes[capability]) + fmt.Fprintf(&b, "- `%s`: %s\n", capability, capabilityNotes[capability]) } b.WriteString("\n## Who assigns roles\n\n") @@ -120,10 +120,8 @@ func renderRoles() ([]byte, error) { b.WriteString("[Authentication modes](/docs/concepts/authentication) covers the details,\n") b.WriteString("including why the last active administrator is never demoted.\n\n") - b.WriteString(":::caution[A role is not a data boundary]\n") - b.WriteString("Roles gate capabilities, not rows. Every role that can read telemetry can read\n") - b.WriteString("all of it — namespaces scope queries, not access. An instance is one trust\n") - b.WriteString("domain.\n") + b.WriteString(":::caution[Telemetry access]\n") + b.WriteString("See [namespace access limits](/docs/reference/capabilities#operating-limits).\n") b.WriteString(":::\n") return []byte(b.String()), nil diff --git a/cmd/fanout-docgen/routes.go b/cmd/fanout-docgen/routes.go index 65bcc3c3..8e099837 100644 --- a/cmd/fanout-docgen/routes.go +++ b/cmd/fanout-docgen/routes.go @@ -234,7 +234,7 @@ func renderRoutes(routes []api.RouteDoc) []byte { b.WriteString("---\n") b.WriteString("title: \"HTTP routes\"\n") b.WriteString("description: \"Every HTTP route the server registers and what guards each one. Mostly the browser client's backend rather than a public API.\"\n") - b.WriteString("summary: \"Every route the server registers — application, operational, protocol and the SPA catch-all — with the authorization requirement for each, taken from the middleware that enforces them.\"\n") + b.WriteString("summary: \"Application, operational and protocol routes and the SPA catch-all, with the authorization requirement for each.\"\n") b.WriteString("read_when:\n") b.WriteString(" - \"You are auditing what an instance exposes and what guards each route.\"\n") b.WriteString(" - \"A request came back 401 or 403 and you want to know which capability it wanted.\"\n") @@ -251,13 +251,13 @@ func renderRoutes(routes []api.RouteDoc) []byte { b.WriteString("## Conditional routes\n\nThe table includes every route the binary can register. These routes depend on\nconfiguration:\n\n") - b.WriteString("- `/debug/pprof/*` — only when `FANOUT_PPROF_ENABLED` is true, which is\n") + b.WriteString("- `/debug/pprof/*`: only when `FANOUT_PPROF_ENABLED` is true, which is\n") b.WriteString(" **not** the default. Enabling it also turns on mutex and block sampling.\n") - b.WriteString("- `/mcp`, `/api/mcp`, `/api/auth/oauth/*`, `/oauth/*` and `/.well-known/*` — only when\n") + b.WriteString("- `/mcp`, `/api/mcp`, `/api/auth/oauth/*`, `/oauth/*` and `/.well-known/*`: only when\n") b.WriteString(" `FANOUT_MCP_ENABLED` is true, which **is** the default.\n") - b.WriteString("- `/api/agent/*` — only when `FANOUT_AI_PROVIDER` selects `anthropic` or\n") + b.WriteString("- `/api/agent/*`: only when `FANOUT_AI_PROVIDER` selects `anthropic` or\n") b.WriteString(" `openai`, with that provider's required key. An empty provider disables chat.\n") - b.WriteString("- `/api/auth/oidc/start` and `/api/auth/oidc/callback` — only in\n") + b.WriteString("- `/api/auth/oidc/start` and `/api/auth/oidc/callback`: only in\n") b.WriteString(" `FANOUT_AUTH_MODE=oidc`.\n\n") b.WriteString("Everything else is always registered. A path shown with `:name` or `*` is the\n") b.WriteString("pattern Echo matches on, not a literal URL.\n") @@ -267,7 +267,7 @@ func renderRoutes(routes []api.RouteDoc) []byte { b.WriteString("## Errors\n\nApplication API errors, including unknown routes and wrong methods, return objects shaped like\n`{\"code\":\"bad_request\",\"message\":\"Safe explanation\"}`. Codes are stable\nsnake_case identifiers; clients branch on `code` rather than message text.\nInvalid dashboard or panel specs also include `problems` with field paths,\nmessages and optional hints. Every error response uses `Cache-Control: no-store`;\nHEAD errors have no body. Server failures return a short, safe message; the\nunderlying error is logged and never returned.\n\nDefault codes by HTTP status:\n\n| Status | Code |\n|---|---|\n| 400 | `bad_request` |\n| 401 | `unauthorized` |\n| 403 | `forbidden` |\n| 404 | `not_found` |\n| 405 | `method_not_allowed` |\n| 409 | `conflict` |\n| 413 | `payload_too_large` |\n| 415 | `unsupported_media_type` |\n| 422 | `unprocessable` |\n| 429 | `rate_limited` |\n| 500 | `internal` |\n| 502 | `bad_gateway` |\n| 503 | `unavailable` |\n| 504 | `timeout` |\n\nOther 4xx statuses use `bad_request`; other 5xx statuses use `internal`.\nSpecific conditions use `already_current`, `dashboard_version_not_found`,\n`dashboard_version_conflict`, `invalid_spec` and `last_active_administrator`.\nTool failures carry `error: {code, message}` inside the tool result.\nPanel execution results keep their own status and error fields.\nOAuth, OTLP/gRPC, MCP JSON-RPC and AG-UI run errors keep their protocol spelling.\nOperational responses at `/healthz`, `/readyz` and `/metrics` keep their bodies.\n\n") - b.WriteString("The **Requires** column comes from the authorization middleware for each method\nand path. [Roles](/docs/reference/roles) maps capabilities to roles.\n\n") + b.WriteString("[Roles](/docs/reference/roles) maps capabilities to roles.\n\n") b.WriteString("| Method | Path | Requires |\n") b.WriteString("|---|---|---|\n") @@ -276,13 +276,13 @@ func renderRoutes(routes []api.RouteDoc) []byte { } b.WriteString("\n## What the requirements mean\n\n") - b.WriteString("- **none** — reachable without a credential.\n") + b.WriteString("- **none**: reachable without a credential.\n") - b.WriteString("- **any signed-in user** — a session, but no particular capability.\n") - b.WriteString("- **protocol handshake** — the OAuth and MCP endpoints, which authenticate\n") + b.WriteString("- **any signed-in user**: a session, but no particular capability.\n") + b.WriteString("- **protocol handshake**: the OAuth and MCP endpoints, which authenticate\n") b.WriteString(" as part of their own protocol rather than through the middleware.\n") - b.WriteString("- **a named capability** — the caller's role must carry it.\n") - b.WriteString("- **or a service credential** — additionally reachable without a browser\n") + b.WriteString("- **a named capability**: the caller's role must carry it.\n") + b.WriteString("- **or a service credential**: additionally reachable without a browser\n") b.WriteString(" session, which is how a scraper reaches the metrics endpoint.\n\n") b.WriteString("`Any` registers every HTTP method with the same authorization requirement.\n`GET /*` serves the single-page application for browser paths. Unknown `/api/*`\nand OTLP paths return errors.\n") diff --git a/internal/config/config.go b/internal/config/config.go index aaaf23bc..e62a3a84 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -69,7 +69,7 @@ type Config struct { // MaintenanceInterval controls Parquet retention and compaction, and // query-cache checkpointing. MaintenanceInterval time.Duration `group:"storage" env:"FANOUT_MAINTENANCE_INTERVAL" default:"1h"` - // RollupSkipToLatest, set once at boot, advances the service and edge rollup + // Set once at boot, this advances the service and edge rollup // watermarks to the current max ingested timestamp. Existing history is not // aggregated into those analytical rollups. Completed-batch trace caches // still rebuild all retained history in bounded passes; diff --git a/site/src/content/docs/docs/concepts/architecture.mdx b/site/src/content/docs/docs/concepts/architecture.mdx index b73aab3c..eef5250c 100644 --- a/site/src/content/docs/docs/concepts/architecture.mdx +++ b/site/src/content/docs/docs/concepts/architecture.mdx @@ -35,9 +35,7 @@ the UI. Monitor the instance from outside its host. ## Deployment scope -Fanout serves one trust domain on one machine. Namespaces filter telemetry -queries; every telemetry reader can access all namespaces. Use separate -instances for mutually untrusted teams. +Fanout runs on one machine; see [trust domains and namespace access](/docs/reference/capabilities#operating-limits). Workloads requiring horizontal ingest, replication or an object-storage tier require a backend that provides those capabilities. diff --git a/site/src/content/docs/docs/concepts/authentication.mdx b/site/src/content/docs/docs/concepts/authentication.mdx index f208ec7b..a4758c99 100644 --- a/site/src/content/docs/docs/concepts/authentication.mdx +++ b/site/src/content/docs/docs/concepts/authentication.mdx @@ -1,7 +1,7 @@ --- title: Authentication modes description: Why Fanout has two sign-in modes, what each one owns, and where the boundary between them sits. -summary: Local codes versus an identity provider — who owns roles in each, and what happens on every login. +summary: Local codes versus an identity provider, who owns roles in each, and what happens on every login. read_when: - You are choosing between local sign-in and OIDC. - You expect a provider group change to take effect and want to know when it does. @@ -32,7 +32,7 @@ an email address. ## OIDC An identity provider handles sign-in. Group and domain allowlists are evaluated -on every login, including for linked identities. Removing a user’s eligibility +on every login, including for linked identities. Removing a user's eligibility blocks their next login; it does not revoke a browser session already issued. Suspending the account in Fanout blocks access and revokes its sessions. @@ -75,6 +75,5 @@ sessions. The last active administrator cannot be demoted, suspended or deleted. ## Access boundaries -Every telemetry reader can query every namespace. Namespaces filter queries; -separate trust domains require separate instances. Dashboard access is scoped -to the owner for every role, including administrators. +See [namespace access limits](/docs/reference/capabilities#operating-limits). +Dashboard access is scoped to the owner for every role, including administrators. diff --git a/site/src/content/docs/docs/concepts/storage.mdx b/site/src/content/docs/docs/concepts/storage.mdx index 0add28c2..aa66f241 100644 --- a/site/src/content/docs/docs/concepts/storage.mdx +++ b/site/src/content/docs/docs/concepts/storage.mdx @@ -11,8 +11,8 @@ status: preview Fanout retains telemetry as ordinary Parquet files in self-contained atomic batch directories. Persistent sidecars index traces, and DuckDB handles SQL, broad scans, and rebuildable analytical rollups in the same process. -Application state—users, sessions, dashboards, alert rules, and agent -history—lives in SQLite. +Application state (users, sessions, dashboards, alert rules, and agent +history) lives in SQLite. | Component | Purpose | |---|---| @@ -103,13 +103,14 @@ compiled into the binary. It downloads no extensions at runtime. Native source builds use `scripts/with-duckdb.sh`, which checks the platform archive and uses the matching engine headers; the ordinary `just` build and test recipes invoke it automatically. DuckDB 2's asynchronous I/O uses its default memory-governed -read-ahead rather than an unlimited queue. Rooted service traversal uses keyed +read-ahead, which bounds the I/O queue. Rooted service traversal uses keyed recursion with explicit hop, accumulated-node, and execution-time limits. Offline verification decodes every Parquet column with the native engine and separately checks physical schemas, span ordering, row counts, and trace-index ranges. It reuses one bounded in-memory engine and creates no spill files. -Discarded hashes force decoding; they do not establish a stored checksum. +Verification computes and discards hashes to force decoding, without storing +a checksum. Verification failures such as memory exhaustion or cancellation never authorize quarantine. @@ -158,9 +159,9 @@ active ID even before cleanup; they cannot invalidate unrelated traces. Compaction derives the output trace contributions from complete cached inputs. If an input is not yet cached, the output remains directly queryable until its own backfill. -The private cache stores per-batch trace parts and one -incremental candidate per trace. It does not copy every span or log timestamp -or maintain global scope indexes. Complete cached compaction replacements +Trace data in the private cache consists of per-batch trace parts and one +incremental candidate per trace. Timestamps are aggregated by batch and trace; +scope lookups use those parts and candidates. Complete cached compaction replacements preserve candidate bounds without recomputation. Retention and incomplete replacements recompute only affected trace IDs from active contributions. A semantic version covers the cache schema and aggregation rules; a version @@ -172,9 +173,9 @@ Service-version observations populate their analytical rollup on a separate worker, so an analytical-gate wait cannot delay the completed-batch worker. Completed-batch and analytical writers use separate gates and two connections outside the read pool. They modify disjoint tables; maintenance holds both gates -for checkpointing. Trace reads avoid materializing every retained candidate into -a temporary relation for each request. A snapshot-local ambiguity probe keeps -unused batch-parts aggregation out of single-scope read plans. +for checkpointing. Trace reads query retained candidates directly. A +snapshot-local ambiguity probe includes batch-parts aggregation only when +the requested scope needs it. Narrow file selections use explicit lists from footer bounds. Broad selections use a glob with a captured-batch-ID filter, which excludes files published after diff --git a/site/src/content/docs/docs/get-started/first-boot.mdx b/site/src/content/docs/docs/get-started/first-boot.mdx index d3b33858..71a622ab 100644 --- a/site/src/content/docs/docs/get-started/first-boot.mdx +++ b/site/src/content/docs/docs/get-started/first-boot.mdx @@ -62,4 +62,4 @@ link for an existing active user. OIDC deployments use the provider's flow inste ## Next [Send your first telemetry](/docs/get-started/send-telemetry), then investigate it with -[Fanout’s agent or an MCP client](/docs/get-started/send-telemetry#investigate-the-data). +[Fanout's agent or an MCP client](/docs/get-started/send-telemetry#investigate-the-data). diff --git a/site/src/content/docs/docs/get-started/install.mdx b/site/src/content/docs/docs/get-started/install.mdx index 97d3196d..6ff7115a 100644 --- a/site/src/content/docs/docs/get-started/install.mdx +++ b/site/src/content/docs/docs/get-started/install.mdx @@ -55,7 +55,7 @@ against it before extracting**, and installs to `/usr/local/bin` when that is writable or `~/.local/bin` otherwise. Set `FANOUT_PREFIX` to choose the directory, or `FANOUT_VERSION` to pin a tag instead of taking the latest. -Versions are CalVer — `vYYYY.M.N[-alpha|-beta|-rc]`, numbered from 0 within each +Versions use CalVer format `vYYYY.M.N[-alpha|-beta|-rc]`, numbered from 0 within each month across all maturity suffixes. Fanout is alpha, so `v2026.10.1-alpha` is the second release of October 2026. GA has no suffix. Container tags omit the leading `v`, and every release moves `latest`. @@ -80,7 +80,7 @@ export FANOUT_AUTH_CODE_SECRET=$(openssl rand -hex 32) ## Verify what you downloaded The install script checks the archive for you. If you took one from the releases -page by hand, check it yourself — both the checksum and the build provenance: +page by hand, check both the checksum and the build provenance: ```sh sha256sum -c SHA256SUMS @@ -99,25 +99,15 @@ Release images are multi-architecture for `linux/amd64` and `linux/arm64`, and archives cover Linux and macOS on both. Every artifact is built on a native runner, because DuckDB needs cgo. -| Image tag | Points at | -|---|---| -| `labstack/fanout:latest` | the newest release | -| `labstack/fanout:2026.10.1-alpha` | that exact release | -| `ghcr.io/labstack/fanout:main` | the tip of `main` | -| `ghcr.io/labstack/fanout:sha-` | one specific commit | - -Release and `latest` tags also exist under `ghcr.io/labstack/fanout`. -Development tags are GHCR-only. +See [image tags](/docs/reference/configuration#image-tags). See the [release contract](https://github.com/labstack/fanout/blob/main/docs/release.md) for the artifact inventory and verification workflow. ## What an instance opens -| Port or path | Purpose | Native default | -|---|---|---| -| `7520` | Browser, API, chat, MCP, OTLP/gRPC and OTLP/HTTP | `127.0.0.1:7520`, loopback | -| `./data` | Telemetry, query state, users, sessions, dashboards, alerts and agent history | `./data` | +See [ports and paths](/docs/reference/configuration#ports-and-paths) for listener +and data-directory defaults. Complete [first boot](/docs/get-started/first-boot) to create the administrator and ingest token, then [configure exporters](/docs/get-started/send-telemetry). **Connect telemetry** diff --git a/site/src/content/docs/docs/get-started/overview.mdx b/site/src/content/docs/docs/get-started/overview.mdx index 3370e387..e2848404 100644 --- a/site/src/content/docs/docs/get-started/overview.mdx +++ b/site/src/content/docs/docs/get-started/overview.mdx @@ -51,8 +51,7 @@ operating tradeoffs. SDKs and Collectors export to Fanout as their telemetry backend. Fanout runs on one machine with local storage. It has no clustering, replication or object tier. -Namespaces scope queries. Every account with telemetry access can read every -namespace; deploy separate instances for separate trust domains. +See [namespace access limits](/docs/reference/capabilities#operating-limits). ## Who it is for @@ -63,4 +62,4 @@ The reference deployment target is a single small VM. ## Stability Fanout is alpha. User-facing contracts can change without migration paths. -The [capabilities page](/docs/reference/capabilities) lists each feature’s stability. +The [capabilities page](/docs/reference/capabilities) lists each feature's stability. diff --git a/site/src/content/docs/docs/get-started/send-telemetry.mdx b/site/src/content/docs/docs/get-started/send-telemetry.mdx index e319eeaf..ed06e569 100644 --- a/site/src/content/docs/docs/get-started/send-telemetry.mdx +++ b/site/src/content/docs/docs/get-started/send-telemetry.mdx @@ -76,8 +76,8 @@ service: The gRPC exporter requires an explicit port. Use `https://` for TLS and `:443` for a proxy serving standard HTTPS. -If nothing in front of Fanout terminates TLS — a local instance, or one on a -private network — the endpoint is `http://fanout.example.com:7520` and the +If nothing in front of Fanout terminates TLS (a local instance, or one on a +private network), the endpoint is `http://fanout.example.com:7520` and the exporter needs `tls: { insecure: true }` under `otlp/fanout`, or it will fail to connect. **Connect telemetry** in the application generates this block with your own endpoint already in it, and includes that line when it applies. @@ -174,8 +174,7 @@ run on hosts that also run observed services. - **What changed?** uses `service.version` on application resources, along with runtime and SDK attributes. Send a new version when deploying a service. -Silence is not health. After two reporting intervals, an endpoint with no -result shows **no recent result**, and a host or container shows **not reporting**, +After two reporting intervals, an endpoint with no result shows **no recent result**, and a host or container shows **not reporting**, both in the bad state. A receiver that has never sent data shows an empty panel. If the machine running Fanout fails, Fanout cannot report that failure on its own page: monitor Fanout itself from outside that machine. @@ -193,9 +192,7 @@ instance hold several services' telemetry without them running together. A query with no namespace filter spans all namespaces. Add a namespace filter to narrow the result. -They are not an authorisation boundary. A namespace is not a user, an -organisation or a tenant, and anyone who can query the instance can query every -namespace in it. An instance is the trust domain. +See [namespace access limits](/docs/reference/capabilities#operating-limits). ## One listener @@ -212,9 +209,9 @@ See [terminate TLS](/docs/guides/tls). ## Investigate the data With an [AI provider configured](/docs/reference/configuration/ai), open chat and ask a -question such as “Why is checkout slower in the last hour?” The agent queries +question such as "Why is checkout slower in the last hour?" The agent queries telemetry and displays its evidence in interactive views. Ask it to [build a dashboard](/docs/guides/dashboards) to save a view for repeated use. To use your own agent, [connect over MCP](/docs/guides/mcp). MCP does not -require Fanout’s built-in AI provider. +require Fanout's built-in AI provider. diff --git a/site/src/content/docs/docs/guides/alerts.mdx b/site/src/content/docs/docs/guides/alerts.mdx index b7421977..543f1f79 100644 --- a/site/src/content/docs/docs/guides/alerts.mdx +++ b/site/src/content/docs/docs/guides/alerts.mdx @@ -53,11 +53,9 @@ replacing it; omitted fields take their zero values. ## Variables available to an expression -An expression reads one service's rollup for the current window. The full list — -`error_rate`, `p95`, `throughput`, the baseline deltas and the rest — is -[generated from the evaluation environment -itself](/docs/reference/alert-expressions), so it cannot drift from what the -evaluator actually binds. +An expression reads one service's rollup for the current window. The +[alert expression reference](/docs/reference/alert-expressions) lists every +variable, including `error_rate`, `p95`, `throughput` and the baseline deltas. For example: @@ -72,11 +70,11 @@ prevents a single failure on a quiet service from triggering this rule. An alert moves through three states: -- **pending** — the condition is true but has not held for `for_seconds` yet. +- **pending**: the condition is true but has not held for `for_seconds` yet. With `for_seconds: 0` a rule skips this and fires immediately. -- **firing** — it has held. The webhook is delivered, and re-delivered every +- **firing**: it has held. The webhook is delivered, and re-delivered every `repeat_interval_s` while it stays true. -- **resolved** — the expression went false. If `notify_on_resolve` is set, that +- **resolved**: the expression went false. If `notify_on_resolve` is set, that is delivered too. `cooldown_s` then holds the rule quiet for that long after resolving, which is @@ -98,4 +96,4 @@ Evaluation reads analytical rollups. The rollup pass follows durable telemetry publication; the evaluation interval and `for_seconds` add further delay. [How storage works](/docs/concepts/storage#rollups-follow-durable-publication) explains the publication horizon and watermark. A shorter evaluation interval -does not increase the rollups’ time resolution. +does not increase the rollups' time resolution. diff --git a/site/src/content/docs/docs/guides/backup-and-restore.mdx b/site/src/content/docs/docs/guides/backup-and-restore.mdx index cbf2e0df..b5160c08 100644 --- a/site/src/content/docs/docs/guides/backup-and-restore.mdx +++ b/site/src/content/docs/docs/guides/backup-and-restore.mdx @@ -29,7 +29,7 @@ and the control SQLite database. Preserve all three together. :::caution[Not while it is running] Do not take a recursive copy of a live data directory. A storage-level snapshot is acceptable only if it gives a crash-consistent point-in-time view of the -whole directory at once — a copy that walks the tree while commits publish can +whole directory at once. A copy that walks the tree while commits publish can capture related files from different moments. ::: @@ -51,7 +51,7 @@ capture related files from different moments. representative queries. Migrations run forward at startup. A rollback to the previous binary after they -have run is not assumed safe — restore the pre-upgrade backup with the previous +have run is not assumed safe. Restore the pre-upgrade backup with the previous version instead. ## Never delete an account from the control database diff --git a/site/src/content/docs/docs/guides/dashboards.mdx b/site/src/content/docs/docs/guides/dashboards.mdx index 2953b962..715b322a 100644 --- a/site/src/content/docs/docs/guides/dashboards.mdx +++ b/site/src/content/docs/docs/guides/dashboards.mdx @@ -8,14 +8,14 @@ read_when: status: preview --- -Open chat and describe the system view you need. For example: “Build a dashboard -of checkout latency and errors by service.” Fanout reads the telemetry schema, +Open chat and describe the system view you need. For example: "Build a dashboard +of checkout latency and errors by service." Fanout reads the telemetry schema, previews the panels and saves a dashboard. The save receipt records the saved version and reports panels that need attention. **Open dashboard** opens the current dashboard. -To refine the result, name the panel and the change: “On the checkout latency -panel, add a warning threshold at 500 ms.” Use the receipt's changes and version +To refine the result, name the panel and the change: "On the checkout latency +panel, add a warning threshold at 500 ms." Use the receipt's changes and version history to inspect what changed. ## Build, check and refine @@ -126,12 +126,12 @@ lists column types and semantics. ### System overview defaults Each user receives an owned copy of **System overview**. Saved copies retain -their specification when the binary’s default changes. The default’s **What +their specification when the binary's default changes. The default's **What changed?** panel (`versions`) and service-host input to **Is it reachable?** (`check_states`) use `resource_inventory`. Deploy a binary supporting `resource_inventory` before editing saved panels. -Use `list_dashboards` and `get_dashboard` to select the owner’s dashboard by ID +Use `list_dashboards` and `get_dashboard` to select the owner's dashboard by ID and capture its version and spec. Compare `versions` and `check_states` with the shipped definitions; skip customized or missing definitions. Preview replacements with the saved time and variables, then submit two `update_panel` @@ -328,7 +328,7 @@ Content and Spec instead of Data. Empty panels show their diagnosis in Chart. When filters together match nothing, the diagnosis names that combination. Chat keeps views whose results are all -empty collapsed on an “empty” summary. Expand an empty view to inspect its +empty collapsed on an "empty" summary. Expand an empty view to inspect its diagnosis. Check the time range, selected variables and panel description before interpreting missing results. Truncation and other result notes remain visible below the panel. If a refresh @@ -357,8 +357,8 @@ Ask chat to add variables for filters you want to reuse: Query and custom variables can allow multiple selections. **All** removes filters that use that variable. Clearing a multi-select chooses All if it is enabled; -otherwise **None** is an empty selection that matches no values. All and None -have different meanings. A query variable can depend on an earlier variable, +otherwise **None** is an empty selection that matches no values. A query variable +can depend on an earlier variable, so selecting a service can narrow a later route selector. Options resolve in saved variable order and change with the window and dependencies. @@ -380,9 +380,8 @@ Use an absolute range when the recipient needs the same time bounds. A relative range moves with the time the link is opened. Links open the current saved dashboard and query its data; they do not freeze a historical version or result. -The recipient needs an account and access to that dashboard. Dashboards are -owner-scoped: another account cannot open your dashboard just because -you copied its link. A view link grants no access and is not a public snapshot. +Dashboard access remains owner-scoped; a view link grants another account no +access and does not create a public snapshot. ## Layout and saved versions @@ -404,7 +403,7 @@ but the selected spec can still be inspected. Choose **Restore version** on an older entry to save that spec as a **new version**. For example, restoring v2 while v5 is current creates v6; it does not rewrite -v2 or remove v3–v5. The dashboard reloads the restored panels. History and restore +v2 or remove v3 through v5. The dashboard reloads the restored panels. History and restore have the same owner scope as the dashboard. ## Keyboard controls diff --git a/site/src/content/docs/docs/guides/mcp.mdx b/site/src/content/docs/docs/guides/mcp.mdx index 27a3866d..ad226c32 100644 --- a/site/src/content/docs/docs/guides/mcp.mdx +++ b/site/src/content/docs/docs/guides/mcp.mdx @@ -9,7 +9,7 @@ status: preview --- Fanout serves MCP at `/mcp` for external agents. Its [tools](/docs/reference/mcp) -query telemetry and manage the authenticated user’s dashboards. The browser +query telemetry and manage the authenticated user's dashboards. The browser client and built-in agent use the same typed query results. [Build and refine dashboards](/docs/guides/dashboards) covers schema discovery, @@ -26,12 +26,12 @@ origin is the URL your MCP client connects to. export FANOUT_PUBLIC_URL=https://fanout.example.com ``` -Fanout derives the MCP resource URI from this — the canonical, externally +Fanout derives the MCP resource URI from this canonical, externally reachable origin that clients are issued tokens against. It must be HTTPS, and it must stay **stable across restarts**: tokens carry the derived resource as their audience, so changing it invalidates every one already issued. -The resource is derived from `FANOUT_PUBLIC_URL`; see +`FANOUT_PUBLIC_URL` is the only public-origin setting; see [server settings](/docs/reference/configuration/server#fanout_public_url). Set `FANOUT_MCP_ENABLED=false` to turn the endpoint off entirely; it defaults @@ -45,7 +45,7 @@ needs only the URL: 1. Discovers the endpoints from `/.well-known/oauth-protected-resource` and `/.well-known/oauth-authorization-server`. -2. Registers itself at `/oauth/register` — dynamic client registration, so +2. Registers itself at `/oauth/register` through dynamic client registration, so nothing is pre-provisioned by hand. 3. Obtains a token from `/oauth/token`, bound to the resource URI above. @@ -78,7 +78,7 @@ on `/mcp`. Modern `2026-07-28` requests carry per-request metadata and `MCP-Protocol-Version: 2026-07-28`, with no session or `initialize` handshake. Legacy clients use `initialize` to negotiate `2025-11-25` or `2025-06-18` and receive an `Mcp-Session-Id` bound to the authenticated user. Legacy sessions -expire after 10 idle minutes, with at most eight live sessions per user–OAuth +expire after 10 idle minutes, with at most eight live sessions per user/OAuth client pair. An expired session or a server restart requires re-initialization. Versions `2025-03-26` and `2024-11-05` are not served. The browser's `/api/mcp` route uses only the modern protocol. @@ -88,7 +88,7 @@ serialization of `FANOUT_PUBLIC_URL`. Malformed or untrusted origins receive `403` before authentication. Without a public URL, only same-authority loopback origins are accepted. Requests without `Origin` pass this check. -`tools/call` requests are limited to 20 per second per user–OAuth client pair, +`tools/call` requests are limited to 20 per second per user/OAuth client pair, with a burst of 100. Excess calls receive `429` with `Retry-After`. The in-process agent is exempt. Modern client disconnects cancel in-flight tool calls, including DuckDB queries. diff --git a/site/src/content/docs/docs/guides/retention.mdx b/site/src/content/docs/docs/guides/retention.mdx index 135ab991..79384074 100644 --- a/site/src/content/docs/docs/guides/retention.mdx +++ b/site/src/content/docs/docs/guides/retention.mdx @@ -17,7 +17,7 @@ file count and query cost. All configuration changes require a restart. export FANOUT_RETENTION_DAYS=30 ``` -Zero means keep everything. Changing it does not delete anything immediately — +Zero means keep everything. Changing it does not delete anything immediately; expired data goes when maintenance next runs, so disk use falls on the maintenance cycle after restart. Expiry uses ingestion time; see [storage semantics](/docs/concepts/storage#small-files-are-bounded-by-compaction). @@ -45,10 +45,9 @@ export FANOUT_INGEST_BATCH_SIZE=50000 ``` A larger target can improve sustained write throughput and create fewer files -under concurrent ingest. Every request is still acknowledged only after its -Parquet batch is durably published. An individual request is never split merely -to satisfy this target: an oversized request remains one atomic batch. The -setting controls admission grouping, not the durability boundary. +under concurrent ingest. The target groups concurrent small requests; a request +above the target remains one atomic batch. Every request is acknowledged only +after its Parquet batch is durably published. ## What the knobs interact with diff --git a/site/src/content/docs/docs/guides/tls.mdx b/site/src/content/docs/docs/guides/tls.mdx index 47d04ad7..d50baebe 100644 --- a/site/src/content/docs/docs/guides/tls.mdx +++ b/site/src/content/docs/docs/guides/tls.mdx @@ -52,7 +52,7 @@ certificate, use an h2c upstream (plaintext HTTP/2 with prior knowledge). Forwarding HTTP/1.1 supports the UI, API and OTLP/HTTP, but breaks gRPC. Preserve the original Host header for MCP, and allow long-lived SSE responses. -For example, [Caddy’s h2c upstream](https://caddyserver.com/docs/caddyfile/directives/reverse_proxy) +For example, [Caddy's h2c upstream](https://caddyserver.com/docs/caddyfile/directives/reverse_proxy) can serve all surfaces with: ```text @@ -70,11 +70,7 @@ the server listener configuration. ## External MCP clients -`FANOUT_PUBLIC_URL` above does double duty: Fanout derives the MCP resource URI -from it, and that derived resource is what tokens are bound to as their -audience. So it must be HTTPS and stable across restarts — changing it -invalidates every token already issued. `FANOUT_PUBLIC_URL` is the only -public-origin setting. +For `FANOUT_PUBLIC_URL` requirements, see [MCP setup](/docs/guides/mcp#set-the-public-url-first). MCP also validates `Origin` before authentication; see [Connect an agent over MCP](/docs/guides/mcp#protocol). diff --git a/site/src/content/docs/docs/guides/troubleshooting.mdx b/site/src/content/docs/docs/guides/troubleshooting.mdx index c79b646d..6a61d8c1 100644 --- a/site/src/content/docs/docs/guides/troubleshooting.mdx +++ b/site/src/content/docs/docs/guides/troubleshooting.mdx @@ -114,7 +114,7 @@ Work down this list in order: configuration; HTTP/1.1 proxy forwarding cannot carry gRPC. For OTLP/HTTP, set the exporter protocol to `http/protobuf` and give it the base -endpoint — `http://fanout:7520`. Exporters derive `/v1/traces`, `/v1/metrics` +endpoint, `http://fanout:7520`. Exporters derive `/v1/traces`, `/v1/metrics` and `/v1/logs` from it themselves. ## Someone cannot sign in and SMTP is not configured @@ -147,7 +147,7 @@ falls back to `FANOUT_DEFAULT_NAMESPACE`, so telemetry exported without one lands in `default`. An unscoped query spans every namespace, so this is only a problem when -something has scoped the query — a dashboard filter, or an MCP call passing +something has scoped the query, through a dashboard filter or an MCP call passing `namespace`. Clear the scope before concluding the data is missing. The alert engine and overview read analytical rollups, which follow durable diff --git a/site/src/content/docs/docs/reference/alert-expressions.mdx b/site/src/content/docs/docs/reference/alert-expressions.mdx index 5754c1d1..460d7f15 100644 --- a/site/src/content/docs/docs/reference/alert-expressions.mdx +++ b/site/src/content/docs/docs/reference/alert-expressions.mdx @@ -1,7 +1,7 @@ --- title: "Alert expression variables" description: "Every value an alert rule expression can read, and what it means." -summary: "The variables available to a CEL alert rule, generated from the evaluation environment itself." +summary: "The variables available to a CEL alert rule." read_when: - "You are writing an alert rule and need the variable names." - "A rule compiles but never fires, and you want to check what it is reading." @@ -12,9 +12,7 @@ generated: true {/* Generated by cmd/fanout-docgen from internal/alert. Edit the generator, not this page. */} An alert rule's `expression` is [CEL](https://cel.dev) evaluated -against one service's rollup for the current window. These are the values it -can read — the names below match the variables declared and bound by the -evaluator. +against one service's rollup for the current window. It can read the following variables. Nonempty expressions supplied when creating or replacing rules through the API are type-checked against these declared variables. Unknown variables, diff --git a/site/src/content/docs/docs/reference/api.mdx b/site/src/content/docs/docs/reference/api.mdx index ca893e52..06c1f823 100644 --- a/site/src/content/docs/docs/reference/api.mdx +++ b/site/src/content/docs/docs/reference/api.mdx @@ -1,7 +1,7 @@ --- title: "HTTP routes" description: "Every HTTP route the server registers and what guards each one. Mostly the browser client's backend rather than a public API." -summary: "Every route the server registers — application, operational, protocol and the SPA catch-all — with the authorization requirement for each, taken from the middleware that enforces them." +summary: "Application, operational and protocol routes and the SPA catch-all, with the authorization requirement for each." read_when: - "You are auditing what an instance exposes and what guards each route." - "A request came back 401 or 403 and you want to know which capability it wanted." @@ -21,13 +21,13 @@ OTLP shares this listener but uses its own handlers and ingest credential; see The table includes every route the binary can register. These routes depend on configuration: -- `/debug/pprof/*` — only when `FANOUT_PPROF_ENABLED` is true, which is +- `/debug/pprof/*`: only when `FANOUT_PPROF_ENABLED` is true, which is **not** the default. Enabling it also turns on mutex and block sampling. -- `/mcp`, `/api/mcp`, `/api/auth/oauth/*`, `/oauth/*` and `/.well-known/*` — only when +- `/mcp`, `/api/mcp`, `/api/auth/oauth/*`, `/oauth/*` and `/.well-known/*`: only when `FANOUT_MCP_ENABLED` is true, which **is** the default. -- `/api/agent/*` — only when `FANOUT_AI_PROVIDER` selects `anthropic` or +- `/api/agent/*`: only when `FANOUT_AI_PROVIDER` selects `anthropic` or `openai`, with that provider's required key. An empty provider disables chat. -- `/api/auth/oidc/start` and `/api/auth/oidc/callback` — only in +- `/api/auth/oidc/start` and `/api/auth/oidc/callback`: only in `FANOUT_AUTH_MODE=oidc`. Everything else is always registered. A path shown with `:name` or `*` is the @@ -80,8 +80,7 @@ Panel execution results keep their own status and error fields. OAuth, OTLP/gRPC, MCP JSON-RPC and AG-UI run errors keep their protocol spelling. Operational responses at `/healthz`, `/readyz` and `/metrics` keep their bodies. -The **Requires** column comes from the authorization middleware for each method -and path. [Roles](/docs/reference/roles) maps capabilities to roles. +[Roles](/docs/reference/roles) maps capabilities to roles. | Method | Path | Requires | |---|---|---| @@ -156,12 +155,12 @@ and path. [Roles](/docs/reference/roles) maps capabilities to roles. ## What the requirements mean -- **none** — reachable without a credential. -- **any signed-in user** — a session, but no particular capability. -- **protocol handshake** — the OAuth and MCP endpoints, which authenticate +- **none**: reachable without a credential. +- **any signed-in user**: a session, but no particular capability. +- **protocol handshake**: the OAuth and MCP endpoints, which authenticate as part of their own protocol rather than through the middleware. -- **a named capability** — the caller's role must carry it. -- **or a service credential** — additionally reachable without a browser +- **a named capability**: the caller's role must carry it. +- **or a service credential**: additionally reachable without a browser session, which is how a scraper reaches the metrics endpoint. `Any` registers every HTTP method with the same authorization requirement. diff --git a/site/src/content/docs/docs/reference/capabilities.mdx b/site/src/content/docs/docs/reference/capabilities.mdx index 314bc78e..a9db5509 100644 --- a/site/src/content/docs/docs/reference/capabilities.mdx +++ b/site/src/content/docs/docs/reference/capabilities.mdx @@ -43,7 +43,7 @@ capabilities uses `preview` if any of its contracts remain unsettled. ## Operating limits -- **Namespaces are not tenants.** Namespaces scope queries, not access. Anyone who can query an instance can query every namespace in it. Each instance is one trust domain. +- Namespaces filter queries. Anyone who can query an instance can query every namespace in it. Each instance is one trust domain; use separate instances for mutually untrusted teams. A namespace does not represent a user, organisation or tenant. - **No horizontal scale.** Ingest, query and maintenance share one process and one machine. - **Capacity depends on your workload.** Payload cardinality, query mix and disk determine capacity. The published benchmark measures one release, host and workload. [Measure your own hardware](/docs/concepts/performance). - **No migration paths.** Fanout is pre-release. Storage-format changes may require a fresh data directory. Preserve backups before upgrading. diff --git a/site/src/content/docs/docs/reference/configuration.mdx b/site/src/content/docs/docs/reference/configuration.mdx index b880f424..62aaa055 100644 --- a/site/src/content/docs/docs/reference/configuration.mdx +++ b/site/src/content/docs/docs/reference/configuration.mdx @@ -1,7 +1,7 @@ --- title: Configuration -description: How Fanout is configured, where settings come from, and how the reference stays true to the binary. -summary: The rules that govern every setting — precedence, naming, secret files, and validation. +description: How Fanout reads and validates configuration settings. +summary: Setting precedence, naming, secret files, and validation. read_when: - You are configuring an instance and want the rules before the list. - A setting you set appears to have been ignored. @@ -12,9 +12,9 @@ Fanout resolves built-in defaults followed by nonempty process environment values. Every setting is listed under [Settings](/docs/reference/configuration/server), grouped by the part of the product it belongs to. -Those pages and `.env.example` are generated from `internal/config` by -`cmd/fanout-docgen`. `just docs-generate-check` verifies that they match the -schema the binary uses. +`internal/config` defines the configuration schema. Use `cmd/fanout-docgen` +to write the settings pages and `.env.example`, and `just docs-generate-check` +to check them against that schema. ## Where a value comes from @@ -35,8 +35,8 @@ environment values or managed secret mounts. ## Naming -Custom names use `FANOUT_` plus a clear name, adding the area only when it -clarifies. Vendor credentials and endpoints keep `ANTHROPIC_API_KEY`, +Fanout settings use the `FANOUT_` prefix. Vendor credentials and endpoints keep +`ANTHROPIC_API_KEY`, `ANTHROPIC_BASE_URL`, `OPENAI_API_KEY`, and `OPENAI_BASE_URL`. `FANOUT_AI_PROVIDER` defaults to empty, disabling chat. Exactly `anthropic` @@ -107,3 +107,22 @@ host and cgroup limits, the selected source, and whether detection was incomplete. If Fanout cannot conclusively inspect a container limit, it warns; set `FANOUT_DUCKDB_MEMORY` for a guaranteed bound. See [storage settings](/docs/reference/configuration/storage) for types and defaults. + +## Image tags + +| Image tag | Points at | +|---|---| +| `labstack/fanout:latest` | the newest release | +| `labstack/fanout:2026.10.1-alpha` | that exact release | +| `ghcr.io/labstack/fanout:main` | the tip of `main` | +| `ghcr.io/labstack/fanout:sha-` | one specific commit | + +Release and `latest` tags also exist under `ghcr.io/labstack/fanout`. +Development tags are GHCR-only. + +## Ports and paths + +| Port or path | Purpose | Native default | +|---|---|---| +| `7520` | Browser, API, chat, MCP, OTLP/gRPC and OTLP/HTTP | `127.0.0.1:7520`, loopback | +| `./data` | Telemetry, query state, users, sessions, dashboards, alerts and agent history | `./data` | diff --git a/site/src/content/docs/docs/reference/configuration/ai.mdx b/site/src/content/docs/docs/reference/configuration/ai.mdx index 78c1c588..85a95e72 100644 --- a/site/src/content/docs/docs/reference/configuration/ai.mdx +++ b/site/src/content/docs/docs/reference/configuration/ai.mdx @@ -14,7 +14,6 @@ generated: true Every setting below is read once at startup and requires a restart to change. Built-in defaults are overridden by nonempty environment values. Unknown `FANOUT_` names fail startup; platform-injected service names are tolerated. -Custom names use `FANOUT_` plus a clear name, adding the area only when it clarifies. Vendor credentials and endpoints keep the vendor's documented names. Fanout also recognizes `ANTHROPIC_API_KEY`, `ANTHROPIC_BASE_URL`, diff --git a/site/src/content/docs/docs/reference/configuration/alerts.mdx b/site/src/content/docs/docs/reference/configuration/alerts.mdx index 8836fe5c..8e68a354 100644 --- a/site/src/content/docs/docs/reference/configuration/alerts.mdx +++ b/site/src/content/docs/docs/reference/configuration/alerts.mdx @@ -13,7 +13,6 @@ generated: true Every setting below is read once at startup and requires a restart to change. Built-in defaults are overridden by nonempty environment values. Unknown `FANOUT_` names fail startup; platform-injected service names are tolerated. -Custom names use `FANOUT_` plus a clear name, adding the area only when it clarifies. Vendor credentials and endpoints keep the vendor's documented names. | Environment variable | Type | Default | diff --git a/site/src/content/docs/docs/reference/configuration/auth.mdx b/site/src/content/docs/docs/reference/configuration/auth.mdx index 40086be4..a069e50e 100644 --- a/site/src/content/docs/docs/reference/configuration/auth.mdx +++ b/site/src/content/docs/docs/reference/configuration/auth.mdx @@ -14,7 +14,6 @@ generated: true Every setting below is read once at startup and requires a restart to change. Built-in defaults are overridden by nonempty environment values. Unknown `FANOUT_` names fail startup; platform-injected service names are tolerated. -Custom names use `FANOUT_` plus a clear name, adding the area only when it clarifies. Vendor credentials and endpoints keep the vendor's documented names. | Environment variable | Type | Default | diff --git a/site/src/content/docs/docs/reference/configuration/ingest.mdx b/site/src/content/docs/docs/reference/configuration/ingest.mdx index 6822196f..ea76b34a 100644 --- a/site/src/content/docs/docs/reference/configuration/ingest.mdx +++ b/site/src/content/docs/docs/reference/configuration/ingest.mdx @@ -14,7 +14,6 @@ generated: true Every setting below is read once at startup and requires a restart to change. Built-in defaults are overridden by nonempty environment values. Unknown `FANOUT_` names fail startup; platform-injected service names are tolerated. -Custom names use `FANOUT_` plus a clear name, adding the area only when it clarifies. Vendor credentials and endpoints keep the vendor's documented names. | Environment variable | Type | Default | diff --git a/site/src/content/docs/docs/reference/configuration/mcp.mdx b/site/src/content/docs/docs/reference/configuration/mcp.mdx index 799c2e83..40f38240 100644 --- a/site/src/content/docs/docs/reference/configuration/mcp.mdx +++ b/site/src/content/docs/docs/reference/configuration/mcp.mdx @@ -13,7 +13,6 @@ generated: true Every setting below is read once at startup and requires a restart to change. Built-in defaults are overridden by nonempty environment values. Unknown `FANOUT_` names fail startup; platform-injected service names are tolerated. -Custom names use `FANOUT_` plus a clear name, adding the area only when it clarifies. Vendor credentials and endpoints keep the vendor's documented names. | Environment variable | Type | Default | diff --git a/site/src/content/docs/docs/reference/configuration/metrics.mdx b/site/src/content/docs/docs/reference/configuration/metrics.mdx index 10cf1d1c..7a3d3a5a 100644 --- a/site/src/content/docs/docs/reference/configuration/metrics.mdx +++ b/site/src/content/docs/docs/reference/configuration/metrics.mdx @@ -13,7 +13,6 @@ generated: true Every setting below is read once at startup and requires a restart to change. Built-in defaults are overridden by nonempty environment values. Unknown `FANOUT_` names fail startup; platform-injected service names are tolerated. -Custom names use `FANOUT_` plus a clear name, adding the area only when it clarifies. Vendor credentials and endpoints keep the vendor's documented names. | Environment variable | Type | Default | diff --git a/site/src/content/docs/docs/reference/configuration/server.mdx b/site/src/content/docs/docs/reference/configuration/server.mdx index 5389cbec..5a4c84fd 100644 --- a/site/src/content/docs/docs/reference/configuration/server.mdx +++ b/site/src/content/docs/docs/reference/configuration/server.mdx @@ -14,7 +14,6 @@ generated: true Every setting below is read once at startup and requires a restart to change. Built-in defaults are overridden by nonempty environment values. Unknown `FANOUT_` names fail startup; platform-injected service names are tolerated. -Custom names use `FANOUT_` plus a clear name, adding the area only when it clarifies. Vendor credentials and endpoints keep the vendor's documented names. | Environment variable | Type | Default | diff --git a/site/src/content/docs/docs/reference/configuration/smtp.mdx b/site/src/content/docs/docs/reference/configuration/smtp.mdx index ea1d09db..3c973305 100644 --- a/site/src/content/docs/docs/reference/configuration/smtp.mdx +++ b/site/src/content/docs/docs/reference/configuration/smtp.mdx @@ -13,7 +13,6 @@ generated: true Every setting below is read once at startup and requires a restart to change. Built-in defaults are overridden by nonempty environment values. Unknown `FANOUT_` names fail startup; platform-injected service names are tolerated. -Custom names use `FANOUT_` plus a clear name, adding the area only when it clarifies. Vendor credentials and endpoints keep the vendor's documented names. | Environment variable | Type | Default | diff --git a/site/src/content/docs/docs/reference/configuration/storage.mdx b/site/src/content/docs/docs/reference/configuration/storage.mdx index 23a6a658..78b63d42 100644 --- a/site/src/content/docs/docs/reference/configuration/storage.mdx +++ b/site/src/content/docs/docs/reference/configuration/storage.mdx @@ -14,7 +14,6 @@ generated: true Every setting below is read once at startup and requires a restart to change. Built-in defaults are overridden by nonempty environment values. Unknown `FANOUT_` names fail startup; platform-injected service names are tolerated. -Custom names use `FANOUT_` plus a clear name, adding the area only when it clarifies. Vendor credentials and endpoints keep the vendor's documented names. | Environment variable | Type | Default | @@ -60,4 +59,4 @@ Schedules analytical rollups; use a Go duration of at least 1s. ### `FANOUT_ROLLUP_SKIP_TO_LATEST` -RollupSkipToLatest, set once at boot, advances the service and edge rollup watermarks to the current max ingested timestamp. Existing history is not aggregated into those analytical rollups. Completed-batch trace caches still rebuild all retained history in bounded passes; uncached files remain immediately queryable. Useful for large pre-seeded datasets (benchmarks, restores); off in normal operation. +Set once at boot, this advances the service and edge rollup watermarks to the current max ingested timestamp. Existing history is not aggregated into those analytical rollups. Completed-batch trace caches still rebuild all retained history in bounded passes; uncached files remain immediately queryable. Useful for large pre-seeded datasets (benchmarks, restores); off in normal operation. diff --git a/site/src/content/docs/docs/reference/data-layout.mdx b/site/src/content/docs/docs/reference/data-layout.mdx index a03ed93e..f90fed3a 100644 --- a/site/src/content/docs/docs/reference/data-layout.mdx +++ b/site/src/content/docs/docs/reference/data-layout.mdx @@ -39,11 +39,11 @@ accounts that own them. [Back up and restore](/docs/guides/backup-and-restore) covers cold backups of the complete directory. -## Two engines, on purpose +## Why two engines Telemetry is append-heavy, queried analytically over wide ranges, and never updated. Application state is small, transactional and updated constantly. -Parquet with DuckDB serves the former; SQLite serves the latter — [how storage +Parquet with DuckDB serves the former; SQLite serves the latter. [How storage works](/docs/concepts/storage) covers the reasoning and the write path. ## `query/tmp/` @@ -55,7 +55,7 @@ disk causes the query to fail. ## Sizing -Retention is the main lever — `FANOUT_RETENTION_DAYS`, with expiry applied on +`FANOUT_RETENTION_DAYS` controls retention, with expiry applied on the maintenance cycle rather than the moment you change it. File count matters as much as byte count for query latency, which is what the compaction pass exists to bound. [Tune retention](/docs/guides/retention) covers both. diff --git a/site/src/content/docs/docs/reference/endpoints.mdx b/site/src/content/docs/docs/reference/endpoints.mdx index d7bb67e3..c4d8d172 100644 --- a/site/src/content/docs/docs/reference/endpoints.mdx +++ b/site/src/content/docs/docs/reference/endpoints.mdx @@ -1,7 +1,7 @@ --- title: Endpoints description: The operational HTTP endpoints a Fanout instance exposes, and what each one requires. -summary: Health, readiness, metrics, profiling and the protocol endpoints — with the credential each expects. +summary: Health, readiness, metrics, profiling and the protocol endpoints, with the credential each expects. read_when: - You are configuring a load balancer, a probe or a Prometheus scrape. - You need to know which endpoints are reachable without authentication. @@ -11,9 +11,7 @@ status: preview Everything below is served by the same HTTP listener as the browser client, on `FANOUT_ADDR` (`127.0.0.1:7520` by default; the image sets `:7520`). -This page covers the operational endpoints and what they are for. For the -complete surface — every route with the exact credential it requires, taken from -the middleware that enforces it — see [HTTP routes](/docs/reference/api). +[HTTP routes](/docs/reference/api) lists every route and the credential it requires. ## Health @@ -32,7 +30,7 @@ Use `/healthz` for restart decisions and `/readyz` for traffic decisions. | `/debug/pprof/*` | `operations:read` | `/metrics` is Prometheus exposition. It accepts a service credential as well -as an admin session, which is how a scraper reaches it without a browser login — +as an admin session, which is how a scraper reaches it without a browser login; set `FANOUT_METRICS_TOKEN`. `FANOUT_METRICS_PUBLIC=true` permits unauthenticated scrapes and emits a startup warning. diff --git a/site/src/content/docs/docs/reference/mcp.mdx b/site/src/content/docs/docs/reference/mcp.mdx index d3059128..fd9d51ae 100644 --- a/site/src/content/docs/docs/reference/mcp.mdx +++ b/site/src/content/docs/docs/reference/mcp.mdx @@ -1,7 +1,7 @@ --- title: "MCP tools" description: "Every tool a Fanout instance exposes over MCP, with its inputs and whether it changes anything." -summary: "20 tools served at /mcp, each with its inputs and mutation semantics, taken from the server's Apps-enabled tools/list answer." +summary: "20 tools served at /mcp, each with its inputs and mutation semantics." read_when: - "You are connecting an agent and want to know what it can call." - "You need to know which tools mutate state and which cannot." @@ -13,8 +13,7 @@ generated: true tool list rather than reading the registrations. Edit the generator, not this page. */} -A Fanout instance serves MCP at `/mcp`. 20 tools, listed below exactly as the -server reports them to an Apps-enabled client. Ordinary clients receive the 17 model-visible tools; the three app-only helpers require Apps negotiation and are never offered to the model. +A Fanout instance serves MCP at `/mcp`. 20 tools are available to an Apps-enabled client. Ordinary clients receive the 17 model-visible tools; the three app-only helpers require Apps negotiation and are never offered to the model. Every tool is **closed-world**: it reads or writes what this instance holds and reaches nothing else. @@ -65,7 +64,7 @@ AG-UI `mcp-app` activity content uses exactly `resource_uri`, `tool_name`, ## create_dashboard -**Create dashboard** — Create a dashboard for the authenticated user from a complete spec. Read get_telemetry_schema first and preview_panels until every non-text panel returns data. Empty or failing panels return error.code invalid_spec with their path, title and diagnosis, plus structured problems. Successful saves return a receipt for that committed version: base_version, version, changes, layout_changed, dashboard_fields and save_check. Changes describe authored panel fields and position_changed; physical packing is only layout_changed. Order chips mark the minimal moved set. save_check reports checked, reason when unchecked, total elapsed_ms and one status/rows result per saved panel with elapsed_ms when executed. checked means every panel was executed, not that error or empty panels are healthy. Text rows identify nonempty content, not telemetry. Use the structured receipt rather than prose warnings as check evidence. Create receipts list added panels without layout or dashboard edit chips. No-op saves still create a new version with no change chips. Per-panel elapsed_ms is omitted when the panel did not run. A dashboard is \{name, description, time:\{range}, variables, panels}. Each panel: \{id (lowercase), title, viz, width 1-12, query or sql}. viz: stat or gauge (one measure; gauge needs min and max), timeseries (bucket auto, at most one by), bar (one or two by), table (up to three by), text (content in Markdown), heatmap (bucket auto, no by), histogram (at most one by), scatter (exactly two measures in x then y order, one item dimension and an optional colour dimension), state_timeline (one measure, one item dimension, bucket auto and thresholds required). Rollup panel types are service_map (dependency graph) and health (overview metrics, error trend and health distribution). They require query.from=spans, no measures, by, bucket or sort, and only namespace/service equality filters with a literal or single-value variable; explicit casts and values with surrounding whitespace are rejected. Reads default to 400 services/edges and are capped at 400 even when query.limit is larger. Service scope applies before the limit; map nodes carry p95_ms and edges carry average_ms; error_rate is percent. Row panel types are logs, log_patterns and traces. Logs and traces take no measures, by or bucket. Patterns use count() by body_template. Patterns group only by body_template. Severity is the most frequent severity, ties choose the higher severity. Service is the most frequent service, ties choose ascending service name. Both describe the scoped, redacted events, not extra grouping dimensions. Each pattern returns dominant severity, top service, count and a dense trend, computed in the same bounded query after redaction and scoped filters. Redaction applies before filtering, grouping, display and drill-down. Row panels cannot use SQL. "erroring" means slowest erroring traces: filter to erroring traces, then rank by root duration. Trace candidate ranking is approximate when filters exclude the root span, as with exemplars. Categorical charts use fixed-order colour slots. Categorical options.top defaults to 6, maximum 6. Structured panels: the server computes Other; series are chosen worst-first by confidence (Wilson lower bound for error rates; at least 20 samples for latency); the rest fold into Other (N). Count, rate, sum and share rank largest first. Error rates require at least 20 requests for eligible Wilson ranking (z=1.96). Percentiles, quantile, avg, max and min require at least 20 non-null numeric samples for eligible value ranking and use better (explicit or inferred). Ineligible series follow eligible ones by sample count descending. Ties use sample count descending, then name ascending. SQL panels: series past six are left out, with a note. state_timeline defaults to 8 rows, maximum 20; other noncategorical panels use the same limits. Scatter supports options.x_scale and options.y_scale (log or linear), with distinct x_unit for x and unit for y, each inferred independently when omitted. Rows returned use the count unit. State timelines preserve null and absent buckets as unknown and keep the top items without merging them into Other. Scatter requires a structured query. SQL state_timeline requires timestamp time, a string item, one numeric measure and optional string state; its interval is one minute and thresholds are required. Items rank by volume (row count) for top N. Previous-period comparison is not drawn for scatter or state_timeline. Distribution panels use a structured query with one count() measure and histogram: \{field: duration_ms, buckets: log2} for spans or \{field: value, buckets: explicit} for metrics. Negative span durations clamp to [0,1); +Inf goes to overflow; NaN is excluded because it is not a measurement. Metric histograms use stored temporality and shared per-series increases; resets contribute current counts. Delta histograms sum observation weights. query: \{from: spans|logs|metrics, where: [filter expressions], measures: [fn(field) as alias], by: [fields], bucket, sort, limit}. Measures: count(), rate() per second, error_rate() percent, share() percent, avg/min/max/sum(field), last(value) for metrics, p50/p75/p90/p95/p99(field), quantile(field, 0.999), count_distinct(field). Fields are columns from get_telemetry_schema or attributes['key'] / resource['key'] with the key written literally. Filters are SQL boolean expressions: service = $service, kind = 'SPAN_KIND_SERVER', status = 'STATUS_CODE_ERROR', http_route IN $routes, attributes['http.response.status_code']::INTEGER >= 500. Variables: \{name, kind: query|custom|constant|text, from, field, default, include_all}; $__all selects All and removes filters that use the variable. sql: one SELECT over spans, logs, metrics, service_rollup, edge_rollup or resource_inventory that filters time with $__window(column). Compare time columns with TIMESTAMP 'YYYY-MM-DD HH:MM:SS' (UTC), or cast TIMESTAMPTZ literals with ::TIMESTAMPTZ_NS. Use values exactly as get_telemetry_schema lists them. Example panel: \{"id":"latency","title":"Checkout latency","viz":"timeseries","width":8,"query":\{"from":"spans","where":["service = 'checkout'","kind = 'SPAN_KIND_SERVER'"],"measures":["p50(duration_ms)","p95(duration_ms)","p99(duration_ms)"],"bucket":"auto"},"unit":"ms","thresholds":[\{"value":1500,"status":"bad"}]}. Metrics: gauge/updown | counter | histogram. count(): points; count_distinct(): distinct. rate(): undefined | series increase/elapsed summed | count rate summed. sum(value): series means summed | increases | sum increases. avg(value): mean series means | undefined | sum/count increases. Gauge/updown sum/avg carry earlier points within 5m. Deltas spread across covered buckets; rate alone carries within 5m. rate() is per-second increase over covered elapsed time; auto >=1m. min/max: points | points | undefined. last(value): latest strictly before bucket/window end, reported within 5m, summed | same | undefined. pNN/quantile: points | undefined | interpolated bucket increases. summary: no value aggregates. Undefined: NULL. Fix name in its own where entry: one literal or single-value variable without All or multi-value; or name in by. Read type/temporality from get_telemetry_schema. Panel SQL also supports resource_inventory: signal, namespace, service, full typed resource, first_seen and last_seen. One row per distinct full resource per signal/namespace/service observed inside the logical panel window; first_seen/last_seen are actual in-window event times. Select signal='spans', 'logs' or 'metrics' and use resource['key'] with literal dotted keys. count(*) counts resource identities per signal, not events or instances. Declare $__window(first_seen). This relation is SQL-only: query.from and variables remain spans|logs|metrics; unscoped SQL cannot read it. state_timeline needs at least one threshold marked warn or bad, rather than only ok. Annotations attach only to timeseries, heatmap and state_timeline panels, scoped by service and namespace filters. drill: logs needs query.from=logs. Drill is not available on metrics. Every table format's field must exist in the returned frame; formatting cannot reference an absent column. A structured-table sparkline field must be a measure alias from that table's query. service_link needs variable to name the query or custom variable selected by the link. highlight applies to logs and log_patterns, at most 200 characters. split: deploy needs a service equality filter, as a literal or a single-value variable; other filters are allowed. When the service is All, a split: deploy panel shows the plain bar with a note explaining the missing service scope. Use service_map for dependency context, health for service health; neither proves causality. Example 1: Reference shapes; use only the types the question needs. \{"name":"Investigation shapes","annotations":\{"deploys":true,"anomalies":true},"variables":[\{"name":"service","kind":"query","from":"spans","field":"service"}],"panels":[\{"id":"latency_heat","title":"Latency over time","viz":"heatmap","query":\{"from":"spans","where":["service = $service"],"measures":["count()"],"bucket":"auto","histogram":\{"field":"duration_ms","buckets":"log2"}},"drill":"traces"},\{"id":"latency_hist","title":"Latency by operation","viz":"histogram","query":\{"from":"spans","where":["service = $service"],"measures":["count()"],"by":["operation"],"histogram":\{"field":"duration_ms","buckets":"log2"}},"drill":"traces"},\{"id":"items","title":"Calls versus tail latency","viz":"scatter","query":\{"from":"spans","where":["service = $service"],"measures":["count() as rows","p95(duration_ms) as tail_latency"],"by":["operation"]},"drill":"traces"},\{"id":"states","title":"Error states","viz":"state_timeline","query":\{"from":"spans","where":["service = $service"],"measures":["error_rate()"],"by":["operation"],"bucket":"auto"},"thresholds":[\{"value":5,"status":"bad"}],"drill":"traces"},\{"id":"log_rows","title":"Recent logs","viz":"logs","query":\{"from":"logs","where":["service = $service"],"sort":"time","limit":100},"drill":"logs"},\{"id":"patterns","title":"Repeated log patterns","viz":"log_patterns","query":\{"from":"logs","where":["service = $service"],"measures":["count()"],"by":["body_template"],"bucket":"auto","limit":50},"drill":"logs"},\{"id":"trace_rows","title":"Slow traces","viz":"traces","query":\{"from":"spans","where":["service = $service"],"sort":"duration_ms","limit":20},"drill":"traces"},\{"id":"map","title":"Service calls","viz":"service_map","query":\{"from":"spans"}},\{"id":"health","title":"Service health","viz":"health","query":\{"from":"spans"}}]} Example 2: Reference shapes; use only the types the question needs. \{"name":"Interaction shapes","variables":[\{"name":"service","kind":"query","from":"spans","field":"service"}],"panels":[\{"id":"hist","title":"Metric distribution","viz":"histogram","query":\{"from":"metrics","where":["service = $service"],"measures":["count()"],"histogram":\{"field":"value","buckets":"explicit"}}},\{"id":"table","title":"Service calls and trends","viz":"table","query":\{"from":"spans","measures":["count() as calls"],"by":["service"]},"options":\{"columns":[\{"field":"service","format":"service_link","variable":"service"},\{"field":"calls","format":"sparkline"}]}},\{"id":"prior","title":"Service calls one day earlier","viz":"bar","query":\{"from":"spans","measures":["count()"],"by":["service"]},"click":\{"set_variable":"service"},"time":\{"shift":"1d"}}]} Metric examples; use when the discovered type fits. \{"name":"Metric measures","panels":[\{"id":"network","title":"Network bytes per second","viz":"timeseries","query":\{"from":"metrics","where":["name = 'system.network.io'"],"measures":["rate()"],"by":["attributes['direction']"],"bucket":"auto"}},\{"id":"duration","title":"Request duration p95","viz":"timeseries","query":\{"from":"metrics","where":["name = 'http.server.request.duration'"],"measures":["p95(value)"],"bucket":"auto"},"unit":"s"}]} Observed-resource SQL example. \{"name":"Observed resources","panels":[\{"id":"resources","title":"Span resources observed in this window","viz":"table","sql":"SELECT namespace,service,resource['service.version']::VARCHAR AS version,first_seen,last_seen FROM resource_inventory WHERE signal='spans' AND $__window(first_seen) ORDER BY namespace,service,version,first_seen,last_seen"}]} +**Create dashboard**: Create a dashboard for the authenticated user from a complete spec. Read get_telemetry_schema first and preview_panels until every non-text panel returns data. Empty or failing panels return error.code invalid_spec with their path, title and diagnosis, plus structured problems. Successful saves return a receipt for that committed version: base_version, version, changes, layout_changed, dashboard_fields and save_check. Changes describe authored panel fields and position_changed; physical packing is only layout_changed. Order chips mark the minimal moved set. save_check reports checked, reason when unchecked, total elapsed_ms and one status/rows result per saved panel with elapsed_ms when executed. checked means every panel was executed, not that error or empty panels are healthy. Text rows identify nonempty content, not telemetry. Use the structured receipt rather than prose warnings as check evidence. Create receipts list added panels without layout or dashboard edit chips. No-op saves still create a new version with no change chips. Per-panel elapsed_ms is omitted when the panel did not run. A dashboard is \{name, description, time:\{range}, variables, panels}. Each panel: \{id (lowercase), title, viz, width 1-12, query or sql}. viz: stat or gauge (one measure; gauge needs min and max), timeseries (bucket auto, at most one by), bar (one or two by), table (up to three by), text (content in Markdown), heatmap (bucket auto, no by), histogram (at most one by), scatter (exactly two measures in x then y order, one item dimension and an optional colour dimension), state_timeline (one measure, one item dimension, bucket auto and thresholds required). Rollup panel types are service_map (dependency graph) and health (overview metrics, error trend and health distribution). They require query.from=spans, no measures, by, bucket or sort, and only namespace/service equality filters with a literal or single-value variable; explicit casts and values with surrounding whitespace are rejected. Reads default to 400 services/edges and are capped at 400 even when query.limit is larger. Service scope applies before the limit; map nodes carry p95_ms and edges carry average_ms; error_rate is percent. Row panel types are logs, log_patterns and traces. Logs and traces take no measures, by or bucket. Patterns use count() by body_template. Patterns group only by body_template. Severity is the most frequent severity, ties choose the higher severity. Service is the most frequent service, ties choose ascending service name. Both describe the scoped, redacted events, not extra grouping dimensions. Each pattern returns dominant severity, top service, count and a dense trend, computed in the same bounded query after redaction and scoped filters. Redaction applies before filtering, grouping, display and drill-down. Row panels cannot use SQL. "erroring" means slowest erroring traces: filter to erroring traces, then rank by root duration. Trace candidate ranking is approximate when filters exclude the root span, as with exemplars. Categorical charts use fixed-order colour slots. Categorical options.top defaults to 6, maximum 6. Structured panels: the server computes Other; series are chosen worst-first by confidence (Wilson lower bound for error rates; at least 20 samples for latency); the rest fold into Other (N). Count, rate, sum and share rank largest first. Error rates require at least 20 requests for eligible Wilson ranking (z=1.96). Percentiles, quantile, avg, max and min require at least 20 non-null numeric samples for eligible value ranking and use better (explicit or inferred). Ineligible series follow eligible ones by sample count descending. Ties use sample count descending, then name ascending. SQL panels: series past six are left out, with a note. state_timeline defaults to 8 rows, maximum 20; other noncategorical panels use the same limits. Scatter supports options.x_scale and options.y_scale (log or linear), with distinct x_unit for x and unit for y, each inferred independently when omitted. Rows returned use the count unit. State timelines preserve null and absent buckets as unknown and keep the top items without merging them into Other. Scatter requires a structured query. SQL state_timeline requires timestamp time, a string item, one numeric measure and optional string state; its interval is one minute and thresholds are required. Items rank by volume (row count) for top N. Previous-period comparison is not drawn for scatter or state_timeline. Distribution panels use a structured query with one count() measure and histogram: \{field: duration_ms, buckets: log2} for spans or \{field: value, buckets: explicit} for metrics. Negative span durations clamp to [0,1); +Inf goes to overflow; NaN is excluded because it is not a measurement. Metric histograms use stored temporality and shared per-series increases; resets contribute current counts. Delta histograms sum observation weights. query: \{from: spans|logs|metrics, where: [filter expressions], measures: [fn(field) as alias], by: [fields], bucket, sort, limit}. Measures: count(), rate() per second, error_rate() percent, share() percent, avg/min/max/sum(field), last(value) for metrics, p50/p75/p90/p95/p99(field), quantile(field, 0.999), count_distinct(field). Fields are columns from get_telemetry_schema or attributes['key'] / resource['key'] with the key written literally. Filters are SQL boolean expressions: service = $service, kind = 'SPAN_KIND_SERVER', status = 'STATUS_CODE_ERROR', http_route IN $routes, attributes['http.response.status_code']::INTEGER >= 500. Variables: \{name, kind: query|custom|constant|text, from, field, default, include_all}; $__all selects All and removes filters that use the variable. sql: one SELECT over spans, logs, metrics, service_rollup, edge_rollup or resource_inventory that filters time with $__window(column). Compare time columns with TIMESTAMP 'YYYY-MM-DD HH:MM:SS' (UTC), or cast TIMESTAMPTZ literals with ::TIMESTAMPTZ_NS. Use values exactly as get_telemetry_schema lists them. Example panel: \{"id":"latency","title":"Checkout latency","viz":"timeseries","width":8,"query":\{"from":"spans","where":["service = 'checkout'","kind = 'SPAN_KIND_SERVER'"],"measures":["p50(duration_ms)","p95(duration_ms)","p99(duration_ms)"],"bucket":"auto"},"unit":"ms","thresholds":[\{"value":1500,"status":"bad"}]}. Metrics: gauge/updown | counter | histogram. count(): points; count_distinct(): distinct. rate(): undefined | series increase/elapsed summed | count rate summed. sum(value): series means summed | increases | sum increases. avg(value): mean series means | undefined | sum/count increases. Gauge/updown sum/avg carry earlier points within 5m. Deltas spread across covered buckets; rate alone carries within 5m. rate() is per-second increase over covered elapsed time; auto >=1m. min/max: points | points | undefined. last(value): latest strictly before bucket/window end, reported within 5m, summed | same | undefined. pNN/quantile: points | undefined | interpolated bucket increases. summary: no value aggregates. Undefined: NULL. Fix name in its own where entry: one literal or single-value variable without All or multi-value; or name in by. Read type/temporality from get_telemetry_schema. Panel SQL also supports resource_inventory: signal, namespace, service, full typed resource, first_seen and last_seen. One row per distinct full resource per signal/namespace/service observed inside the logical panel window; first_seen/last_seen are actual in-window event times. Select signal='spans', 'logs' or 'metrics' and use resource['key'] with literal dotted keys. count(*) counts resource identities per signal, not events or instances. Declare $__window(first_seen). This relation is SQL-only: query.from and variables remain spans|logs|metrics; unscoped SQL cannot read it. state_timeline needs at least one threshold marked warn or bad, rather than only ok. Annotations attach only to timeseries, heatmap and state_timeline panels, scoped by service and namespace filters. drill: logs needs query.from=logs. Drill is not available on metrics. Every table format's field must exist in the returned frame; formatting cannot reference an absent column. A structured-table sparkline field must be a measure alias from that table's query. service_link needs variable to name the query or custom variable selected by the link. highlight applies to logs and log_patterns, at most 200 characters. split: deploy needs a service equality filter, as a literal or a single-value variable; other filters are allowed. When the service is All, a split: deploy panel shows the plain bar with a note explaining the missing service scope. Use service_map for dependency context, health for service health; neither proves causality. Example 1: Reference shapes; use only the types the question needs. \{"name":"Investigation shapes","annotations":\{"deploys":true,"anomalies":true},"variables":[\{"name":"service","kind":"query","from":"spans","field":"service"}],"panels":[\{"id":"latency_heat","title":"Latency over time","viz":"heatmap","query":\{"from":"spans","where":["service = $service"],"measures":["count()"],"bucket":"auto","histogram":\{"field":"duration_ms","buckets":"log2"}},"drill":"traces"},\{"id":"latency_hist","title":"Latency by operation","viz":"histogram","query":\{"from":"spans","where":["service = $service"],"measures":["count()"],"by":["operation"],"histogram":\{"field":"duration_ms","buckets":"log2"}},"drill":"traces"},\{"id":"items","title":"Calls versus tail latency","viz":"scatter","query":\{"from":"spans","where":["service = $service"],"measures":["count() as rows","p95(duration_ms) as tail_latency"],"by":["operation"]},"drill":"traces"},\{"id":"states","title":"Error states","viz":"state_timeline","query":\{"from":"spans","where":["service = $service"],"measures":["error_rate()"],"by":["operation"],"bucket":"auto"},"thresholds":[\{"value":5,"status":"bad"}],"drill":"traces"},\{"id":"log_rows","title":"Recent logs","viz":"logs","query":\{"from":"logs","where":["service = $service"],"sort":"time","limit":100},"drill":"logs"},\{"id":"patterns","title":"Repeated log patterns","viz":"log_patterns","query":\{"from":"logs","where":["service = $service"],"measures":["count()"],"by":["body_template"],"bucket":"auto","limit":50},"drill":"logs"},\{"id":"trace_rows","title":"Slow traces","viz":"traces","query":\{"from":"spans","where":["service = $service"],"sort":"duration_ms","limit":20},"drill":"traces"},\{"id":"map","title":"Service calls","viz":"service_map","query":\{"from":"spans"}},\{"id":"health","title":"Service health","viz":"health","query":\{"from":"spans"}}]} Example 2: Reference shapes; use only the types the question needs. \{"name":"Interaction shapes","variables":[\{"name":"service","kind":"query","from":"spans","field":"service"}],"panels":[\{"id":"hist","title":"Metric distribution","viz":"histogram","query":\{"from":"metrics","where":["service = $service"],"measures":["count()"],"histogram":\{"field":"value","buckets":"explicit"}}},\{"id":"table","title":"Service calls and trends","viz":"table","query":\{"from":"spans","measures":["count() as calls"],"by":["service"]},"options":\{"columns":[\{"field":"service","format":"service_link","variable":"service"},\{"field":"calls","format":"sparkline"}]}},\{"id":"prior","title":"Service calls one day earlier","viz":"bar","query":\{"from":"spans","measures":["count()"],"by":["service"]},"click":\{"set_variable":"service"},"time":\{"shift":"1d"}}]} Metric examples; use when the discovered type fits. \{"name":"Metric measures","panels":[\{"id":"network","title":"Network bytes per second","viz":"timeseries","query":\{"from":"metrics","where":["name = 'system.network.io'"],"measures":["rate()"],"by":["attributes['direction']"],"bucket":"auto"}},\{"id":"duration","title":"Request duration p95","viz":"timeseries","query":\{"from":"metrics","where":["name = 'http.server.request.duration'"],"measures":["p95(value)"],"bucket":"auto"},"unit":"s"}]} Observed-resource SQL example. \{"name":"Observed resources","panels":[\{"id":"resources","title":"Span resources observed in this window","viz":"table","sql":"SELECT namespace,service,resource['service.version']::VARCHAR AS version,first_seen,last_seen FROM resource_inventory WHERE signal='spans' AND $__window(first_seen) ORDER BY namespace,service,version,first_seen,last_seen"}]} **Additive.** It creates something new and alters nothing that exists. @@ -75,7 +74,7 @@ AG-UI `mcp-app` activity content uses exactly `resource_uri`, `tool_name`, ## edit_dashboard -**Edit dashboard** — Every non-text panel must return data in the preview or the save returns error.code invalid_spec with the panel path, title and diagnosis, plus structured problems. A stale base_version returns error.code dashboard_version_conflict. Change a dashboard with typed operations, applied in order and saved as one version: add_panel, update_panel (set replaces the named fields), remove_panel, move_panel, set_variable, remove_variable, set_time, rename. Panels not named are left unchanged. Only edit when the user asks to change that dashboard. Successful saves return a receipt for that committed version: base_version, version, changes, layout_changed, dashboard_fields and save_check. Changes describe authored panel fields and position_changed; physical packing is only layout_changed. Order chips mark the minimal moved set. save_check reports checked, reason when unchecked, total elapsed_ms and one status/rows result per saved panel with elapsed_ms when executed. checked means every panel was executed, not that error or empty panels are healthy. Text rows identify nonempty content, not telemetry. Use the structured receipt rather than prose warnings as check evidence. Create receipts list added panels without layout or dashboard edit chips. No-op saves still create a new version with no change chips. Per-panel elapsed_ms is omitted when the panel did not run. See create_dashboard for the spec guide. +**Edit dashboard**: Every non-text panel must return data in the preview or the save returns error.code invalid_spec with the panel path, title and diagnosis, plus structured problems. A stale base_version returns error.code dashboard_version_conflict. Change a dashboard with typed operations, applied in order and saved as one version: add_panel, update_panel (set replaces the named fields), remove_panel, move_panel, set_variable, remove_variable, set_time, rename. Panels not named are left unchanged. Only edit when the user asks to change that dashboard. Successful saves return a receipt for that committed version: base_version, version, changes, layout_changed, dashboard_fields and save_check. Changes describe authored panel fields and position_changed; physical packing is only layout_changed. Order chips mark the minimal moved set. save_check reports checked, reason when unchecked, total elapsed_ms and one status/rows result per saved panel with elapsed_ms when executed. checked means every panel was executed, not that error or empty panels are healthy. Text rows identify nonempty content, not telemetry. Use the structured receipt rather than prose warnings as check evidence. Create receipts list added panels without layout or dashboard edit chips. No-op saves still create a new version with no change chips. Per-panel elapsed_ms is omitted when the panel did not run. See create_dashboard for the spec guide. **Replaces existing state.** @@ -88,7 +87,7 @@ AG-UI `mcp-app` activity content uses exactly `resource_uri`, `tool_name`, ## get_dashboard -**Get dashboard** — Read one dashboard's complete spec and version. Read it before editing so operations name real panel ids. See create_dashboard for the spec guide. +**Get dashboard**: Read one dashboard's complete spec and version. Read it before editing so operations name real panel ids. See create_dashboard for the spec guide. Read-only. @@ -98,7 +97,7 @@ Read-only. ## get_intelligence_snapshot -**Latest detected anomalies** — Return the latest precomputed health score, anomalies, insights, and recurring warning or error log patterns. +**Latest detected anomalies**: Return the latest precomputed health score, anomalies, insights, and recurring warning or error log patterns. Read-only. @@ -106,7 +105,7 @@ Takes no arguments. ## get_observability_overview -**System health overview** — Summarize service health for a bounded telemetry window. Start here for incident triage. All-empty results return no_data guidance instead of a view. State no matching data findings in the answer and prefer signals the service actually has. +**System health overview**: Summarize service health for a bounded telemetry window. Start here for incident triage. All-empty results return no_data guidance instead of a view. State no matching data findings in the answer and prefer signals the service actually has. Read-only. @@ -122,7 +121,7 @@ Read-only. ## get_service_dependencies -**Service dependency traversal** — Find upstream or downstream dependencies of one service with minimum hop counts. Uses the full scoped edge rollup, handles cycles, and reports truncation from depth or node limits. +**Service dependency traversal**: Find upstream or downstream dependencies of one service with minimum hop counts. Uses the full scoped edge rollup, handles cycles, and reports truncation from depth or node limits. Read-only. @@ -137,7 +136,7 @@ Read-only. ## get_service_performance -**Service performance explorer** — Display separate latency, error rate and request rate panels plus a Slow operations table showing p95 duration grouped by operation over server and consumer spans, covering HTTP, gRPC and message consumers, for one service or the system. All-empty results return no_data guidance instead of a view. State no matching data findings in the answer and prefer signals the service actually has. +**Service performance explorer**: Display separate latency, error rate and request rate panels plus a Slow operations table showing p95 duration grouped by operation over server and consumer spans, covering HTTP, gRPC and message consumers, for one service or the system. All-empty results return no_data guidance instead of a view. State no matching data findings in the answer and prefer signals the service actually has. Read-only. @@ -154,7 +153,7 @@ Read-only. ## get_service_topology -**Service dependency topology** — Return services and observed dependency edges with health, traffic, latency, and error data. All-empty results return no_data guidance instead of a view. State no matching data findings in the answer and prefer signals the service actually has. +**Service dependency topology**: Return services and observed dependency edges with health, traffic, latency, and error data. All-empty results return no_data guidance instead of a view. State no matching data findings in the answer and prefer signals the service actually has. Read-only. @@ -170,7 +169,7 @@ Read-only. ## get_telemetry_schema -**Telemetry schema** — List the signals, columns with their common values, attribute keys seen per service, metric names, services, measure functions, units and SQL-only relations in sql_relations. Read this before drafting panels so filters use values that exist. Compare time columns with TIMESTAMP 'YYYY-MM-DD HH:MM:SS' (UTC), or cast TIMESTAMPTZ literals with ::TIMESTAMPTZ_NS. See create_dashboard for the spec guide. +**Telemetry schema**: List the signals, columns with their common values, attribute keys seen per service, metric names, services, measure functions, units and SQL-only relations in sql_relations. Read this before drafting panels so filters use values that exist. Compare time columns with TIMESTAMP 'YYYY-MM-DD HH:MM:SS' (UTC), or cast TIMESTAMPTZ literals with ::TIMESTAMPTZ_NS. See create_dashboard for the spec guide. Read-only. @@ -181,7 +180,7 @@ Read-only. ## inspect_trace -**Trace detail** — Inspect an exact trace, or select the most relevant recent error or slow trace, with spans, waterfall, flame graph, and correlated logs. All-empty results return no_data guidance instead of a view. State no matching data findings in the answer and prefer signals the service actually has. +**Trace detail**: Inspect an exact trace, or select the most relevant recent error or slow trace, with spans, waterfall, flame graph, and correlated logs. All-empty results return no_data guidance instead of a view. State no matching data findings in the answer and prefer signals the service actually has. Read-only. @@ -199,7 +198,7 @@ Read-only. ## list_dashboard_versions -**List dashboard versions** — List the authenticated user's saved versions of one dashboard, newest first, with author and message. The historical version identifies a restore target; restore_dashboard_version saves it as a new version. +**List dashboard versions**: List the authenticated user's saved versions of one dashboard, newest first, with author and message. The historical version identifies a restore target; restore_dashboard_version saves it as a new version. Read-only. @@ -209,7 +208,7 @@ Read-only. ## list_dashboards -**List dashboards** — List the authenticated user's dashboards with panel counts and versions. See create_dashboard for the spec guide. +**List dashboards**: List the authenticated user's dashboards with panel counts and versions. See create_dashboard for the spec guide. Read-only. @@ -217,7 +216,7 @@ Takes no arguments. ## list_panel_exemplars -**Panel exemplars** — Find bounded traces for a panel selection. +**Panel exemplars**: Find bounded traces for a panel selection. Read-only. @@ -237,7 +236,7 @@ Read-only. ## preview_panels -**Preview panels** — Check and run panels without saving them. Each panel reports ok with rows and a sample, empty with the reason, error, or invalid with the exact field and a suggestion. The result includes total elapsed_ms and executed per-panel elapsed_ms; invalid and not_run panels omit elapsed_ms because they were not executed. Fix every invalid panel and replace or drop every empty one before saving. Spec guide: see create_dashboard. +**Preview panels**: Check and run panels without saving them. Each panel reports ok with rows and a sample, empty with the reason, error, or invalid with the exact field and a suggestion. The result includes total elapsed_ms and executed per-panel elapsed_ms; invalid and not_run panels omit elapsed_ms because they were not executed. Fix every invalid panel and replace or drop every empty one before saving. Spec guide: see create_dashboard. Read-only. @@ -250,7 +249,7 @@ Read-only. ## query_panel_fragment -**Refresh telemetry panels** — Run every panel in a dashboard fragment; panel subsets are forbidden. All-empty results return no_data guidance instead of a view. State no matching data findings in the answer and prefer signals the service actually has. +**Refresh telemetry panels**: Run every panel in a dashboard fragment; panel subsets are forbidden. All-empty results return no_data guidance instead of a view. State no matching data findings in the answer and prefer signals the service actually has. Read-only. @@ -266,7 +265,7 @@ Read-only. ## query_telemetry -**Telemetry panel** — Display one v1 panel in chat as an executable dashboard fragment. Use get_telemetry_schema to discover fields; see create_dashboard for the spec guide. The authored spec plus vars must fit 128 KiB (131072 serialized JSON bytes); larger inputs are rejected with their actual size. All-empty results return no_data guidance instead of a view. State no matching data findings in the answer and prefer signals the service actually has. +**Telemetry panel**: Display one v1 panel in chat as an executable dashboard fragment. Use get_telemetry_schema to discover fields; see create_dashboard for the spec guide. The authored spec plus vars must fit 128 KiB (131072 serialized JSON bytes); larger inputs are rejected with their actual size. All-empty results return no_data guidance instead of a view. State no matching data findings in the answer and prefer signals the service actually has. Read-only. @@ -281,7 +280,7 @@ Read-only. ## replace_dashboard -**Replace dashboard** — Every non-text panel must return data in the preview or the save returns error.code invalid_spec with the panel path, title and diagnosis, plus structured problems. A stale base_version returns error.code dashboard_version_conflict. Replace a dashboard's whole spec; omitted panels are removed. Use only for a redesign the user asked for; use edit_dashboard to change a few panels. Successful saves return a receipt for that committed version: base_version, version, changes, layout_changed, dashboard_fields and save_check. Changes describe authored panel fields and position_changed; physical packing is only layout_changed. Order chips mark the minimal moved set. save_check reports checked, reason when unchecked, total elapsed_ms and one status/rows result per saved panel with elapsed_ms when executed. checked means every panel was executed, not that error or empty panels are healthy. Text rows identify nonempty content, not telemetry. Use the structured receipt rather than prose warnings as check evidence. Create receipts list added panels without layout or dashboard edit chips. No-op saves still create a new version with no change chips. Per-panel elapsed_ms is omitted when the panel did not run. See create_dashboard for the spec guide. +**Replace dashboard**: Every non-text panel must return data in the preview or the save returns error.code invalid_spec with the panel path, title and diagnosis, plus structured problems. A stale base_version returns error.code dashboard_version_conflict. Replace a dashboard's whole spec; omitted panels are removed. Use only for a redesign the user asked for; use edit_dashboard to change a few panels. Successful saves return a receipt for that committed version: base_version, version, changes, layout_changed, dashboard_fields and save_check. Changes describe authored panel fields and position_changed; physical packing is only layout_changed. Order chips mark the minimal moved set. save_check reports checked, reason when unchecked, total elapsed_ms and one status/rows result per saved panel with elapsed_ms when executed. checked means every panel was executed, not that error or empty panels are healthy. Text rows identify nonempty content, not telemetry. Use the structured receipt rather than prose warnings as check evidence. Create receipts list added panels without layout or dashboard edit chips. No-op saves still create a new version with no change chips. Per-panel elapsed_ms is omitted when the panel did not run. See create_dashboard for the spec guide. **Replaces existing state.** @@ -294,7 +293,7 @@ Read-only. ## resolve_panel_variables -**Panel variable options** — Resolve variable options within a dashboard fragment window. +**Panel variable options**: Resolve variable options within a dashboard fragment window. Read-only. @@ -308,7 +307,7 @@ Read-only. ## restore_dashboard_version -**Restore dashboard version** — Restore a saved historical version of the authenticated user's dashboard only on an explicit user request. The input version selects the historical spec; the result's version is the newly saved version. Every successful call appends another version, even when restoring the latest spec. A missing or pruned target returns error.code dashboard_version_not_found; list_dashboard_versions shows retained versions. Spec validation failures return error.code invalid_spec with the presented problems. Successful saves return a receipt for that committed version: base_version, version, changes, layout_changed, dashboard_fields and save_check. Changes describe authored panel fields and position_changed; physical packing is only layout_changed. Order chips mark the minimal moved set. save_check reports checked, reason when unchecked, total elapsed_ms and one status/rows result per saved panel with elapsed_ms when executed. checked means every panel was executed, not that error or empty panels are healthy. Text rows identify nonempty content, not telemetry. Use the structured receipt rather than prose warnings as check evidence. Create receipts list added panels without layout or dashboard edit chips. No-op saves still create a new version with no change chips. Per-panel elapsed_ms is omitted when the panel did not run. +**Restore dashboard version**: Restore a saved historical version of the authenticated user's dashboard only on an explicit user request. The input version selects the historical spec; the result's version is the newly saved version. Every successful call appends another version, even when restoring the latest spec. A missing or pruned target returns error.code dashboard_version_not_found; list_dashboard_versions shows retained versions. Spec validation failures return error.code invalid_spec with the presented problems. Successful saves return a receipt for that committed version: base_version, version, changes, layout_changed, dashboard_fields and save_check. Changes describe authored panel fields and position_changed; physical packing is only layout_changed. Order chips mark the minimal moved set. save_check reports checked, reason when unchecked, total elapsed_ms and one status/rows result per saved panel with elapsed_ms when executed. checked means every panel was executed, not that error or empty panels are healthy. Text rows identify nonempty content, not telemetry. Use the structured receipt rather than prose warnings as check evidence. Create receipts list added panels without layout or dashboard edit chips. No-op saves still create a new version with no change chips. Per-panel elapsed_ms is omitted when the panel did not run. **Replaces existing state.** @@ -319,7 +318,7 @@ Read-only. ## search_logs -**Log explorer** — Search and filter logs with a severity timeline and links back to correlated traces. All-empty results return no_data guidance instead of a view. State no matching data findings in the answer and prefer signals the service actually has. +**Log explorer**: Search and filter logs with a severity timeline and links back to correlated traces. All-empty results return no_data guidance instead of a view. State no matching data findings in the answer and prefer signals the service actually has. Read-only. @@ -385,10 +384,10 @@ Per-service analytical rollup by namespace and time bucket. Panel SQL only; filt ## Authentication -MCP uses OAuth tokens bound to the instance’s resource URI. +MCP uses OAuth tokens bound to the instance's resource URI. [Connect an agent](/docs/guides/mcp#scopes) documents protocol requirements and delegated scopes. Dashboard tools operate on the authenticated -user’s own dashboards and require the account capability +user's own dashboards and require the account capability `dashboards:manage-own`; see [roles](/docs/reference/roles). ## Other interfaces diff --git a/site/src/content/docs/docs/reference/roles.mdx b/site/src/content/docs/docs/reference/roles.mdx index 2e041d3b..9a2689ad 100644 --- a/site/src/content/docs/docs/reference/roles.mdx +++ b/site/src/content/docs/docs/reference/roles.mdx @@ -1,7 +1,7 @@ --- title: "Roles" description: "The three Fanout roles and the capabilities each one carries." -summary: "viewer, operator and admin, and exactly what each is permitted to do — taken from the authorization table the server enforces." +summary: "viewer, operator and admin, and what each is permitted to do." read_when: - "You are deciding which role to give someone." - "You are mapping identity-provider groups onto Fanout roles." @@ -12,8 +12,7 @@ generated: true {/* Generated by cmd/fanout-docgen from internal/api. Edit the generator, not this page. */} -Each user has one fixed role: `viewer`, `operator` or `admin`. The table is -generated from the capability map enforced by the authorization middleware. +Each user has one fixed role: `viewer`, `operator` or `admin`. | Capability | viewer | operator | admin | |---|---|---|---| @@ -28,14 +27,14 @@ generated from the capability map enforced by the authorization middleware. ## What each capability permits -- `agent:run` — Run the built-in agent, which calls a model provider and acts on the instance's behalf. -- `alerts:manage` — Create, change and delete alert rules. -- `dashboards:manage-own` — Create and edit dashboards you own. Owner-scoped for every role, including admin. -- `ingest:manage` — Rotate the ingest token. -- `ingest:read-metadata` — See ingest settings, but not the token itself. -- `operations:read` — Read the instance's own internals: Prometheus metrics and, when enabled, the profiling endpoints. -- `telemetry:read` — Read every trace, log and metric the instance holds. -- `users:manage` — Create, change and remove user accounts and their roles. +- `agent:run`: Run the built-in agent, which calls a model provider and acts on the instance's behalf. +- `alerts:manage`: Create, change and delete alert rules. +- `dashboards:manage-own`: Create and edit dashboards you own. Owner-scoped for every role, including admin. +- `ingest:manage`: Rotate the ingest token. +- `ingest:read-metadata`: See ingest settings, but not the token itself. +- `operations:read`: Read the instance's own internals: Prometheus metrics and, when enabled, the profiling endpoints. +- `telemetry:read`: Read every trace, log and metric the instance holds. +- `users:manage`: Create, change and remove user accounts and their roles. ## Who assigns roles @@ -47,8 +46,6 @@ role and reapplies it on each login; otherwise roles stay local. [Authentication modes](/docs/concepts/authentication) covers the details, including why the last active administrator is never demoted. -:::caution[A role is not a data boundary] -Roles gate capabilities, not rows. Every role that can read telemetry can read -all of it — namespaces scope queries, not access. An instance is one trust -domain. +:::caution[Telemetry access] +See [namespace access limits](/docs/reference/capabilities#operating-limits). :::