settings

package
v0.9.4 Latest Latest
Warning

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

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

Documentation

Overview

Package settings is the typed-sectioned config layer.

Each known section is a Go type with a Validate() method, registered statically in this package's init via Register(). The DB stores one row per section in the `settings` table; the value is opaque JSONB, the typed struct enforces shape on read and write.

Adding a new section: create <section>.go with the typed struct, implement Validate() error, call Register() from its init().

Hot-path consumers read from the in-memory Cache populated by the catalog reconciler; the Store is admin-plane only.

Index

Constants

View Source
const (
	SectionGovernanceProvider = "governance:provider"
	SectionGovernanceHost     = "governance:host"
	SectionGovernanceModel    = "governance:model"
	SectionGovernancePolicy   = "governance:policy"
)

Governance section keys. Colon-namespaced so they double as future RBAC permission targets (governance:<resource>). Only the catalog-managed kinds need a section; purely user-owned kinds fall through to the user tier in Governs.

View Source
const AuthOIDCSection = "auth:oidc"

AuthOIDCSection is the section name.

View Source
const OAuthSectionPrefix = "oauth:"

OAuthSectionPrefix namespaces per-provider OAuth sections (oauth:anthropic, oauth:openai, …), mirroring the governance:<kind> convention.

View Source
const SectionCatalogSource = "catalog-source"

SectionCatalogSource is the section key for the catalog-source marker — the record of which public-catalog version the DB's template rows were last seeded from.

View Source
const SectionInference = "inference"

SectionInference is the section key for normal-mode inference behavior knobs — settings that affect /v1/* traffic served via an authenticated RelayKey. Distinct from proxy-mode (BYO-creds), which has its own section.

View Source
const SectionParsing = "parsing"

SectionParsing is the section key for request-parsing behavior knobs — how much structure the relay extracts from inbound request bodies before dispatch. Applies across flows (authenticated /v1/* and proxy-mode), so it is its own section rather than living under inference or proxy-mode.

View Source
const SectionPayloadLogging = "payload-logging"

SectionPayloadLogging is the section key for the request/response body capture observer's runtime config. Mutable at runtime via the settings API — the payload observer reconciles the live value and hot-swaps its sink (toggle, backend, bucket, credentials) without a restart.

View Source
const SectionProxyMode = "proxy-mode"

SectionProxyMode is the section key for the proxy-mode settings — the singleton that gates anonymous-key (BYO upstream creds) inference traffic served by the future app/proxy flow.

View Source
const SectionUsageLogging = "usage-logging"

SectionUsageLogging is the section key for the log (usage) event sink's backend selection. The log observer reconciles the live value and hot-swaps its sink + reader without a restart. Mirrors payload-logging, minus the per-request opt-in (logging is constant — every request).

Connection-level config (DSNs) stays bootstrap-tier (boot env), the same way the PG DSN does: you need a connection before you can read settings. Only the backend selection + safe per-backend knobs live here.

Variables

GovernanceSections lists every registered governance:<kind> section key.

Functions

func Governs

func Governs(r Reader, op Op, kind, ownerKind string) error

Governs decides whether op may be applied to a row of the given kind with the given ownerKind. It is the single source of truth for mutation governance; httpapi calls it and maps a non-nil result to 403.

Owner tiers are hardcoded invariants (not operator-toggleable):

  • system → never deleted; edited only via limited APIs (the settings API and specialized endpoints), never generic CRUD. Editing or deleting a system row can break the whole router.
  • user → always allowed (the row is the caller's). RBAC will later add an owner.id == caller match here.
  • else (provider/host-owned, i.e. catalog-managed) → consult the kind's governance:<kind> section; absent or unregistered ⇒ the safe default (edit allowed, delete denied).

func Names

func Names() []string

Names returns all registered section names in lexical order. Used by the list endpoint and the cache to enumerate sections.

func OAuthSection added in v0.4.2

func OAuthSection(provider string) string

OAuthSection returns the settings section name for a provider.

func Register

func Register(sec Section)

Register adds sec to the section registry. Called from each section's init(). Panics on duplicate registration — the typo would otherwise be silent.

func RegisterOAuthProvider added in v0.4.2

func RegisterOAuthProvider(provider string)

RegisterOAuthProvider registers the oauth:<provider> settings section so its config can be seeded and edited via the settings API. Call once per provider (e.g. from a vendor wiring file in the composition root) before settings are seeded or served. Panics on duplicate registration.

func SeedDir

func SeedDir(ctx context.Context, store *Store, dir string) ([]string, error)

SeedDir loads settings from manifest YAML in dir and upserts each section that has NO existing DB row — **seed-if-absent**. It never clobbers a runtime change or a prior seed, so a live `PUT /settings/...` stays authoritative. This is the first-boot / airgapped path; managed deployments are configured at runtime via the settings API.

Settings use the same Kubernetes-style manifest envelope as every other catalog resource — `apiVersion` + `kind: Setting` + `metadata.name` (the section key) + `spec` (the section's typed value). Files may hold multiple `---`-separated docs. metadata.name selecting an unregistered section, a spec that fails the section's Decode, or a non-Setting kind in the tree are all hard errors (fail fast on a typo). A missing dir is a no-op.

Returns the section names actually seeded, lexically sorted.

func SetSchemaRef

func SetSchemaRef(name, ref string)

SetSchemaRef records the OpenAPI component name for section's typed value. Called by the HTTP layer right after huma registers the typed GET/PUT pair, so the per-section schema id is discoverable via the list / sections endpoints.

Types

type AuthOIDC added in v0.7.0

type AuthOIDC struct {
	Enabled bool `json:"enabled"`

	// Issuer is the provider's issuer URL; endpoints are discovered from
	// its metadata document.
	Issuer   string `json:"issuer,omitempty"`
	ClientID string `json:"clientId,omitempty"`

	// ClientSecretEnv names the env var holding the client secret.
	// Indirection keeps the secret out of the settings row (world-readable
	// to any authenticated operator).
	ClientSecretEnv string `json:"clientSecretEnv,omitempty"`

	// RedirectURL is this relay's public callback,
	// e.g. https://relay.example.com/api/auth/oidc/callback.
	RedirectURL string `json:"redirectUrl,omitempty"`

	// Scopes defaults to ["openid", "profile", "email"].
	Scopes []string `json:"scopes,omitempty"`

	// AuthParams are extra authorization-URL query params some providers
	// require beyond the OIDC standard set (e.g. WorkOS AuthKit needs
	// {"provider": "authkit"}). Provider-specific values live here in
	// config, never in code.
	AuthParams map[string]string `json:"authParams,omitempty"`

	// Registration gates first-login auto-provisioning: "open" creates a
	// user row on first successful OIDC login; "closed" (default) rejects
	// subjects with no existing user row.
	Registration string `json:"registration,omitempty"`
}

AuthOIDC is the auth:oidc settings section: inbound OpenID Connect login for the control plane. Disabled by default — the zero value changes nothing about a deployment. When enabled, GET /auth/oidc/start redirects to the provider and /auth/oidc/callback exchanges the code and mints a normal relay session, so everything downstream of login is unchanged.

The provider is generic OIDC: any issuer publishing a discovery document (RFC 8414 / OpenID discovery) works. The client secret is referenced by env var name, never stored in the section value.

func AuthOIDCFrom added in v0.7.0

func AuthOIDCFrom(r Reader) *AuthOIDC

AuthOIDCFrom reads the typed section from a settings Reader, tolerating absent or mistyped values (returns the zero value → disabled).

func (*AuthOIDC) EffectiveScopes added in v0.7.0

func (c *AuthOIDC) EffectiveScopes() []string

EffectiveScopes returns Scopes or the OIDC default set.

func (*AuthOIDC) OpenRegistration added in v0.7.0

func (c *AuthOIDC) OpenRegistration() bool

OpenRegistration reports whether first-login auto-provisioning is on.

func (*AuthOIDC) Validate added in v0.7.0

func (c *AuthOIDC) Validate() error

Validate enforces shape only when enabled — a disabled section may be sparse or empty.

type CatalogSource added in v0.5.0

type CatalogSource struct {
	// Version is the catalog ref (tag) the last versioned seed ran from,
	// e.g. "v0.1.0". Empty when the catalog was seeded without a version
	// (local dir / embedded first-boot seed) or never seeded.
	Version string `json:"version,omitempty"`

	// SeededAt is when that seed completed (RFC 3339).
	SeededAt string `json:"seededAt,omitempty"`
}

CatalogSource records the provenance of the seeded catalog. Written by the bootstrap seeder after a successful versioned seed; compared against RELAY_CATALOG_VERSION at boot to decide whether a re-seed is due. An operator can blank Version via the settings API to force a re-seed on the next boot. Re-seeds only touch pristine template rows: operator- edited (dirty) rows are skipped and overlays re-merge at snapshot load, so user changes survive.

func (*CatalogSource) Validate added in v0.5.0

func (c *CatalogSource) Validate() error

type Governance

type Governance struct {
	AllowEdit   bool `json:"allowEdit"`
	AllowDelete bool `json:"allowDelete"`
}

Governance is the per-resource mutation policy: whether a catalog-managed row of a given kind may be edited or deleted through the generic control CRUD API. One value type is shared by every governance:<kind> section so the shape — and the decision logic in Governs — stays uniform.

These toggles are a speed-bump against accidental mutation, not a wall: editing catalog rows is rare but safe (the router self-heals; nothing hard-depends on a specific row surviving), so AllowEdit defaults true. Deleting is the click-around risk, so AllowDelete defaults false and an operator flips it on when they actually mean to prune.

func (*Governance) Validate

func (g *Governance) Validate() error

type Inference

type Inference struct {
	// AllowMissingPolicy permits a RelayKey with an empty Spec.PolicyID
	// to reach inference endpoints. The request bypasses the per-policy
	// authorization gate: any model served by any host the relay has
	// hostkeys for is reachable, with no policy-level rate limits (system
	// ratelimits still apply). When false, requests from such keys are
	// rejected with 403.
	//
	// Default false. Turn on only for self-hosted setups where the
	// operator is the caller (single-tenant) and wants a god-mode key.
	AllowMissingPolicy bool `json:"allowMissingPolicy"`
}

Inference controls authenticated-flow behavior.

func (*Inference) Validate

func (i *Inference) Validate() error

type MutationError

type MutationError struct {
	Op        Op
	Kind      string
	OwnerKind string
	Reason    string
}

MutationError is returned by Governs when an operation is not permitted. The control layer maps it to a 403.

func (*MutationError) Error

func (e *MutationError) Error() string

type OAuthProvider added in v0.4.2

type OAuthProvider struct {
	oauth.ProviderConfig
}

OAuthProvider is the typed value of an oauth:<provider> settings section: the flow config (authorize/token endpoints, client id, scopes, authorize params) used to acquire and refresh that provider's OAuth credentials.

The relay core ships only this generic shape. Concrete provider values are operator/community config — register a provider's section with RegisterOAuthProvider and supply the values via the settings API or a config/settings/oauth-<provider>.yaml. No provider is registered by default.

func (*OAuthProvider) Validate added in v0.4.2

func (c *OAuthProvider) Validate() error

Validate enforces the minimum a flow + refresh needs.

type Op

type Op string

Op is a mutating operation subject to the governance check.

const (
	OpEdit   Op = "edit"
	OpDelete Op = "delete"
)

type Parsing

type Parsing struct {
	// RichParsing extracts per-request metadata and messages from the
	// body (for attribution / observability). When false the relay
	// reads only the minimal fields needed to route (model, stream),
	// leaving metadata and messages unparsed — lower CPU on the hot
	// path, no body-level observability.
	//
	// Default true. Hot-swappable: a change takes effect on the next
	// request within a reconcile interval, no restart.
	RichParsing bool `json:"richParsing"`
}

Parsing controls inbound request-body parsing depth.

func (*Parsing) Validate

func (p *Parsing) Validate() error

type PayloadClickHouse

type PayloadClickHouse struct {
	// RetentionDays overrides the MergeTree TTL; 0 uses the backend default.
	RetentionDays int `json:"retentionDays,omitempty"`
	// WALDir overrides the local WAL segment directory; empty uses the
	// boot default.
	WALDir string `json:"walDir,omitempty"`
}

PayloadClickHouse configures the ClickHouse backend (Langfuse-style: text bodies as ZSTD String columns, queryable). The DSN is NOT stored here — it reuses the relay's boot CH connection (RELAY_CH_DSN, the same cluster the usage sink uses), so no credentials live in this row. Only the per-backend knobs that are safe to hot-swap live here.

type PayloadFile

type PayloadFile struct {
	Path string `json:"path"`
}

PayloadFile configures the JSONL file backend.

type PayloadLogging

type PayloadLogging struct {
	// Enabled is the global master switch. When false the observer
	// produces nothing regardless of per-request opt-in.
	Enabled bool `json:"enabled"`

	// Backend selects the sink: "file" (default), "s3", or "clickhouse".
	Backend string `json:"backend"`

	// MaxBytes caps each stored body; 0 = unlimited.
	MaxBytes int `json:"maxBytes"`

	File PayloadFile       `json:"file"`
	S3   PayloadS3         `json:"s3"`
	CH   PayloadClickHouse `json:"clickhouse"`
}

PayloadLogging configures the payloadlog observer. Off by default; the per-request opt-in (policy/relaykey) still gates capture on top of Enabled. Credentials are secret.Refs resolved via pkg/secret, so they can live in env, encrypted-PG, or a future external backend — never as plaintext in this row.

func (*PayloadLogging) Validate

func (p *PayloadLogging) Validate() error

Validate is enforced before any write. Only meaningful when Enabled — a disabled section can hold partial config (e.g. while an operator fills in S3 details before flipping it on).

type PayloadS3

type PayloadS3 struct {
	Endpoint  string     `json:"endpoint"`
	Bucket    string     `json:"bucket"`
	Region    string     `json:"region,omitempty"`
	Prefix    string     `json:"prefix,omitempty"`
	UseSSL    bool       `json:"useSSL"`
	AccessKey secret.Ref `json:"accessKey"`
	SecretKey secret.Ref `json:"secretKey"`
}

PayloadS3 configures the object-store backend. AccessKey/SecretKey are secret.Refs (kind env or stored), resolved at sink-build time.

type ProxyMode

type ProxyMode struct {
	// Enabled turns the proxy flow on. When false, requests without a
	// valid relay key get 401, regardless of any other field below.
	Enabled bool `json:"enabled"`

	// AllowUnauthenticated lets proxy callers reach the upstream with
	// no relay-side auth — the inbound Authorization header is forwarded
	// as-is. When false, the proxy flow still requires a relay key
	// (proxied traffic gets logged against that key) but the upstream
	// credential comes from the caller.
	AllowUnauthenticated bool `json:"allowUnauthenticated"`
}

ProxyMode controls whether the relay accepts inference requests where the caller brings their own upstream provider key instead of a relay key. The flow lives in app/proxy (not yet built); this section gates the dispatch at the inference handler.

func (*ProxyMode) Validate

func (p *ProxyMode) Validate() error

type Reader

type Reader interface {
	Setting(section string) (any, bool)
}

Reader is the narrow read surface Governs needs — satisfied by *catalog.Catalog. Settings cache reads are lock-free and total (a registered section always returns at least its Defaults).

type Row

type Row struct {
	Section   string
	Value     any
	UpdatedAt time.Time
}

Row is a single settings row, decoded into a typed value via its section's Decode func. UpdatedAt is informational.

type Section

type Section struct {
	Name        string
	Description string
	Defaults    func() any
	Decode      func([]byte) (any, error)
	SchemaRef   string
}

Section describes one known settings section. Defaults returns a fresh zero-value instance; Decode parses raw JSON into a typed value and validates it; SchemaRef is the OpenAPI component name of the typed value's schema (set automatically by the per-section registerSettingsSection helper); Description is operator-facing prose explaining the section's purpose.

func Lookup

func Lookup(name string) (Section, bool)

Lookup returns the registered section by name. ok=false means the caller asked for an unknown section — handler returns 404.

type SectionName

type SectionName string

SectionName is a typed string whose OpenAPI schema is a string enum of every registered section. Use it in handler request/response types instead of bare `string` so the generated spec carries the closed set of valid section keys.

func (SectionName) Schema

func (SectionName) Schema(_ huma.Registry) *huma.Schema

Schema implements huma.SchemaProvider — huma calls this when registering any type that embeds SectionName, snapshotting the current registry into the OpenAPI components.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store is the data-access layer for settings rows.

func NewStore

func NewStore(q *gen.Queries) *Store

func (*Store) Get

func (s *Store) Get(ctx context.Context, section string) (*Row, error)

Get returns the typed value for section. When no row exists, the section's Defaults() is returned with UpdatedAt zero — so the read path is total: every registered section always has a value.

func (*Store) List

func (s *Store) List(ctx context.Context) ([]*Row, error)

List returns one Row per registered section, falling back to Defaults for sections without a DB row.

func (*Store) Upsert

func (s *Store) Upsert(ctx context.Context, section string, raw json.RawMessage) (*Row, error)

Upsert writes value as the typed section. The Decode round-trip validates input before any DB write.

type UsageClickHouse

type UsageClickHouse struct {
	RetentionDays int    `json:"retentionDays,omitempty"`
	WALDir        string `json:"walDir,omitempty"`
}

UsageClickHouse holds the safe-to-hot-swap CH knobs. The DSN reuses the boot CH connection (bootstrap-tier), so no credentials live in this row.

type UsageFile

type UsageFile struct {
	Path string `json:"path,omitempty"`
}

UsageFile configures the JSONL file backend.

type UsageLogging

type UsageLogging struct {
	// Backend selects the sink+reader: "file" (default), "clickhouse",
	// "postgres", or "valkey".
	Backend string `json:"backend"`

	File UsageFile       `json:"file"`
	CH   UsageClickHouse `json:"clickhouse"`
}

UsageLogging selects and tunes the log/usage event backend. Per the config direction (minimize env), the backend choice lives here, not in env; the legacy RELAY_EVENTLOG_BACKEND is honored only as an interim fallback when this section is unset, until the YAML→DB settings seed lands.

func (*UsageLogging) Validate

func (u *UsageLogging) Validate() error

Validate enforces the backend enum + non-negative knobs.

Jump to

Keyboard shortcuts

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