featuregate

package
v1.801.60 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package featuregate is the launch-control GATE for Hanzo's hosted services — the COMPLETE waitlist feature, COMPOSING the ONE flag engine (clients/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 (+ /v1/featuregate/mode compat), Mount,
  • the native enforcement middleware (Enforce, this file),
  • the per-user approval predicate (Approvals, reused from IAM — approval.go).

flags NEVER imports featuregate; featuregate 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

Constants

This section is empty.

Variables

View Source
var ErrServiceNotFound = errors.New("featuregate: 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

func Mount(app *zip.App, deps cloud.Deps) error

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 the CURRENT frozen path (/v1/flags/waitlist) plus the /v1/featuregate/mode compat alias. 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

func NormalizeHost(host string) string

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 Shutdown

func Shutdown() error

Shutdown closes the launch registry's per-org store handles.

func WaitlistModeForHost added in v1.801.59

func WaitlistModeForHost(ctx context.Context, host string) (mode bool, service string, known bool)

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 properties.approvalStatus == "pending"). Resolution order:

  1. global admin (c.IsAdmin()) → approved (admins are never gated)
  2. validated header X-User-Approved → its bit (forward-perfect, no lookup)
  3. IAM get-account (caller's creds) → approved unless approvalStatus=="pending" — cached per user for ttl; FAIL-OPEN on any IAM error.

func NewApprovals

func NewApprovals(iamBase string, ttl time.Duration) *Approvals

NewApprovals builds a resolver. iamBase is the in-cluster IAM base (e.g. http://iam.hanzo.svc.cluster.local:8000); ttl bounds the per-user cache. A zero iamBase yields a resolver whose lookup always fails-open (approved) — safe for a deployment where approval is enforced elsewhere (the guard).

func (*Approvals) Approved

func (a *Approvals) Approved(c *zip.Ctx) bool

Approved reports whether the caller is off the waitlist.

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 from IAMBase.
	Approvals *Approvals

	// IAMBase is the in-cluster IAM base used to build Approvals when it is nil.
	IAMBase string

	// 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 (hk-/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(featuregate.Enforce(featuregate.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

type SeedService struct {
	Service     string
	DisplayName string
	Description string
	Hosts       []string
}

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 added in v1.801.59

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 added in v1.801.59

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 added in v1.801.59

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 added in v1.801.59

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 added in v1.801.59

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 added in v1.801.59

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).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL