policy

package
v0.8.0 Latest Latest
Warning

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

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

Documentation

Overview

Package policy is the domain layer for the Policy entity — the named group that bundles Models (m:n), ProviderKeys (m:n), and an optional single RateLimit (m:1, the one rule set that applies to traffic through this Policy).

Wire format: Policy YAML/JSON carries entity *names*; the admin and seed boundaries resolve names to ids.

Go domain shape: Policy.Spec carries id arrays / scalar id. Callers read the lists directly without dealing with joins.

PG storage: arrays land in junction tables (policy_models, policy_provider_keys) with proper FKs; the single RateLimit ref is a column on the policies table. Policy's store.go fans the writes out across the junctions inside a transaction and rebuilds the arrays on read. (Migration + sqlc queries land as a follow-up; current store.go keeps Spec in JSONB until then, see TODO in store.go.)

Reverse direction: RelayKey carries a single Spec.PolicyID (m:1). Provider names its default via Spec.DefaultPolicyID. Models and ProviderKeys carry no policy reference — they are discovered via the junctions / via Policy.Spec.

service.go: runtime orchestrator. Picks a key, resolves the applicable RL per (provider, model, host), reserves inbound + upstream buckets, and rolls them back on failure. One Service per process.

store.go is the data-access layer for Policy. Spec.ModelIDs, Spec.HostKeyIDs, and Spec.RateLimitID live in their dedicated columns / junction tables (migration 0009) — not in the JSONB spec column. Upsert fans out across `policies`, `policy_models`, and `policy_host_keys` inside a single transaction so callers see a consistent view.

Index

Constants

View Source
const (
	KeySelectionPrioritized       = keypool.KeySelectionPrioritized
	KeySelectionRoundRobin        = keypool.KeySelectionRoundRobin
	KeySelectionLeastRecentlyUsed = keypool.KeySelectionLeastRecentlyUsed
)

Re-exported constants for source compatibility.

Variables

View Source
var ErrSaturated = errors.New("policy: upstream key saturated")

ErrSaturated: chosen key is over its upstream cap. Service has already recorded the failure; caller appends Key to Excluded and retries.

Functions

This section is empty.

Types

type AcquireInput

type AcquireInput struct {
	Policy   *Policy
	Keys     []*hostkey.HostKey
	Excluded []*hostkey.HostKey
	Model    *model.Model
	Host     *host.Host
	Provider string
}

type Acquisition

type Acquisition struct {
	Key         *hostkey.HostKey
	Reservation *pkgratelimit.Reservation
}

Acquisition carries the chosen key + its upstream reservation (nil when the tier policy has no applicable RL).

func (*Acquisition) KeyHash

func (a *Acquisition) KeyHash() string

type KeySelection

type KeySelection = keypool.KeySelection

KeySelection is an alias for keypool.KeySelection so existing callers can keep writing policy.KeySelection without an import change. The canonical definition lives in app/keypool.

type Policy

type Policy struct {
	Meta meta.Metadata `json:"metadata" yaml:"metadata"`
	Spec Spec          `json:"spec"     yaml:"spec"`
}

Policy is the routing/auth bundle. Composition layer enforces that every id in the lists below resolves and (for ProviderKeys) shares the Provider of the Models in the same Policy.

func (*Policy) EffectiveKeySelection

func (p *Policy) EffectiveKeySelection() keypool.KeySelection

EffectiveKeySelection returns KeySelection or the prioritized default.

func (*Policy) IsEnabled

func (p *Policy) IsEnabled() bool

IsEnabled returns true when Enabled is unset or explicitly true.

func (*Policy) ResolveRules

func (p *Policy) ResolveRules(rl *ratelimit.RateLimit) []pkgratelimit.Rule

ResolveRules converts the given RateLimit into the limiter's Rule shape, scoped by this policy's slug. Returns nil when rl is nil, disabled, or has no Rules. This is the runtime equivalent of the old ratelimit.Resolve(pol, rl) function — moved here so app/ratelimit stays free of policy imports.

func (*Policy) SelectRateLimitID

func (p *Policy) SelectRateLimitID(providerSlug, modelSlug, hostSlug string) string

SelectRateLimitID returns the id of the RateLimit that applies to one request triple, or "" when the policy doesn't cap this request.

Resolution: when Spec.RLBindings is non-empty, the first binding whose Models matches the (provider, model, host) triple wins. Otherwise the flat Spec.RateLimitID is used. A request that matches no binding and has no flat fallback is uncapped at this policy.

Pure query — no snapshot lookup, no I/O. Caller resolves the id to a *ratelimit.RateLimit via the snapshot.

func (*Policy) Validate

func (p *Policy) Validate() error

Validate runs intra-row rules via the shared meta.Validator and enforces:

  • Owner.Kind is user or system.
  • ModelIDs / HostKeyIDs have no within-list duplicates.

Cross-entity checks (every id resolves; ProviderKeys + Models share a Provider; RateLimitID resolves; auth-required Providers have at least one resolvable ProviderKey) live in the composition layer.

type RLBinding

type RLBinding struct {
	Models      []string `json:"models"      yaml:"models"      validate:"required,min=1,dive,min=1"`
	RateLimitID string   `json:"rateLimitId" yaml:"rateLimitId" validate:"required,uuid"`
}

RLBinding maps a set of Models to one RateLimit. The Models list uses the modelref DSL (same grammar as Policy.Spec.Models): a single binding can name explicit models, a provider/host wildcard, etc. Resolution scans bindings in declared order and the first match wins; authors should keep model sets disjoint within a policy.

type Service

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

func NewService

func NewService(snap SnapshotReader, sel *keypool.Selector, lim *pkgratelimit.Limiter) *Service

func (*Service) Acquire

func (s *Service) Acquire(ctx context.Context, in AcquireInput) (*Acquisition, error)

Acquire picks one key (excluding in.Excluded) and reserves its upstream bucket. On ExceededError, records FailureRateLimitShort and returns ErrSaturated (with Acquisition.Key set for logging).

func (*Service) Commit

func (s *Service) Commit(ctx context.Context, acq *Acquisition, obs pkgratelimit.Observations) error

Commit returns the upstream reservation to the bucket.

func (*Service) CommitInbound

func (s *Service) CommitInbound(ctx context.Context, res *pkgratelimit.Reservation, obs pkgratelimit.Observations) error

CommitInbound returns the inbound reservation to the bucket with the observed usage. Safe to call with res=nil.

func (*Service) RecordSuccess

func (s *Service) RecordSuccess(ctx context.Context, acq *Acquisition)

func (*Service) Release

func (s *Service) Release(ctx context.Context, acq *Acquisition, kind keypool.FailureKind, retryAfter time.Duration)

Release rolls back the upstream reservation (zero observations) and records the key failure.

func (*Service) ReserveInbound

func (s *Service) ReserveInbound(ctx context.Context, pol *Policy, providerSlug, modelSlug, hostSlug string) (*pkgratelimit.Reservation, error)

ReserveInbound reserves the inbound policy's RL bucket for this request. Returns (nil, nil) when the policy has no applicable RL.

type SnapshotReader

type SnapshotReader interface {
	Policy(id string) (*Policy, bool)
	RateLimit(id string) (*ratelimit.RateLimit, bool)
}

SnapshotReader is the narrow catalog read surface Service needs. *appcatalog.Catalog implements it via a thin adapter.

type Spec

type Spec struct {
	// Models is the catalog grant — a list of ref strings parsed by
	// app/modelref. See its package doc for the grammar. Each entry can
	// match a single binding ("anthropic/claude-opus-4-7@bedrock"), all
	// hosts for a model ("anthropic/claude-opus-4-7" or trailing @*),
	// or every model under a provider ("anthropic" or "anthropic/*").
	//
	// Patterns expand against the live catalog at snapshot build time,
	// so a wildcard automatically includes models added later.
	//
	// **Empty Models AND empty ModelIDs is treated as an implicit
	// wildcard**: every model reachable through the policy's HostKeys is
	// allowed. The hostkey-coverage check at routing time is the real
	// authorization gate in that case; Models/ModelIDs, when set, narrow
	// that gate. Deprecated models are hidden from the implicit wildcard
	// unless Spec.IncludeDeprecated.
	Models []string `json:"models,omitempty" yaml:"models,omitempty" validate:"omitempty,dive,min=1"`

	// ModelIDs is the legacy literal-ID grant — exact Model UUIDs, no
	// wildcards. Coexists with Models during the transition; the
	// snapshot expands both into the same bindingsByPolicy index.
	// New grants should prefer Models. See Models above for the
	// empty-list = implicit-wildcard rule.
	ModelIDs []string `json:"modelIds,omitempty" yaml:"modelIds,omitempty" validate:"omitempty,dive,uuid"`

	// HostKeyIDs is the set of HostKeys this Policy can draw from
	// (m:n with ProviderKey). Order is significant for KeySelectionPrioritized.
	HostKeyIDs []string `json:"hostKeyIds,omitempty" yaml:"hostKeyIds,omitempty" validate:"omitempty,dive,uuid"`

	// RateLimitID is a single RateLimit applied uniformly to every model
	// the policy grants. Mutually exclusive with RLBindings — pick one or
	// the other (or neither for an uncapped policy). The flat form is the
	// common case for user-authored policies that don't differentiate
	// caps per model.
	RateLimitID string `json:"rateLimitId,omitempty" yaml:"rateLimitId,omitempty" validate:"omitempty,uuid"`

	// RLBindings declares per-model rate-limit mappings. Use this when
	// caps vary by the model being called (the typical host-tier case
	// where, e.g., gpt-4o has different TPM than gpt-4o-mini at the same
	// upstream tier). At request time the first binding whose Models
	// matches the requested model contributes its RateLimit's rules; a
	// model not covered by any binding is uncapped at this policy.
	//
	// Each binding must declare at least one entry in Models. Mutually
	// exclusive with RateLimitID — Validate rejects setting both.
	RLBindings []RLBinding `json:"rlBindings,omitempty" yaml:"rlBindings,omitempty" validate:"omitempty,dive"`

	// KeySelection is the algorithm used to pick a ProviderKey from the
	// healthy pool. Defaults to prioritized. The enum lives in app/keypool
	// (the consumer); policy re-exports the constants via type aliases
	// for backward source-compatibility.
	KeySelection keypool.KeySelection `` /* 130-byte string literal not displayed */

	// SkipDefaultLimits opts out of the implicit "every Policy targeting an
	// auth-required Provider must enforce at least requests + tokens" rule
	// performed by the composition layer.
	SkipDefaultLimits bool `json:"skipDefaultLimits,omitempty" yaml:"skipDefaultLimits,omitempty"`

	// IncludeDeprecated controls whether wildcard Models entries expand
	// to models whose Spec.Deprecation.Status is "deprecated" or
	// "sunset". Default false (deprecated models excluded). Literal
	// grants in Models always resolve regardless — if you explicitly
	// name a deprecated model by slug, you mean to grant it.
	IncludeDeprecated bool `json:"includeDeprecated,omitempty" yaml:"includeDeprecated,omitempty"`

	// Enabled defaults to true when nil.
	Enabled *bool `json:"enabled,omitempty" yaml:"enabled,omitempty"`

	// PayloadLoggingEnabled opts every request authorized by this policy
	// into full request/response body capture by the payloadlog observer.
	// Off by default. A RelayKey may also opt in independently; either
	// being set enables capture for the request.
	PayloadLoggingEnabled bool `json:"payloadLoggingEnabled,omitempty" yaml:"payloadLoggingEnabled,omitempty"`
}

Spec carries the membership lists, the single rate-limit, and the strategy knobs.

type Store

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

Store is the Policy data-access type. Holds a pool so Upsert can run a multi-table transaction; List uses the pool directly without one.

func NewStore

func NewStore(pool *pgxpool.Pool) *Store

NewStore constructs a Store bound to a pool.

func (*Store) Delete

func (s *Store) Delete(ctx context.Context, id string) error

Delete removes a Policy by id. Junction rows cascade via FK.

func (*Store) Get

func (s *Store) Get(ctx context.Context, id string) (*Policy, error)

Get returns the Policy with the given id, hydrating ModelIDs/HostKeyIDs/RateLimitID. Returns (nil, nil) if not found.

func (*Store) List

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

List returns every Policy row, hydrating ModelIDs/HostKeyIDs/RateLimitID from their relational sources.

func (*Store) Upsert

func (s *Store) Upsert(ctx context.Context, p *Policy) error

Upsert writes p across policies + junction tables in a single tx.

Jump to

Keyboard shortcuts

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