refactor: keep tenant applications out of the administrator's reach

Claiming the system administrator role no longer grants access to every
application in the installation. Owning a tenant is what lets an administrator
administer it; entering the applications running inside it is a separate,
explicit grant that can be withdrawn again. Handing it out at claim time gave
every installation a standing administrator account inside every customer
application, which is the opposite of the boundary this product draws.

The behaviour suite now covers single sign-on between applications directly: a
second application on the same tenant must receive a code and identify the same
person with no sign-in page in between, once Go Loose already holds a session.

Also adds theme-token tests against the shared styling contract and store
coverage for application grants, and drops the redundant server and security
scheme declarations from the published specification.
This commit is contained in:
Daniel Noulet 2026-10-05 21:48:05 +02:00
parent 8d8970d5a7
commit 49ab89ae46
6 changed files with 319 additions and 20 deletions

View file

@ -232,6 +232,102 @@ func clientLoginPhases(t *testing.T, j *journey) {
statusOnly(t, replay, http.StatusUnauthorized) statusOnly(t, replay, http.StatusUnauthorized)
}) })
phase(t, "a second application is signed in without asking again", func(t *testing.T) {
// The single sign-on property: once Go Loose holds a session for this person
// in this tenant, another application on the same tenant must receive a
// code straight away, with no sign-in page in between.
type application struct {
ID string `json:"id"`
ClientID string `json:"client_id"`
RedirectURI []string `json:"redirect_uris"`
}
type setupResponse struct {
Application application `json:"application"`
Secret string `json:"client_secret"`
}
created := decode[application](t, j.admin.json(http.MethodPost, host, "/api/v1/applications", map[string]any{
"tenant_id": j.scopedTenantID, "slug": "second-app", "name": "Second App",
"allowed_hosts": []string{"second.example.test"},
}), http.StatusCreated)
setup := decode[setupResponse](t, j.admin.json(http.MethodPost, host,
"/api/v1/applications/"+created.ID+"/client-config", map[string]any{
"redirect_uris": []string{"https://second.example.test/callback"},
}), http.StatusOK)
if setup.Application.ClientID == "" || setup.Secret == "" {
t.Fatal("the second application was not configured for client login")
}
// The member needs access to it, the same as any other application.
access := j.admin.json(http.MethodPut, host, "/api/v1/users/"+j.memberID+"/access", map[string]any{
"tenant_id": j.scopedTenantID, "role_id": j.viewerRoleID,
"application_ids": []string{j.applicationID, created.ID},
})
if access.StatusCode != http.StatusNoContent {
t.Fatalf("grant the second application = %d: %s", access.StatusCode, bodyOf(t, access))
}
// Sign in once, through the first application.
browser := j.browser()
tenantHost := j.h.tenantHost(j.scopedTenant)
firstQuery := "response_type=code&client_id=" + j.clientID +
"&redirect_uri=" + url.QueryEscape(redirectURI) + "&state=first"
signIn := location(t, browser.get(tenantHost, "/connect/authorize?"+firstQuery, nil))
token := csrfFrom(t, bodyOf(t, browser.get(tenantHost, signIn, nil)))
response := browser.form(tenantHost, "/auth/password", neturlValues(map[string]string{
"csrf_token": token, "email": j.memberEmail, "password": j.memberPassword,
"return": returnParam(t, signIn),
}), nil)
if response.StatusCode != http.StatusSeeOther {
t.Fatalf("sign-in = %d: %s", response.StatusCode, bodyOf(t, response))
}
if response := browser.get(tenantHost, "/connect/authorize?"+firstQuery, nil); response.StatusCode != http.StatusFound {
t.Fatalf("first application = %d", response.StatusCode)
} else {
response.Body.Close()
}
// The second application must not see a sign-in page.
secondQuery := "response_type=code&client_id=" + setup.Application.ClientID +
"&redirect_uri=" + url.QueryEscape("https://second.example.test/callback") + "&state=second"
response = browser.get(tenantHost, "/connect/authorize?"+secondQuery, nil)
if response.StatusCode != http.StatusFound {
t.Fatalf("second application = %d, want a code with no prompt: %s",
response.StatusCode, bodyOf(t, response))
}
callback, err := url.Parse(location(t, response))
if err != nil {
t.Fatalf("parse callback: %v", err)
}
if callback.Host != "second.example.test" {
t.Errorf("callback host = %q", callback.Host)
}
if callback.Query().Get("code") == "" {
t.Error("no authorization code for the second application")
}
if callback.Query().Get("state") != "second" {
t.Errorf("state = %q", callback.Query().Get("state"))
}
// And it identifies the same person, with no further prompt.
exchange := j.browser().request(http.MethodPost, j.h.apex(), "/connect/token",
strings.NewReader(url.Values{
"grant_type": {"authorization_code"}, "code": {callback.Query().Get("code")},
"redirect_uri": {"https://second.example.test/callback"},
}.Encode()), map[string]string{
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": "Basic " + basicAuth(setup.Application.ClientID, setup.Secret),
})
type secondToken struct {
User struct {
Email string `json:"email"`
} `json:"user"`
}
granted := decode[secondToken](t, exchange, http.StatusOK)
if granted.User.Email != j.memberEmail {
t.Errorf("the second application saw %q, want %s", granted.User.Email, j.memberEmail)
}
})
phase(t, "the token identifies the user at the userinfo endpoint", func(t *testing.T) { phase(t, "the token identifies the user at the userinfo endpoint", func(t *testing.T) {
type identity struct { type identity struct {
ID string `json:"id"` ID string `json:"id"`

View file

@ -1,8 +1,11 @@
package server package server
import ( import (
"fmt"
"strings" "strings"
"testing" "testing"
"github.com/danyel/go-loose/web"
) )
// The theme origin is substituted into every HTML page. These tests cover the two // The theme origin is substituted into every HTML page. These tests cover the two
@ -89,3 +92,79 @@ func TestThemeOriginLeavesNonThemePagesAlone(t *testing.T) {
t.Errorf("a page without theme lines was modified:\n%s", got) t.Errorf("a page without theme lines was modified:\n%s", got)
} }
} }
// contractBackedTokens maps this application's own colour token onto the shared
// contract role that carries the same meaning. The right-hand side is a bare role
// name because the variable prefix belongs to the contract.
var contractBackedTokens = map[string]string{
"--bg": "bg",
"--surface": "surface",
// go-loose's second surface step is the contract's raised surface.
"--surface2": "surface-raised",
"--text": "text",
"--muted": "muted",
"--accent": "accent",
// go-loose's secondary accent is the contract's secondary role: for every
// palette the two agree, tokyo's #bb9af7 and catppuccin's #cba6f7 among them.
"--accent2": "secondary",
"--good": "good",
"--danger": "danger",
"--border": "border",
}
// TestLocalTokensAdoptTheContractWhereItIsServed covers the difference between a
// theme selector that is present in the page and one that works.
//
// Every palette this application names has to resolve through the contract's
// variables, with this application's own value as the fallback. The fallback is
// what keeps the install and login pages styled as they always were: they never load
// the contract, so the variable is undefined there and the literal applies. Loading
// the contract on a console page therefore adopts the shared palette without
// changing anything for the pages that do not ask for it.
//
// Both directions are asserted. Adopting the role is not enough on its own: a single
// declaration left holding a bare literal overrides the contract for whichever
// palette that block names, and the console then renders half shared and half local.
func TestLocalTokensAdoptTheContractWhereItIsServed(t *testing.T) {
css, err := web.Files.ReadFile("style.css")
if err != nil {
t.Fatalf("style.css must be embedded: %v", err)
}
stylesheet := string(css)
for token, role := range contractBackedTokens {
if !strings.Contains(stylesheet, fmt.Sprintf("%s:var(--bn-%s,", token, role)) {
t.Errorf("token %s does not adopt --bn-%s, so selecting a palette outside this application's own four changes nothing:\n%s",
token, role, firstLines(css, 8))
}
// A literal assignment anywhere would win for the palette it sits under, and
// the contract's value for that one palette would be discarded.
if literal := token + ":#"; strings.Contains(stylesheet, literal) {
t.Errorf("token %s is still assigned a bare literal, which overrides the contract for the palette that block names:\n%s",
token, firstLines(css, 8))
}
}
}
// TestTheShadowTokenIsNotMapped guards the one token deliberately left alone.
// The contract's shadow family holds complete box-shadow values, and this token
// holds a colour, so wiring them together would substitute a shadow for a colour.
func TestTheShadowTokenIsNotMapped(t *testing.T) {
css, err := web.Files.ReadFile("style.css")
if err != nil {
t.Fatalf("style.css must be embedded: %v", err)
}
if strings.Contains(string(css), "--shadow:var(--bn-") {
t.Error("--shadow is a colour here and the contract's shadow roles are box-shadow values; mapping them would break every elevation")
}
}
// firstLines trims a stylesheet for an error message, since the token blocks sit at
// the top of the file and the whole of it is noise in a test failure.
func firstLines(css []byte, n int) string {
lines := strings.SplitN(string(css), "\n", n+1)
if len(lines) > n {
lines = lines[:n]
}
return strings.Join(lines, "\n")
}

View file

@ -0,0 +1,121 @@
package store_test
import (
"errors"
"testing"
"github.com/danyel/go-loose/internal/store"
)
// TestSystemAdministratorIsNotAutomaticallyInsideTenantApplications pins the
// boundary between the management plane and the client plane.
//
// Owning every tenant is what lets the first administrator reach the whole
// installation from the console. It is not permission to sign in to the
// applications running inside those tenants: ClientForAuthorization answers for a
// user_application_access row and nothing else, so a claim that inserted one per
// application would ship every installation with a standing administrator
// account inside every customer application. Access to an application is an
// explicit grant, and this test also pins that such a grant is still possible on
// purpose, because an administrator is allowed to attach themselves.
func TestSystemAdministratorIsNotAutomaticallyInsideTenantApplications(t *testing.T) {
s, _, ctx := integrationStore(t)
// Two tenants, each with a client configured, so that "every application" is
// not satisfied by a single row.
firstOwner := newUser(t, s, ctx, "first@example.test")
first := newTenant(t, s, ctx, firstOwner, "nmbs")
secondOwner := newUser(t, s, ctx, "second@example.test")
second := newTenant(t, s, ctx, secondOwner, "ypto")
const redirect = "https://app.example.test/callback"
application := func(owner, tenantID, slug string) store.Application {
t.Helper()
created, err := s.CreateApplication(ctx, owner, store.Application{
TenantID: tenantID, Slug: slug, Name: slug, AllowedHosts: []string{},
})
if err != nil {
t.Fatalf("create application %s: %v", slug, err)
}
if _, err := s.ConfigureClient(ctx, owner, created.ID, []string{redirect}, secretHash(slug)); err != nil {
t.Fatalf("configure client login for %s: %v", slug, err)
}
return created
}
firstApp := application(firstOwner, first.ID, "guess")
secondApp := application(secondOwner, second.ID, "tell")
admin := newUser(t, s, ctx, "admin@example.test")
claimed, err := s.ClaimFirstSystemAdministrator(ctx, admin)
if err != nil {
t.Fatalf("claim first system administrator: %v", err)
}
if !claimed {
t.Fatal("the first user to sign in must become the system administrator")
}
// The management plane still reaches everything: the claim owns both tenants,
// which is what the console needs to administer them.
console, err := s.HasConsoleAccess(ctx, admin)
if err != nil {
t.Fatalf("HasConsoleAccess: %v", err)
}
if !console {
t.Error("a system administrator must have console access")
}
tenants, err := s.ListTenants(ctx, admin)
if err != nil {
t.Fatalf("ListTenants: %v", err)
}
if len(tenants) != 2 {
t.Errorf("the administrator owns %d tenants, want 2", len(tenants))
}
// The client plane does not. No application was granted, so neither endpoint
// that mints or resolves a hosted-login session will recognize them.
for _, test := range []struct {
name string
application store.Application
}{
{name: "nmbs", application: firstApp},
{name: "ypto", application: secondApp},
} {
t.Run(test.name, func(t *testing.T) {
if _, err := s.ClientForAuthorization(ctx, test.application.ClientID, redirect, admin); !errors.Is(err, store.ErrNotFound) {
t.Fatalf("ClientForAuthorization = %v, want ErrNotFound: owning a tenant must not open its applications", err)
}
if _, err := s.ClientUserByToken(ctx, secretHash("no-session")); !errors.Is(err, store.ErrNotFound) {
t.Fatalf("ClientUserByToken = %v, want ErrNotFound", err)
}
})
}
// Attaching the administrator to one application is allowed, and it is the only
// way in. They hold users.manage in every tenant by virtue of owning it, which
// is what makes the deliberate act possible.
ownerRole, err := s.SystemRoleID(ctx, "owner")
if err != nil {
t.Fatalf("look up the owner role: %v", err)
}
if err := s.SetUserAccess(ctx, admin, first.ID, admin, ownerRole, []string{firstApp.ID}); err != nil {
t.Fatalf("an administrator must be able to attach themselves to an application: %v", err)
}
if _, err := s.ClientForAuthorization(ctx, firstApp.ClientID, redirect, admin); err != nil {
t.Fatalf("ClientForAuthorization after an explicit grant = %v, want the application", err)
}
// Withdrawing it takes effect immediately and leaves the console untouched.
if err := s.SetUserAccess(ctx, admin, first.ID, admin, ownerRole, nil); err != nil {
t.Fatalf("withdraw the grant: %v", err)
}
if _, err := s.ClientForAuthorization(ctx, firstApp.ClientID, redirect, admin); !errors.Is(err, store.ErrNotFound) {
t.Fatalf("ClientForAuthorization after withdrawal = %v, want ErrNotFound", err)
}
console, err = s.HasConsoleAccess(ctx, admin)
if err != nil {
t.Fatalf("HasConsoleAccess: %v", err)
}
if !console {
t.Error("withdrawing an application grant must not cost the administrator their console")
}
}

View file

@ -98,6 +98,13 @@ func (s *Store) Install(ctx context.Context, installation Installation, demoUser
return tx.Commit() return tx.Commit()
} }
// ClaimFirstSystemAdministrator promotes the first user to sign in to system
// administrator and gives them ownership of every tenant that exists at that
// moment. It is a no-op once an administrator exists.
//
// It grants console reach across the installation and nothing more: no
// application grant, so the new administrator cannot sign in to a tenant
// application until one is deliberately given to them.
func (s *Store) ClaimFirstSystemAdministrator(ctx context.Context, userID string) (bool, error) { func (s *Store) ClaimFirstSystemAdministrator(ctx context.Context, userID string) (bool, error) {
tx, err := s.db.BeginTx(ctx, nil) tx, err := s.db.BeginTx(ctx, nil)
if err != nil { if err != nil {
@ -124,11 +131,14 @@ func (s *Store) ClaimFirstSystemAdministrator(ctx context.Context, userID string
ON CONFLICT DO NOTHING`, userID); err != nil { ON CONFLICT DO NOTHING`, userID); err != nil {
return false, err return false, err
} }
if _, err := tx.ExecContext(ctx, ` // Deliberately no application grant. Owning every tenant is what lets the
INSERT INTO user_application_access(application_id, user_id, granted_by) // administrator administer it; it is not a licence to enter the applications
SELECT a.id, $1, $1 FROM applications a ON CONFLICT DO NOTHING`, userID); err != nil { // running inside it. Access to a client application is only ever an explicit
return false, err // grant, so an administrator who wants to use one attaches themselves the same
} // way anybody else does, visibly, from the console, and it can be withdrawn
// again without touching the tenant. Granting it here instead would hand every
// installation a standing administrator account inside every customer
// application, which is the opposite of the boundary this product draws.
return true, tx.Commit() return true, tx.Commit()
} }

View file

@ -5,14 +5,7 @@
"version": "0.1.0", "version": "0.1.0",
"description": "Multi-tenant API-key management and runtime authorization." "description": "Multi-tenant API-key management and runtime authorization."
}, },
"servers": [{"url": "/"}],
"components": { "components": {
"securitySchemes": {
"session": {"type": "apiKey", "in": "cookie", "name": "go_loose_session"},
"apiKey": {"type": "apiKey", "in": "header", "name": "X-API-Key"},
"clientBasic": {"type": "http", "scheme": "basic"},
"userBearer": {"type": "http", "scheme": "bearer"}
},
"schemas": { "schemas": {
"Error": {"type": "object", "properties": {"error": {"type": "string"}}}, "Error": {"type": "object", "properties": {"error": {"type": "string"}}},
"AuthorizationRequest": { "AuthorizationRequest": {

View file

@ -1,11 +1,11 @@
:root{--bg:#16161e;--surface:#1f2335;--surface2:#24283b;--text:#c0caf5;--muted:#7aa2f7;--accent:#7dcfff;--accent2:#bb9af7;--good:#9ece6a;--danger:#f7768e;--border:#3b4261;--shadow:rgba(0,0,0,.35)} :root{--bg:var(--bn-bg,#16161e);--surface:var(--bn-surface,#1f2335);--surface2:var(--bn-surface-raised,#24283b);--text:var(--bn-text,#c0caf5);--muted:var(--bn-muted,#7aa2f7);--accent:var(--bn-accent,#7dcfff);--accent2:var(--bn-secondary,#bb9af7);--good:var(--bn-good,#9ece6a);--danger:var(--bn-danger,#f7768e);--border:var(--bn-border,#3b4261);--shadow:rgba(0,0,0,.35)}
[data-theme=catppuccin]{--bg:#1e1e2e;--surface:#181825;--surface2:#313244;--text:#cdd6f4;--muted:#89b4fa;--accent:#94e2d5;--accent2:#cba6f7;--good:#a6e3a1;--danger:#f38ba8;--border:#45475a} [data-theme=catppuccin]{--bg:var(--bn-bg,#1e1e2e);--surface:var(--bn-surface,#181825);--surface2:var(--bn-surface-raised,#313244);--text:var(--bn-text,#cdd6f4);--muted:var(--bn-muted,#89b4fa);--accent:var(--bn-accent,#94e2d5);--accent2:var(--bn-secondary,#cba6f7);--good:var(--bn-good,#a6e3a1);--danger:var(--bn-danger,#f38ba8);--border:var(--bn-border,#45475a)}
[data-theme=tomorrow]{--bg:#1d1f21;--surface:#282a2e;--surface2:#373b41;--text:#c5c8c6;--muted:#81a2be;--accent:#8abeb7;--accent2:#b294bb;--good:#b5bd68;--danger:#cc6666;--border:#4d5057} [data-theme=tomorrow]{--bg:var(--bn-bg,#1d1f21);--surface:var(--bn-surface,#282a2e);--surface2:var(--bn-surface-raised,#373b41);--text:var(--bn-text,#c5c8c6);--muted:var(--bn-muted,#81a2be);--accent:var(--bn-accent,#8abeb7);--accent2:var(--bn-secondary,#b294bb);--good:var(--bn-good,#b5bd68);--danger:var(--bn-danger,#cc6666);--border:var(--bn-border,#4d5057)}
[data-theme=kanagawa]{--bg:#1f1f28;--surface:#2a2a37;--surface2:#363646;--text:#dcd7ba;--muted:#7e9cd8;--accent:#7fb4ca;--accent2:#957fb8;--good:#98bb6c;--danger:#e46876;--border:#54546d} [data-theme=kanagawa]{--bg:var(--bn-bg,#1f1f28);--surface:var(--bn-surface,#2a2a37);--surface2:var(--bn-surface-raised,#363646);--text:var(--bn-text,#dcd7ba);--muted:var(--bn-muted,#7e9cd8);--accent:var(--bn-accent,#7fb4ca);--accent2:var(--bn-secondary,#957fb8);--good:var(--bn-good,#98bb6c);--danger:var(--bn-danger,#e46876);--border:var(--bn-border,#54546d)}
[data-mode=light][data-theme=tokyo]{--bg:#e6e7ed;--surface:#fff;--surface2:#dfe2ed;--text:#343b58;--muted:#34548a;--accent:#0f4b6e;--accent2:#5a4a78;--good:#485e30;--danger:#8c4351;--border:#b4b5c3;--shadow:rgba(52,59,88,.15)} [data-mode=light][data-theme=tokyo]{--bg:var(--bn-bg,#e6e7ed);--surface:var(--bn-surface,#fff);--surface2:var(--bn-surface-raised,#dfe2ed);--text:var(--bn-text,#343b58);--muted:var(--bn-muted,#34548a);--accent:var(--bn-accent,#0f4b6e);--accent2:var(--bn-secondary,#5a4a78);--good:var(--bn-good,#485e30);--danger:var(--bn-danger,#8c4351);--border:var(--bn-border,#b4b5c3);--shadow:rgba(52,59,88,.15)}
[data-mode=light][data-theme=catppuccin]{--bg:#eff1f5;--surface:#fff;--surface2:#e6e9ef;--text:#4c4f69;--muted:#1e66f5;--accent:#179299;--accent2:#8839ef;--good:#40a02b;--danger:#d20f39;--border:#bcc0cc;--shadow:rgba(76,79,105,.15)} [data-mode=light][data-theme=catppuccin]{--bg:var(--bn-bg,#eff1f5);--surface:var(--bn-surface,#fff);--surface2:var(--bn-surface-raised,#e6e9ef);--text:var(--bn-text,#4c4f69);--muted:var(--bn-muted,#1e66f5);--accent:var(--bn-accent,#179299);--accent2:var(--bn-secondary,#8839ef);--good:var(--bn-good,#40a02b);--danger:var(--bn-danger,#d20f39);--border:var(--bn-border,#bcc0cc);--shadow:rgba(76,79,105,.15)}
[data-mode=light][data-theme=tomorrow]{--bg:#f5f5f5;--surface:#fff;--surface2:#e8e8e8;--text:#4d4d4c;--muted:#4271ae;--accent:#3e999f;--accent2:#8959a8;--good:#718c00;--danger:#c82829;--border:#c9c9c9;--shadow:rgba(77,77,76,.15)} [data-mode=light][data-theme=tomorrow]{--bg:var(--bn-bg,#f5f5f5);--surface:var(--bn-surface,#fff);--surface2:var(--bn-surface-raised,#e8e8e8);--text:var(--bn-text,#4d4d4c);--muted:var(--bn-muted,#4271ae);--accent:var(--bn-accent,#3e999f);--accent2:var(--bn-secondary,#8959a8);--good:var(--bn-good,#718c00);--danger:var(--bn-danger,#c82829);--border:var(--bn-border,#c9c9c9);--shadow:rgba(77,77,76,.15)}
[data-mode=light][data-theme=kanagawa]{--bg:#f2ecde;--surface:#fffaf0;--surface2:#e7dba9;--text:#545464;--muted:#4d699b;--accent:#4d7f8c;--accent2:#624c83;--good:#6f894e;--danger:#c34043;--border:#c8c093;--shadow:rgba(84,84,100,.15)} [data-mode=light][data-theme=kanagawa]{--bg:var(--bn-bg,#f2ecde);--surface:var(--bn-surface,#fffaf0);--surface2:var(--bn-surface-raised,#e7dba9);--text:var(--bn-text,#545464);--muted:var(--bn-muted,#4d699b);--accent:var(--bn-accent,#4d7f8c);--accent2:var(--bn-secondary,#624c83);--good:var(--bn-good,#6f894e);--danger:var(--bn-danger,#c34043);--border:var(--bn-border,#c8c093);--shadow:rgba(84,84,100,.15)}
*{box-sizing:border-box}html{background:var(--bg);color:var(--text);font-family:Inter,ui-sans-serif,system-ui,sans-serif}body{margin:0;min-height:100vh;background:radial-gradient(circle at 70% -10%,color-mix(in srgb,var(--accent2) 13%,transparent),transparent 35%),var(--bg)}button,input,textarea,select{font:inherit}.noise{position:fixed;inset:0;pointer-events:none;opacity:.025;background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E")} *{box-sizing:border-box}html{background:var(--bg);color:var(--text);font-family:Inter,ui-sans-serif,system-ui,sans-serif}body{margin:0;min-height:100vh;background:radial-gradient(circle at 70% -10%,color-mix(in srgb,var(--accent2) 13%,transparent),transparent 35%),var(--bg)}button,input,textarea,select{font:inherit}.noise{position:fixed;inset:0;pointer-events:none;opacity:.025;background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E")}
.docs-page{background-color: white}header{height:72px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;padding:0 max(24px,5vw);background:color-mix(in srgb,var(--bg) 85%,transparent);backdrop-filter:blur(16px);position:sticky;top:0;z-index:20}.brand{display:flex;align-items:center;gap:12px;color:var(--text);font-weight:750;text-decoration:none;letter-spacing:-.02em}.brand-mark{display:grid;place-items:center;width:34px;height:34px;border:1px solid var(--accent);color:var(--accent);font:700 12px ui-monospace,monospace;clip-path:polygon(10% 0,100% 0,100% 75%,75% 100%,0 100%,0 10%)}nav{display:flex;align-items:center;gap:18px}nav a{color:var(--muted);text-decoration:none;font-size:14px}nav form{margin:0}.compact{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--muted)}select,input,textarea{color:var(--text);background:var(--surface2);border:1px solid var(--border);border-radius:7px;padding:10px 12px;outline:none}select:focus,input:focus,textarea:focus{border-color:var(--accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 15%,transparent)}.compact select{padding:6px}.icon-button,.ghost{padding:7px 10px;background:transparent;border:1px solid var(--border);color:var(--text);border-radius:7px}.identity{font-size:13px;color:var(--accent)} .docs-page{background-color: white}header{height:72px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;padding:0 max(24px,5vw);background:color-mix(in srgb,var(--bg) 85%,transparent);backdrop-filter:blur(16px);position:sticky;top:0;z-index:20}.brand{display:flex;align-items:center;gap:12px;color:var(--text);font-weight:750;text-decoration:none;letter-spacing:-.02em}.brand-mark{display:grid;place-items:center;width:34px;height:34px;border:1px solid var(--accent);color:var(--accent);font:700 12px ui-monospace,monospace;clip-path:polygon(10% 0,100% 0,100% 75%,75% 100%,0 100%,0 10%)}nav{display:flex;align-items:center;gap:18px}nav a{color:var(--muted);text-decoration:none;font-size:14px}nav form{margin:0}.compact{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--muted)}select,input,textarea{color:var(--text);background:var(--surface2);border:1px solid var(--border);border-radius:7px;padding:10px 12px;outline:none}select:focus,input:focus,textarea:focus{border-color:var(--accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 15%,transparent)}.compact select{padding:6px}.icon-button,.ghost{padding:7px 10px;background:transparent;border:1px solid var(--border);color:var(--text);border-radius:7px}.identity{font-size:13px;color:var(--accent)}
main{width:min(1400px,92vw);margin:0 auto 80px}.hero{min-height:330px;display:flex;align-items:center;justify-content:space-between;gap:60px;border-bottom:1px solid var(--border)}.eyebrow{font:700 11px ui-monospace,monospace;letter-spacing:.16em;color:var(--accent);margin:0 0 12px}.hero h1{font-size:clamp(44px,5vw,74px);line-height:.94;letter-spacing:-.055em;margin:0}.hero h1 span{color:var(--accent2)}.lede{max-width:680px;color:color-mix(in srgb,var(--text) 70%,transparent);font-size:17px;line-height:1.7}.status-card{min-width:320px;background:linear-gradient(145deg,var(--surface),var(--surface2));border:1px solid var(--border);border-radius:14px;padding:22px;box-shadow:0 20px 60px var(--shadow);display:grid;grid-template-columns:auto 1fr;gap:12px;align-items:center}.status-card span{display:block;color:var(--good);font-size:12px;margin-top:4px}.status-card code{grid-column:1/-1;background:var(--bg);padding:12px;border-radius:7px;color:var(--muted)}.pulse{width:10px;height:10px;border-radius:50%;background:var(--good);box-shadow:0 0 0 6px color-mix(in srgb,var(--good) 14%,transparent)} main{width:min(1400px,92vw);margin:0 auto 80px}.hero{min-height:330px;display:flex;align-items:center;justify-content:space-between;gap:60px;border-bottom:1px solid var(--border)}.eyebrow{font:700 11px ui-monospace,monospace;letter-spacing:.16em;color:var(--accent);margin:0 0 12px}.hero h1{font-size:clamp(44px,5vw,74px);line-height:.94;letter-spacing:-.055em;margin:0}.hero h1 span{color:var(--accent2)}.lede{max-width:680px;color:color-mix(in srgb,var(--text) 70%,transparent);font-size:17px;line-height:1.7}.status-card{min-width:320px;background:linear-gradient(145deg,var(--surface),var(--surface2));border:1px solid var(--border);border-radius:14px;padding:22px;box-shadow:0 20px 60px var(--shadow);display:grid;grid-template-columns:auto 1fr;gap:12px;align-items:center}.status-card span{display:block;color:var(--good);font-size:12px;margin-top:4px}.status-card code{grid-column:1/-1;background:var(--bg);padding:12px;border-radius:7px;color:var(--muted)}.pulse{width:10px;height:10px;border-radius:50%;background:var(--good);box-shadow:0 0 0 6px color-mix(in srgb,var(--good) 14%,transparent)}