Documentation
¶
Overview ¶
Package admission is the launch-control GATE for Hanzo's hosted services — the COMPLETE waitlist feature, COMPOSING the ONE flag engine (apps/flags) one-way. It owns:
- the host→service registry (registry.go) + the brand seed (waitlist.go),
- the per-service MODE decide WaitlistModeForHost — a service's mode IS the switch waitlist.<svc>, evaluated through the flag engine (flags.Bool),
- the admin control funcs (List/Set/Upsert) the /v1/admin/services board calls,
- the guard's public mode read /v1/flags/waitlist, Mount,
- the native enforcement middleware (Enforce, this file),
- the per-user approval predicate (Approvals, reused from IAM — approval.go).
flags NEVER imports admission; admission imports flags. The engine is the pure (Principal, context) -> verdict primitive; this package is its first composed tenant. Enforcement is decomplected into two orthogonal axes:
- PER-SERVICE waitlist mode on|off — the switch waitlist.<svc>, resolved for a request host via WaitlistModeForHost (the decide, waitlist.go).
- PER-USER approvalStatus pending|approved — owned by IAM (approval.go), REUSED.
THE RULE, applied at ONE native enforcement point (Enforce):
if waitlistMode[host] AND NOT user.approved → bounce to the waitlist if approved OR mode=off → allow unauthenticated → login first
Index ¶
- Variables
- func Enforce(cfg EnforceConfig) zip.Handler
- func Mount(app cloud.Router, deps cloud.Deps) error
- func NormalizeHost(host string) string
- func Shutdown() error
- func WaitlistModeForHost(ctx context.Context, host string) (mode bool, service string, known bool)
- type Approvals
- type EnforceConfig
- type SeedService
- type ServiceInput
- type ServiceRow
- type ServiceView
Constants ¶
This section is empty.
Variables ¶
var ErrServiceNotFound = errors.New("admission: waitlist service not found")
ErrServiceNotFound is returned when a service slug is not in the registry.
Functions ¶
func Enforce ¶
func Enforce(cfg EnforceConfig) zip.Handler
Enforce builds the native enforcement middleware. It is a no-op passthrough when the decide reports the host is not governed (gate known=false — the flags registry not mounted yet, a store error, or an un-governed host), so a request before boot completes is never wrongly gated.
func Mount ¶
Mount installs the launch-control gate: it opens the platform-tenant host→service registry, seeds it for the deployment brand, registers a waitlist.<svc> switch per service in the flag engine (flags.Register), and serves the guard's public mode read at /v1/flags/waitlist. Fail-safe: a registry error (e.g. cek master key not yet injected) degrades to the in-memory seed switches — WaitlistModeForHost then fail-opens. Mounts AFTER flags so the engine's platform-switch plane is installed first.
func NormalizeHost ¶
NormalizeHost reduces a request Host to the registry key: lowercased, trimmed, port stripped. ONE canonicalization for the seed, onboard, and every lookup, so "Hanzo.Chat:443" and "hanzo.chat" resolve to the same service.
func WaitlistModeForHost ¶
WaitlistModeForHost is THE decide the Enforce consumer, /v1/flags/waitlist, and the admin board call: resolve host→service, then read the waitlist.<svc> switch through the flag engine. FAIL-OPEN by construction — an unmounted registry, a store error, or an un-governed host all return known=false, so a request is NEVER gated pre-boot or on a registry fault (availability over a hard gate, matching the guard).
Types ¶
type Approvals ¶
type Approvals struct {
// contains filtered or unexported fields
}
Approvals resolves whether the current caller is off the waitlist. It is the ONE approval predicate the native middleware uses, DRY with the @file waitlist-guard (both read approvalStatus == "pending"). Resolution order:
- global admin (c.IsAdmin()) → approved (admins are never gated)
- validated header X-User-Approved → its bit (forward-perfect, no lookup)
- the iam peer (plane.IAMApproval) → approved unless approvalStatus=="pending" — cached per user for ttl; FAIL-OPEN on any error.
func NewApprovals ¶
NewApprovals builds a resolver over the iam peer; ttl bounds the per-user cache.
It takes no address. iam is reached by NAME over its own socket, so there is no base URL for a deployment to supply and no way for one to be wrong — the zero-iamBase case this used to carry (a resolver that always failed open because nobody had configured a URL) is not expressible any more.
type EnforceConfig ¶
type EnforceConfig struct {
// WaitlistURL is where an unapproved / unauthenticated browser is bounced
// (per-brand, e.g. https://waitlist.hanzo.ai). Empty → API-style 403/401 for
// everyone (no redirect target), so enforcement still holds.
WaitlistURL string
// Approvals resolves whether the caller is off the waitlist. When nil, Enforce
// builds one over the iam peer — which takes no address, so there is nothing
// else for a deployment to supply.
Approvals *Approvals
// ExemptPrefixes are request-path prefixes never gated (health/metrics/auth).
// A sensible default set is used when empty.
ExemptPrefixes []string
// Gate is THE decide: it resolves whether a request host is in waitlist mode,
// via the ONE policy engine. When nil it is WaitlistModeForHost —
// host→service→waitlist.<svc>. Injected only in tests. Fail-open by contract:
// known=false (unmounted / registry error / un-governed host) → not gated.
Gate func(ctx context.Context, host string) (mode bool, service string, known bool)
}
Enforce is the NATIVE, forward-perfect enforcement point for the waitlist — the single in-binary middleware that reads the registry (in-process, no HTTP hop) and the caller's approval, and applies THE RULE for every request whose Host is a governed service. As product hosts fold into the one-binary cloud, this is the ONE enforcement point (the @file waitlist-guard is the interim gate for hosts not yet cloud-fronted; both read the SAME registry so a toggle governs both).
THE RULE (per request, on a governed host in waitlist mode):
carries a Hanzo API key (pk-/sk-) → allow (paid inference; possession-gated) exempt path (health/iam/waitlist) → allow unauthenticated → 302 waitlist (browser) / 401 (API) waitlist mode OFF (or host un-governed) → allow (c.Next) waitlist mode ON AND approved → allow (c.Next) waitlist mode ON AND NOT approved → 302 waitlist (browser) / 403 (API)
INTEGRATION POINT — wire in serve.go RIGHT AFTER SanitizeIdentity:
app.Use(IdentityMiddleware(cfg)) // establishes the validated principal
app.Use(admission.Enforce(admission.EnforceConfig{ WaitlistURL: … })) // ← here
It reads the sanitized X-User-Id / X-User-IsAdmin / X-User-Approved that IdentityMiddleware minted, so it MUST run after it and (like BillingGate) before the subsystem handlers. It is deliberately NOT wired here — the unified-binary agent owns serve.go's boot chain; this package exposes Enforce so the one-line app.Use lands without a merge collision. The decide (WaitlistModeForHost) is resolved PER REQUEST and fail-opens until the flags engine has mounted, so Enforce can be constructed before Mount runs.
WHY NATIVE IS CANONICAL (in-cluster-bypass). The @file edge guard only gates traffic arriving THROUGH the ingress — a pod reaching another service's pod directly in-cluster bypasses it (a cluster-wide baseline NetworkPolicy + Cilium broad-allow union means additive netpols can't seal that). For a waitlist (threat model = external users) edge-only is acceptable, but this native middleware is FORWARD-PERFECT: when the app IS cloud, the gate is IN the request path, so reaching the pod directly STILL hits it — there is no edge to go around. That is the reason the native middleware is the canonical enforcement and the @file guard is purely interim. It is also STATELESS (it reads the sanitized X-User-* headers, sets no cookie), so the multi-apex cookie-domain concern the @file guard must handle does not exist here at all.
Paths that must NEVER be gated (health, the waitlist page's own API, auth callbacks) are skipped via ExemptPrefixes so enforcement can't lock the platform out of its own recovery/observability surface.
type SeedService ¶
SeedService is one row of the launch registry (a hosted service + its hosts). Mode is intentionally absent — the launch posture (gated) is waitlistDef's Default "true".
type ServiceInput ¶
type ServiceInput struct {
Service string `json:"service"`
DisplayName string `json:"displayName"`
Description string `json:"description"`
Hosts []string `json:"hosts"`
WaitlistMode bool `json:"waitlistMode"`
}
ServiceInput is the admin onboard/edit payload for /v1/admin/services. WaitlistMode sets the launch switch for a NEW service; a re-register PRESERVES the live switch.
type ServiceRow ¶
type ServiceRow struct {
Service string `json:"service"`
DisplayName string `json:"displayName"`
Description string `json:"description"`
Hosts []string `json:"hosts"`
CreatedAt int64 `json:"createdAt"`
UpdatedAt int64 `json:"updatedAt"`
UpdatedBy string `json:"updatedBy"`
}
ServiceRow is one hosted service in the registry (host→service + metadata). The waitlist MODE is intentionally absent — it is the platform switch waitlist.<svc>, read through the engine; ListWaitlistServices composes the two into a ServiceView.
type ServiceView ¶
type ServiceView struct {
ServiceRow
WaitlistMode bool `json:"waitlistMode"`
}
ServiceView is one service as the admin board renders it: the registry row plus its LIVE waitlist mode (the waitlist.<svc> switch evaluated through the engine).
func ListWaitlistServices ¶
func ListWaitlistServices(ctx context.Context) ([]ServiceView, error)
ListWaitlistServices returns the admin board: every registered service with its LIVE mode (the waitlist.<svc> switch). SuperAdmin surface (the caller gates).
func SetWaitlistMode ¶
func SetWaitlistMode(ctx context.Context, service string, mode bool, actor string) (ServiceView, error)
SetWaitlistMode flips one service's waitlist switch — the launch lever — and returns the updated view. It is the ONE write path (through flags.SetPlatformSwitch, audited in the flag activity log); the flip is hot (this pod applies immediately, peers converge within the eval TTL). ErrServiceNotFound when the slug is unknown.
func UpsertWaitlistService ¶
func UpsertWaitlistService(ctx context.Context, in ServiceInput, actor string) (ServiceView, error)
UpsertWaitlistService onboards or edits a hosted service so a new host is governed WITHOUT a redeploy. A NEW service takes in.WaitlistMode as its launch mode; a re-register PRESERVES the live switch (never silently re-gating an opened service).