Documentation
¶
Overview ¶
Package governance is Harbor's policy middleware between the runtime and the LLM-edge chain. It owns identity-scoped enforcement of cost ceilings, per-call MaxTokens, and rate limits. The package composes OUTSIDE the retry wrapper — for the full chain order:
governance(retry(downgrade(corrections(safety(driver)))))
Governance ships LATENT at V1 (per the V1 scoping): the interface + math + events + persistence all ship and wire, but every enforcement path is operator-opt-in. With zero `Governance.IdentityTiers` configured (the loader's default), every `PreCall` permits and every `PostCall` is an accumulator update only — no ceilings fire, no events emit beyond the accumulator's own bookkeeping.
Concurrent reuse: one Subsystem instance is safe to share across N concurrent goroutines. Per-key state lives in a `sync.Map` of `*identityState`; each `identityState` uses atomic primitives for its counters (lock-free CAS for monetary float64 sums; atomic for integer counts). Per-run scope flows via `ctx`; no mutable state on the Subsystem struct itself crosses run boundaries beyond the keyed per-identity caches.
Package governance — registry.
Governance composes OUTSIDE the rest of the LLM-edge chain via the `llm.RegisterGovernanceWrapper` hook. The wrapper is INSTALLED here (from `init()`), but only FIRES when a registered factory has been supplied via `SetFactory`. The factory takes a `llm.ConfigSnapshot` + `llm.Deps` and returns the per-`llm.Open` Subsystem (or nil to leave governance latent for this caller).
Why a factory instead of a global singleton: each `llm.Open` may run with different `Config` (different tier maps, different StateStore for tests). the accumulator is a long-lived sibling of the LLMClient — operators build one per Open and dispose with the client.
The factory is installed by the production assembly (`internal/runtime/assemble.Assemble`,) whenever the operator config declares identity tiers, and cleared when it declares none. If unset when `llm.Open` runs, the governance hook is a no-op pass-through — preserving the latent default for callers (especially test code) that don't wire governance explicitly.
Index ¶
- Constants
- Variables
- func CacheLen(s Subsystem) int
- func ClearFactory()
- func ErrIs(err, target error) bool
- func RegisterFailoverDriver(name string, f FailoverDriverFactory)
- func SetFactory(f Factory)
- func Wrap(inner llm.LLMClient, sub Subsystem) llm.LLMClient
- func WrapWithFailover(inner llm.LLMClient, sub Subsystem, chain []ProviderRef, act KeyActivator, ...) (llm.LLMClient, error)
- type BudgetExceededPayload
- type Clock
- type Config
- type CostAccumulator
- func (a *CostAccumulator) CacheLen() int
- func (a *CostAccumulator) Close(_ context.Context) error
- func (a *CostAccumulator) PostCall(ctx context.Context, req llm.CompleteRequest, resp llm.CompleteResponse, ...) error
- func (a *CostAccumulator) PreCall(ctx context.Context, req llm.CompleteRequest) error
- func (a *CostAccumulator) Snapshot(ctx context.Context, q identity.Quadruple) (float64, map[string]float64, error)
- type CostReader
- type Factory
- type FailoverConfig
- type FailoverDriverFactory
- type FailoverPolicy
- type GovernanceFailoverPayload
- type GovernancePostureSetPayload
- type KeyActivator
- type KeyRotatedPayload
- type MaxTokensEnforcer
- type MaxTokensExceededPayload
- type PostureProvider
- type PostureReadAdminPayload
- type ProviderRef
- type RateLimitConfig
- type RateLimitedPayload
- type RateLimiter
- func (r *RateLimiter) CacheLen() int
- func (r *RateLimiter) Close(_ context.Context) error
- func (r *RateLimiter) PostCall(_ context.Context, _ llm.CompleteRequest, _ llm.CompleteResponse, _ error) error
- func (r *RateLimiter) PreCall(ctx context.Context, req llm.CompleteRequest) error
- func (r *RateLimiter) Snapshot(ctx context.Context, q identity.Quadruple) (map[string]int, error)
- type RealClock
- type SetPosturePolicy
- type SetPostureSpec
- type Snapshot
- type Subsystem
- type TenantOverridePolicy
- type TenantOverrideSpec
- type TenantOverridesSetPayload
- type TierConfig
- type TierResolver
- type TierSource
Constants ¶
const ( // EventTypeBudgetExceeded — emitted from `CostAccumulator.PreCall` // when the per-identity total cost meets or exceeds the configured // tier ceiling. EventTypeBudgetExceeded events.EventType = "governance.budget_exceeded" // EventTypeRateLimited — emitted from `RateLimiter.PreCall` when // the token bucket for the request's (identity, model) underflows // the requested drain. EventTypeRateLimited events.EventType = "governance.rate_limited" // EventTypeMaxTokensExceeded — emitted from // `MaxTokensEnforcer.PreCall` when `req.MaxTokens` exceeds the // resolved tier's cap. EventTypeMaxTokensExceeded events.EventType = "governance.maxtokens_exceeded" // EventTypePostureReadAdmin — Emitted when an // admin-scoped caller reads ANOTHER tenant's governance posture via // the `governance.posture` Protocol method. A caller reading its // OWN tenant does NOT emit this event (matches the // sessions.inspect convention — own-scope reads are not audited). // The cross-tenant read is a privileged action and lands on the // audit trail per CLAUDE.md §7 + RFC §6.15. EventTypePostureReadAdmin events.EventType = "governance.posture_read_admin" // EventTypeTenantOverridesSet — Emitted when an admin-scoped caller // sets (or clears) a tenant's default LLM-parameter overrides via the // `governance.set_tenant_overrides` Protocol method. The change is a // privileged, no-deploy reconfiguration of the tenant's default // model / temperature / max-tokens / reasoning-effort / extra // instructions and lands on the audit trail (CLAUDE.md §7, RFC §6.15 // — the `ModelOverride` governance seam). The payload carries only // which dimensions were set (booleans) plus the model name — never // the extra-instructions text (operator-supplied prose runs through // the redactor and is not echoed onto the audit event). EventTypeTenantOverridesSet events.EventType = "governance.tenant_overrides_set" // EventTypeKeyRotated — Emitted when an admin-scoped caller rotates an // LLM provider API key via the `governance.rotate_key` Protocol // method. The change is a privileged, no-deploy credential swap and // lands on the audit trail (CLAUDE.md §7, RFC §6.15 — the key-rotation // seam). The payload carries the provider + a NON-REVERSIBLE // fingerprint of the new key + actor + timestamp — NEVER the key value // (CLAUDE.md §7: secrets are never logged or persisted to the bus). EventTypeKeyRotated events.EventType = "governance.key_rotated" // EventTypePostureSet — Emitted when an admin-scoped caller writes the // identity-tier policy table via the `governance.set_posture` Protocol // method. The change is a privileged, no-deploy full-replace of the // per-tier budget / rate-limit / max-tokens policy plus the default-tier // assignment and lands on the audit trail (CLAUDE.md §7, RFC §6.15). The // payload carries only the non-secret tier SUMMARY (sorted tier names + // counts + default-tier before/after) — never the ceiling VALUES and // never a secret; the payload still runs through the wired bus's audit // Redactor before delivery (CLAUDE.md §7 rule 6). EventTypePostureSet events.EventType = "governance.posture_set" // EventTypeFailover — emitted per HOP when the Harbor-orchestrated // failover walk advances from one broker-pulled provider to the next on // a retryable provider error. Harbor walks the ordered chain at the // governance layer (the provider SDK's native fallback array is NOT // used), so every hop is a Harbor event through the audit Redactor + // event bus + the per-identity cost accumulator. SafePayload: it carries // the run identity, the from/to provider, the hop index, the accumulated // cost the re-run PreCall gates against, and a bounded non-secret // retryable-error CLASS — never the raw provider error string. EventTypeFailover events.EventType = "governance.failover" )
governance event types. Registered via init() so the canonical events registry stays the single source of truth (AGENTS.md §17.6's "wiring gap" lesson — register at declaration time, publish at use time).
All payloads are SafePayload (compose events.SafeSealed): identity, model, ceilings, and totals are operator-visible metadata, not secret-shaped. Cost values are USD floats — same wire shape as `llm.CostRecordedPayload`.
const DefaultFailoverDriver = "chain"
DefaultFailoverDriver is the name of the shipped chain-walk driver — the ordered broker-pulled chain, Harbor-orchestrated at the governance layer.
Variables ¶
var ( // ErrBudgetExceeded — PreCall blocked because the // (identity, tier) accumulator hit or exceeded the configured // BudgetCeilingUSD. Wraps with the identity + total + ceiling so // log handlers can render the failure deterministically. ErrBudgetExceeded = errors.New("governance: budget ceiling exceeded") // ErrRateLimited — PreCall blocked because the per- // (identity, model) token bucket underflowed the requested drain. ErrRateLimited = errors.New("governance: rate-limited") // ErrMaxTokensExceeded — PreCall blocked because the // request's MaxTokens exceeded the identity tier's cap. ErrMaxTokensExceeded = errors.New("governance: per-call MaxTokens exceeded") // ErrIdentityRequired — the request reached governance with a // missing or incomplete identity. AGENTS.md §6 rule 9 mandates a // fail-closed; governance enforces here. ErrIdentityRequired = errors.New("governance: identity required") // AGENTS.md §13 forbids silent permits on read failure; the // wrapper returns this error rather than fall through to a // "permit by default." ErrStateUnavailable = errors.New("governance: state store unavailable") // ErrClosed — the Subsystem (or its underlying StateStore) was // closed. Subsequent calls fail loud. ErrClosed = errors.New("governance: subsystem closed") // ErrInvalidConfig — operator config did not validate at // construction. Used by `NewCostAccumulator` / `NewRateLimiter` / // `NewMaxTokensEnforcer`. ErrInvalidConfig = errors.New("governance: invalid config") // ErrUnknownModel — an admin tried to set a tenant default model // that has no configured `ModelProfile`. Validation happens at set // time (fail loud) rather than deferring the failure to call time. ErrUnknownModel = errors.New("governance: unknown model (no configured ModelProfile)") // ErrInvalidOverride — a tenant override carried a structurally // invalid value (a temperature outside [0,2], a non-positive // max-tokens, an unknown reasoning-effort). Fails closed rather than // silently passing the value to the provider. ErrInvalidOverride = errors.New("governance: invalid tenant override") )
Sentinel errors. Callers compare via errors.Is.
Each sentinel maps 1:1 to a `governance.*` event type — code that catches one of these knows which policy fired and can route the outcome (e.g. ErrBudgetExceeded → pause/resume primitive in +).
var ( // ErrPolicyWidening — a `set_posture` write OMITS or zeroes an // enforced ceiling the current effective policy enforces. Fails closed // (never budget-widening, never a de-enforced tier) BEFORE any // StateStore mutation. ErrPolicyWidening = errors.New("governance: set_posture omits or zeroes an enforced ceiling (fail-closed; never budget-widening)") // ErrInvalidPosture — a `set_posture` policy failed structural // validation (a negative ceiling / capacity / max-tokens, or an empty // default tier when a tier is enforced). ErrInvalidPosture = errors.New("governance: set_posture policy failed validation") )
Sentinels callers compare with errors.Is.
var ErrFailoverExhausted = errors.New("governance: failover chain exhausted")
ErrFailoverExhausted — the failover walk advanced through every entry in the ordered chain and the terminal hop STILL returned a retryable provider error. Wraps the last hop's error so the operator sees why the chain ran out. A PERMANENT error (or a PreCall trip) stops the walk earlier with its own error, never this one. Callers compare via errors.Is.
Functions ¶
func CacheLen ¶ added in v1.5.1
CacheLen returns the total identity-scoped governance cache size for s — the sum across any enforcer that holds an identity-keyed cache (cost ceilings + rate-limit buckets). A Subsystem with no such cache (or a nil Subsystem — the latent no-enforcement default) returns 0. The runtime observability wiring calls this to source the harbor_runtime_governance_cache_entries gauge.
func ClearFactory ¶
func ClearFactory()
ClearFactory removes the registered factory. Restores the latent default behaviour. Concurrent-safe.
func ErrIs ¶
ErrIs reports whether `err` wraps `target` and one of governance's sentinels. Useful in callers that branch on policy outcome (e.g. the runtime can route an ErrBudgetExceeded to the unified pause/resume primitive while letting other errors propagate).
Returns false on nil err.
func RegisterFailoverDriver ¶ added in v1.17.0
func RegisterFailoverDriver(name string, f FailoverDriverFactory)
RegisterFailoverDriver installs a named FailoverPolicy factory. Called from init(); the write-once-read-many registry is the §4.4 seam pattern (the one sanctioned package-level mutable state — a driver registry, the concurrent-reuse contract's write-once-read-many carve-out).
func SetFactory ¶
func SetFactory(f Factory)
SetFactory installs the per-`llm.Open` factory. Calling SetFactory twice is allowed (the second call wins) — the production assembly (`assemble.Assemble`, the seam's first production caller) calls this once at boot when `Config.IdentityTiers` is non-empty; tests may swap it repeatedly. Concurrent-safe.
Multi-runtime limitation: the factory is PROCESS-GLOBAL. An embedder assembling two runtime stacks with different tier maps in one process would collide here — the second SetFactory wins for every subsequent `llm.Open`, and the same wipe exists on the CLOSE side: a tier-bearing stack's `Stack.Close` runs `ClearFactory()` unconditionally, so closing an older stack removes a newer sibling stack's installed factory for any `llm.Open` issued afterwards (already-wrapped clients keep their enforcement). The binary assembles exactly one stack, so the zero-ceremony seam stays; multi-runtime embedders skip SetFactory entirely and compose per stack instead:
sub, err := governance.NewSubsystemFromConfig(cfg, store, bus) client = governance.Wrap(llmClient, sub) // governance outermost
See `Wrap` and `NewSubsystemFromConfig` for the documented headless path.
func Wrap ¶
Wrap composes a `Subsystem` around an `llm.LLMClient`. The returned client invokes `sub.PreCall` then the inner client then `sub.PostCall` on every Complete. PreCall non-nil short-circuits and propagates the error directly to the caller; the inner client is NOT invoked.
PostCall's return is observability-only — a non-nil PostCall error is logged at Warn but does NOT replace the original `(resp, callErr)` outcome. This is RFC §6.15 line 1128's "PostCall errors do not supplant the call's result" behaviour.
Compose order: governance is the OUTERMOST wrapper, sitting outside the retry wrapper. The wrapper closures here read identity from ctx via `identityFromCtx`; missing identity → fail-closed with `ErrIdentityRequired`.
Headless / multi-runtime path: Wrap + `NewSubsystemFromConfig` are the documented composition for embedders that run N runtime stacks (each with its own tier map) in one process — the process-global `SetFactory` seam would collide there (second SetFactory wins; see its godoc). Build one Subsystem per stack and wrap each `llm.Open` result directly:
sub, err := governance.NewSubsystemFromConfig(cfg, store, bus) client = governance.Wrap(client, sub)
Concurrent reuse: the returned client is a thin functional adapter (no mutable state of its own); concurrent reuse depends on the inner client + Subsystem both honouring the contract.
func WrapWithFailover ¶ added in v1.17.0
func WrapWithFailover(inner llm.LLMClient, sub Subsystem, chain []ProviderRef, act KeyActivator, cost CostReader, bus events.EventBus) (llm.LLMClient, error)
WrapWithFailover composes governance around `inner` AND routes every Complete through a broker-pulled failover chain when one is configured. It is the CONSUMER of the FailoverPolicy seam: with a non-empty `chain` the returned client delegates the whole PreCall → issue → PostCall cycle (per hop) to a chain-walk FailoverPolicy built over the SAME inner client and Subsystem; with an EMPTY chain it is byte-for-byte the plain single-provider `Wrap` path (one path, chain length 0 == single provider — no two parallel implementations, CLAUDE.md §13).
`act` swaps the broker-pulled live key per hop; `cost` (optional) reads the accumulated per-identity cost for the hop event; `bus` publishes the governance.failover hop event. Nil inner / sub panic (composition error caught at boot), mirroring Wrap.
Types ¶
type BudgetExceededPayload ¶
type BudgetExceededPayload struct {
events.SafeSealed
Identity identity.Quadruple
Tier string
Model string
TotalCost float64
Ceiling float64
Currency string
OccurredAt time.Time
}
BudgetExceededPayload is the typed payload for EventTypeBudgetExceeded. SafePayload — identity + cost figures + tier name are operator-visible.
`TotalCost` is the per-identity sum at the time of the block (i.e. the pre-call running total; the rejected call did not contribute). `Ceiling` is the operator-configured cap at the time of the emit. `Model` carries the request's model identifier; `Tier` is the resolved tier name.
type Clock ¶
Clock is governance's time source. The standard `time` package implements this implicitly via the default `realClock` value; tests inject a fake clock with controllable Now().
type Config ¶
type Config struct {
// DefaultTier is the tier name applied to an identity that does
// not match a custom Resolver mapping. Empty = no default tier =
// no enforcement for unmatched identities (latent).
DefaultTier string
// IdentityTiers maps tier name → policy shape. A nil / empty map
// disables all enforcement (latent default).
IdentityTiers map[string]TierConfig
// Resolver maps identity → tier name. nil = "use DefaultTier for
// every identity." Operators with claim-based or session-attribute-
// based tier assignment wire their resolver here.
Resolver TierResolver
// Clock is the time source for bucket-refill math. nil =
// `time.Now` (the production default). Tests inject a fake clock.
Clock Clock
// contains filtered or unexported fields
}
Config is the operator-supplied governance shape. Zero-value = latent (no enforcement). Operators populate `IdentityTiers` + `DefaultTier` (or a custom `Resolver`) to switch on per-policy enforcement.
func ConfigFromOperator ¶ added in v1.3.0
func ConfigFromOperator(in config.GovernanceConfig) Config
ConfigFromOperator projects the operator-supplied `config.GovernanceConfig` onto the `governance.Config` shape. Only the declarative policy fields are projected — `DefaultTier` + `IdentityTiers` (each tier's `BudgetCeilingUSD` / `RateLimit` / `MaxTokens`). The runtime-injection fields (`Resolver` / `Clock`) stay zero by design: they are Go-level seams (a custom TierResolver, a test clock) with no YAML surface; callers wire them after projection when needed. The read-only posture surface consumes the projection as-is (a nil Resolver is correct — every caller's `ResolvedTier` falls back to `DefaultTier`).
`config.GovernanceConfig.RepairAttempts` is NOT part of this projection: it is the LLM repair-loop knob (consumed by the retry surface), not a tier-policy field — `governance.Config` carries no equivalent. The field-parity test names this exclusion.
An empty `IdentityTiers` in the YAML (the latent default) yields a `governance.Config` with an empty tier map — the posture surface reports it verbatim and enforcement stays latent.
func WithTierSource ¶ added in v1.17.0
func WithTierSource(cfg Config, src *TierSource) Config
WithTierSource returns a copy of the Config bound to the hot-swappable TierSource. The enforcement subsystem reads the tier VALUES + the effective DefaultTier through the source, so an admin `set_posture` write (which swaps the source) takes effect on the next PreCall. The embedded `IdentityTiers` / `DefaultTier` are also set to the source's current snapshot so the latent-default decision (`len(IdentityTiers) == 0`) and any config-only reader see the effective policy at construction time.
type CostAccumulator ¶
type CostAccumulator struct {
// contains filtered or unexported fields
}
CostAccumulator is the `Subsystem` that aggregates LLM cost per `(tenant, user, session, model)` and enforces per-tier ceilings in PreCall. State persists to a `state.StateStore` so the accumulator survives runtime restart; three V1 drivers (in-mem / SQLite / Postgres) pass identical conformance tests.
Latent default: an empty `Config.IdentityTiers` map disables every enforcement path. PreCall returns nil; PostCall still records cost so the accumulator's observability surface stays alive (operators get per-identity cost dashboards without opting into enforcement). This satisfies the V1 scoping: "interface + math only, ceilings opt-in."
Concurrent reuse: the accumulator's per-key state lives in a `sync.Map` of `*costKeyState`; each `costKeyState` uses an atomic `uint64`-packed float64 for the cumulative total. The CAS loop in `add` is lock-free and N concurrent PostCalls cannot lose updates. PreCall reads the atomic snapshot — the race window between PreCall (read) and the next call's PostCall (write) is bounded by `in_flight × per_call_cost`; this is documented in the phase plan.
func NewCostAccumulator ¶
func NewCostAccumulator(state state.StateStore, bus events.EventBus, cfg Config) (*CostAccumulator, error)
NewCostAccumulator constructs a `CostAccumulator`. `state` is the persistence floor (mandatory); `bus` is the observability bus (mandatory); `cfg` carries operator-supplied tier ceilings.
Validation: nil state or nil bus → `ErrInvalidConfig`. Config itself may have an empty IdentityTiers map — that's the latent default.
func (*CostAccumulator) CacheLen ¶ added in v1.5.1
func (a *CostAccumulator) CacheLen() int
CacheLen returns the number of distinct identity-scoped cache keys the accumulator currently holds. Identity-scoped keying (RFC §6.15) means this counts identities, not runs, so the value is bounded by the active-identity set — a safe, low-cardinality reading for the runtime governance-cache gauge. It is a lock-safe Range count over the internally-synchronised sync.Map; no per-run state is read.
func (*CostAccumulator) Close ¶
func (a *CostAccumulator) Close(_ context.Context) error
Close marks the accumulator closed; subsequent PreCall / PostCall / Snapshot calls fail loud with `ErrClosed`. Idempotent.
func (*CostAccumulator) PostCall ¶
func (a *CostAccumulator) PostCall(ctx context.Context, req llm.CompleteRequest, resp llm.CompleteResponse, _ error) error
PostCall accumulates `resp.Cost.TotalCost` plus any intermediate provider-attempt spend the retry / downgrade wrappers reported into the per-call attempt-cost tap, regardless of `callErr`. Failures still incur whatever cost the provider reported (some report 0 on failure; that's fine — the accumulator records 0), and attempt spend accumulates even when the outer call ultimately errors (retry / downgrade exhaustion — spend is spend).
The accumulator path is in-band synchronous (RFC §6.15 line 1128) rather than event-subscriber: the next PreCall sees the latest total without a bus-delivery race. The attempt-cost tap extends that in-band posture one level deeper — intermediate attempts are folded here, not re-accumulated off a bus subscription.
`governance.budget_exceeded` events are NOT emitted from PostCall; they fire only from PreCall on the NEXT call that exceeds. A PostCall that pushes the accumulator over the ceiling is accepted (the call already happened); the operator sees the breach via the cost-recorded observability stream that bifrost already publishes.
func (*CostAccumulator) PreCall ¶
func (a *CostAccumulator) PreCall(ctx context.Context, req llm.CompleteRequest) error
PreCall checks the cost ceiling for the request's identity. With no ceiling configured (latent default) the function returns nil unconditionally. On state-read failure → wrapped `ErrStateUnavailable` (no silent permit per AGENTS.md §13).
func (*CostAccumulator) Snapshot ¶
func (a *CostAccumulator) Snapshot(ctx context.Context, q identity.Quadruple) (float64, map[string]float64, error)
Snapshot returns the current per-(identity, model) accumulator total + the identity-level grand total. Used by tests and by the Console-driven inspection (post-V1). Concurrent-safe.
type CostReader ¶ added in v1.17.0
type CostReader interface {
Snapshot(ctx context.Context, q identity.Quadruple) (float64, map[string]float64, error)
}
CostReader reads the accumulated per-identity cost so the governance.failover hop event can carry the running total the re-run PreCall gates against. The CostAccumulator satisfies it; a nil reader yields a zero AccumCostUSD (the event still emits — the ceiling check itself runs inside PreCall, not off this read).
type Factory ¶
Factory builds a `Subsystem` from the same `(cfg, deps)` pair that `llm.Open` consumes. Returning a nil Subsystem disables governance for this Open invocation (the wrapper passes through unchanged) — useful for tests that share a single `cmd/harbor` blank-import with production code but don't want governance to fire.
type FailoverConfig ¶ added in v1.17.0
type FailoverConfig struct {
// Inner is the one-method LLMClient re-issued on every hop (the same
// client the plain single-provider path uses — the walk is mechanism
// ABOVE the client, never a new client method).
Inner llm.LLMClient
// Governance is the enforcement Subsystem whose PreCall re-gates every
// hop (budget / rate-limit / MaxTokens) and whose PostCall accounts each
// hop's cost so the NEXT PreCall sees the accumulated total.
Governance Subsystem
// Activator swaps the broker-pulled live key to a hop's provider binding.
Activator KeyActivator
// Cost reads the accumulated per-identity cost for the hop event. Optional.
Cost CostReader
// Bus publishes the governance.failover hop event. Mandatory.
Bus events.EventBus
// Clock is the time source for the hop event's OccurredAt. Nil = DefaultClock.
Clock Clock
// Logger receives PostCall observability warnings. Nil = slog.Default().
Logger *slog.Logger
}
FailoverConfig is the constructor input for the chain-walk FailoverPolicy. Inner, Governance, Activator and Bus are mandatory; Cost and Logger are optional.
type FailoverDriverFactory ¶ added in v1.17.0
type FailoverDriverFactory func(cfg FailoverConfig) (FailoverPolicy, error)
FailoverDriverFactory builds a FailoverPolicy from a FailoverConfig. The §4.4 seam records driver name → factory so an alternate walk strategy (a future health-aware variant) can register without touching callers.
type FailoverPolicy ¶ added in v1.17.0
type FailoverPolicy interface {
// Complete issues the request against the primary provider, then walks
// the ordered chain on a retryable error. Before EACH re-issue it
// re-runs PreCall for the run's identity; a hop that trips PreCall
// returns the budget / rate sentinel and STOPS the walk (no silent
// continue). Each hop emits governance.failover. Returns the first
// successful response, or the terminal error on chain exhaustion
// (ErrFailoverExhausted) / a permanent error (returned verbatim).
Complete(ctx context.Context, ident identity.Identity, req llm.CompleteRequest, chain []ProviderRef) (llm.CompleteResponse, error)
}
FailoverPolicy walks an ordered chain of broker-pulled provider descriptors on a retryable provider error, re-running PreCall before each re-issue and emitting a governance.failover hop event. It realizes the RFC §6.15 failover seam — Harbor orchestrates the walk at the governance layer: the provider SDK's native fallback array is deliberately NOT used, so every hop is a Harbor event through audit + bus + the per-identity cost accumulator, and a cross-provider chain stays fully expressible.
Immutable after construction; safe to share across N goroutines. Per-run state (hop index, per-hop response) lives on the call stack / ctx, never on the policy (the concurrent-reuse contract).
func NewFailoverPolicy ¶ added in v1.17.0
func NewFailoverPolicy(cfg FailoverConfig) (FailoverPolicy, error)
NewFailoverPolicy constructs the chain-walk FailoverPolicy. It validates the mandatory collaborators loud (a nil Inner / Governance / Activator / Bus is a construction bug, not a runtime degradation) and returns the shared, immutable policy.
func NewFailoverPolicyByName ¶ added in v1.17.0
func NewFailoverPolicyByName(name string, cfg FailoverConfig) (FailoverPolicy, error)
NewFailoverPolicyByName builds the named FailoverPolicy driver. An unknown name fails loud naming the registered drivers so a misconfiguration is obvious (§4.4).
type GovernanceFailoverPayload ¶ added in v1.17.0
type GovernanceFailoverPayload struct {
events.SafeSealed
Identity identity.Quadruple
FromProvider string
ToProvider string
HopIndex int
AccumCostUSD float64
Reason string
}
GovernanceFailoverPayload is the typed payload for EventTypeFailover. SafePayload — the run identity, the from/to provider names, the hop index, the accumulated cost, and the bounded retryable-error class are all operator-visible metadata, not secret-shaped. The raw provider error string is deliberately NOT carried (it may hold provider-specific detail); the payload still runs through the wired bus's audit Redactor before delivery (CLAUDE.md §7 rule 6).
`AccumCostUSD` is the per-identity running total at the moment of the advance — the same figure the re-run PreCall gates against, so an operator can see WHY a later hop tripped the ceiling. `HopIndex` is 1-based: hop 1 is the first fallback entry (the primary provider is hop 0 and emits no failover event).
type GovernancePostureSetPayload ¶ added in v1.17.0
type GovernancePostureSetPayload struct {
events.SafeSealed
// Actor is the admin-scoped caller that performed the write.
Actor identity.Quadruple
// DefaultTierBefore / DefaultTierAfter report the default-tier
// assignment before and after the full-replace write.
DefaultTierBefore string
DefaultTierAfter string
// TierCountBefore / TierCountAfter report the number of tiers in the
// effective policy before and after the write.
TierCountBefore int
TierCountAfter int
// TiersBefore / TiersAfter are the sorted tier names before and after
// the write. Non-secret metadata (tier names only, never ceilings).
TiersBefore []string
TiersAfter []string
// OccurredAt is the wall-clock time of the write.
OccurredAt time.Time
}
GovernancePostureSetPayload is the typed payload for EventTypePostureSet. SafePayload — the actor identity, the default-tier before/after, the tier counts, and the sorted tier NAMES are operator-visible audit metadata. The per-tier ceiling VALUES are never carried here (only the names + counts), so no policy figure leaks onto the bus; the payload still runs through the audit Redactor before publish (CLAUDE.md §7 rule 6).
type KeyActivator ¶ added in v1.17.0
type KeyActivator interface {
// Activate installs ref's broker-pulled key as the live key. A non-nil
// return fails the hop loud (the walk stops); it never silently serves a
// prior provider's key.
Activate(ctx context.Context, ref ProviderRef) error
}
KeyActivator makes a ProviderRef's broker-pulled key the live key the one-method LLMClient reads on its next Complete. It is the seam between the governance-orchestrated failover walk and the inference broker-pull credential source: production wires it to swap the runtime's atomic LiveKey to the ref's binding; a fail-loud activation (broker unreachable, binding unknown) stops the walk rather than re-issuing against a stale or empty key.
A KeyActivator is a compiled artifact shared across N goroutines; it holds no per-run state.
type KeyRotatedPayload ¶ added in v1.5.0
type KeyRotatedPayload struct {
events.SafeSealed
// Actor is the admin-scoped caller that performed the rotation.
Actor identity.Quadruple
// Provider is the provider whose key was rotated.
Provider string
// Fingerprint is the non-reversible digest of the NEW key
// (`sha256:<prefix>`). Never the key itself.
Fingerprint string
// OccurredAt is the wall-clock time of the rotation.
OccurredAt time.Time
}
KeyRotatedPayload is the typed payload for EventTypeKeyRotated. SafePayload — the actor identity, the provider, and the NON-REVERSIBLE key fingerprint are operator-visible audit metadata. The key VALUE is NEVER carried here (CLAUDE.md §7: secrets never reach the bus); only the `sha256:<prefix>` fingerprint is, so an operator can correlate a rotation without the secret ever leaving the runtime's key holder. The payload still runs through the audit Redactor before publish.
type MaxTokensEnforcer ¶
type MaxTokensEnforcer struct {
// contains filtered or unexported fields
}
MaxTokensEnforcer is the Subsystem that gates `req.MaxTokens` against the resolved identity tier's cap. Stateless — no StateStore dependency. Fail-loud per master plan line 420 + RFC §6.15 line 1122.
Latent default: with `TierConfig.MaxTokens == 0` for the resolved tier, PreCall is a permit no-op. Same with an unresolved tier.
Concurrency: the enforcer holds no mutable state beyond `Config`; the concurrent-reuse contract is trivially satisfied. Operators may share one enforcer across the entire runtime.
func NewMaxTokensEnforcer ¶
func NewMaxTokensEnforcer(bus events.EventBus, cfg Config) *MaxTokensEnforcer
NewMaxTokensEnforcer constructs the enforcer. The bus is required for `governance.maxtokens_exceeded` emit. Nil bus rejected.
func (*MaxTokensEnforcer) Close ¶
func (e *MaxTokensEnforcer) Close() error
Close marks the enforcer closed.
func (*MaxTokensEnforcer) PostCall ¶
func (e *MaxTokensEnforcer) PostCall(_ context.Context, _ llm.CompleteRequest, _ llm.CompleteResponse, _ error) error
PostCall is a no-op. MaxTokens enforcement is a pre-call gate only.
func (*MaxTokensEnforcer) PreCall ¶
func (e *MaxTokensEnforcer) PreCall(ctx context.Context, req llm.CompleteRequest) error
PreCall checks `req.MaxTokens` against the tier cap. Permits if either is unset / zero.
type MaxTokensExceededPayload ¶
type MaxTokensExceededPayload struct {
events.SafeSealed
Identity identity.Quadruple
Tier string
Model string
Requested int
Cap int
OccurredAt time.Time
}
MaxTokensExceededPayload is the typed payload for EventTypeMaxTokensExceeded. `Requested` is the value on `CompleteRequest.MaxTokens`; `Cap` is the tier's configured ceiling.
type PostureProvider ¶
type PostureProvider struct {
// contains filtered or unexported fields
}
PostureProvider is the read-only accessor over the effective governance identity-tier policy — the StateStore-backed policy record (written via `governance.set_posture`) layered over the operator-configured `governance.Config` defaults. Built once per Runtime process via NewPostureProvider / NewPostureProviderWithState; `Posture` is safe for concurrent use by N goroutines.
func NewPostureProvider ¶
func NewPostureProvider(cfg Config) *PostureProvider
NewPostureProvider builds a PostureProvider over the operator-supplied governance configuration WITHOUT a StateStore layer — `Posture` returns exactly the config defaults. A latent (zero-value) Config is valid — `Posture` returns an empty `IdentityTiers` map and empty tier selectors, which the Console renders as the explicit "No tiers configured" state.
func NewPostureProviderWithState ¶ added in v1.17.0
func NewPostureProviderWithState(cfg Config, st state.StateStore) *PostureProvider
NewPostureProviderWithState builds a PostureProvider whose `Posture` reflects the StateStore-backed effective policy layered over the config defaults: when a `governance.set_posture` record has been written, the provider returns that record's tiers + default tier; otherwise it returns the config-declared defaults (additive / backward-compatible). `st` is the StateStore the runtime persists the policy record through.
func (*PostureProvider) Posture ¶
func (p *PostureProvider) Posture(ctx context.Context) (Snapshot, error)
Posture returns a deep-copied Snapshot of the configured governance posture for the caller's identity. Identity is mandatory (CLAUDE.md §6 rule 9, RFC §5.5): a missing / incomplete identity in `ctx` fails loudly with wrapped `ErrIdentityRequired` — there is no opt-out.
The returned `IdentityTiers` map is a fresh deep copy; the `ResolvedTier` is computed by applying the Config's `TierResolver` (falling back to `DefaultTier`) to the caller's identity. A nil resolver makes `ResolvedTier == DefaultTier` for every caller.
type PostureReadAdminPayload ¶
type PostureReadAdminPayload struct {
events.SafeSealed
// Actor is the identity of the admin-scoped caller that performed
// the cross-tenant read.
Actor identity.Quadruple
// RequestedTenant is the tenant_id the caller asked to read — a
// tenant other than the caller's own.
RequestedTenant string
// OccurredAt is the wall-clock time of the cross-tenant read.
OccurredAt time.Time
}
PostureReadAdminPayload is the typed payload for EventTypePostureReadAdmin. SafePayload — the actor's identity and the requested tenant are operator-visible audit metadata, not secret-shaped. The payload still runs through the audit Redactor before the bus publish (CLAUDE.md §7 rule 6).
type ProviderRef ¶ added in v1.17.0
type ProviderRef struct {
// Provider is the LLM provider the entry authenticates against (e.g.
// "openai", "anthropic"). Entries may name DIFFERENT providers.
Provider string
// Name is the installed provider-binding name resolved by the
// KeyActivator (bare-name resolution). It references a zero-URL,
// broker-pulled binding — never a credential-sink lever on the wire.
Name string
}
ProviderRef names one entry in the ordered failover chain — a broker-pulled, zero-URL/zero-secret descriptor. The fallback key is pulled via the inference broker-pull source and never persisted; advancing a hop activates the ref's live key and re-issues through the one-method LLMClient. Provider MAY differ across entries so a cross-provider (heterogeneous) chain is expressible.
type RateLimitConfig ¶
RateLimitConfig is the token-bucket shape. Capacity is the bucket ceiling (max tokens reservable). RefillTokens fill the bucket every RefillInterval. A zero Capacity disables the rate limit even if other fields are set.
func (RateLimitConfig) IsEnabled ¶
func (r RateLimitConfig) IsEnabled() bool
IsEnabled reports whether the RateLimitConfig has the minimum fields set to drive enforcement (Capacity > 0; refill knobs may be zero for non-refilling buckets, but Capacity is the gate).
func (RateLimitConfig) IsZero ¶
func (r RateLimitConfig) IsZero() bool
IsZero reports whether the RateLimitConfig is fully zero-valued (no rate-limit enforcement configured).
type RateLimitedPayload ¶
type RateLimitedPayload struct {
events.SafeSealed
Identity identity.Quadruple
Tier string
Model string
Requested int
Available int
Capacity int
RefillTokens int
RefillEvery time.Duration
OccurredAt time.Time
}
RateLimitedPayload is the typed payload for EventTypeRateLimited. `Requested` is the number of tokens the drain attempted to remove; `Available` is the bucket's level at the moment of the block.
type RateLimiter ¶
type RateLimiter struct {
// contains filtered or unexported fields
}
RateLimiter is the `Subsystem` implementing a token bucket per `(identity, model)`. Bucket state lives in `state.StateStore` so it survives restart; latent default: zero `TierConfig.RateLimit` → no enforcement.
Time math (Clock):
- `Capacity` is the bucket ceiling.
- `RefillTokens` are added every `RefillInterval` (continuous accrual via `floor(elapsed / RefillInterval) * RefillTokens`).
- `expected_tokens` per call defaults to `req.MaxTokens` if set, else 1. consciously does NOT pre-charge `MaxTokens` for unbounded requests — the operator who needs that sets a tier `MaxTokens` cap (the other policy).
Concurrency model: per-key state is a `sync.Map`; each `bucketKeyState` has its own mutex for drain serialisation. The fan-out at scale is per-(identity, model), which is naturally sharded — N concurrent calls on different keys never contend.
func NewRateLimiter ¶
func NewRateLimiter(s state.StateStore, bus events.EventBus, cfg Config) (*RateLimiter, error)
NewRateLimiter constructs a RateLimiter. Validates deps.
func (*RateLimiter) CacheLen ¶ added in v1.5.1
func (r *RateLimiter) CacheLen() int
CacheLen returns the number of distinct identity-scoped cache keys the limiter currently holds. Identity-scoped keying (RFC §6.15) means this counts identities, not runs, so the value is bounded by the active-identity set — a safe, low-cardinality reading for the runtime governance-cache gauge. It is a lock-safe Range count over the internally-synchronised sync.Map; no per-run state is read.
func (*RateLimiter) Close ¶
func (r *RateLimiter) Close(_ context.Context) error
Close marks the limiter closed; subsequent calls fail loud.
func (*RateLimiter) PostCall ¶
func (r *RateLimiter) PostCall(_ context.Context, _ llm.CompleteRequest, _ llm.CompleteResponse, _ error) error
PostCall is a no-op for the rate limiter — drain happens entirely in PreCall. Per RFC §6.15 simplicity, there are no refunds on call failure.
func (*RateLimiter) PreCall ¶
func (r *RateLimiter) PreCall(ctx context.Context, req llm.CompleteRequest) error
PreCall drains the per-(identity, model) bucket by `expected_tokens`. Underflow → wrapped ErrRateLimited + governance.rate_limited event. Latent: a tier without RateLimit returns nil immediately.
type RealClock ¶
type RealClock struct{}
RealClock wraps `time.Now` and is the production default. Exported so callers that construct a Config with an explicit Clock for one dimension can keep the default for the rest.
type SetPosturePolicy ¶ added in v1.17.0
type SetPosturePolicy struct {
// contains filtered or unexported fields
}
SetPosturePolicy owns the StateStore-backed effective identity-tier policy record layered over the config-declared defaults. Immutable after construction (the StateStore is the mutable seam); safe to share across N goroutines.
Concurrent reuse: every field is set once at construction and never mutated except the `closed` atomic flag. Set / (the read layering it shares with PostureProvider) are safe for N concurrent goroutines; the record is a single runtime-level policy, so concurrent Set is last-writer-wins linearizable at the StateStore seam — there is no per-run mutable state on the struct.
func NewSetPosturePolicy ¶ added in v1.17.0
func NewSetPosturePolicy(st state.StateStore, bus events.EventBus, defaults Config, clock Clock, tierSource *TierSource, enforcementActive bool) (*SetPosturePolicy, error)
NewSetPosturePolicy constructs the policy. `st` (the persistence floor) and `bus` (the audit/observability bus) are mandatory; a nil either fails loud with `ErrInvalidConfig`. `defaults` is the config-declared governance policy the write layers over — a runtime with no written override enforces exactly these defaults. `clock` defaults to DefaultClock when nil. `tierSource` is the shared enforcement tier source a successful write swaps so the change ENFORCES with no restart; nil is valid (the write still persists + surfaces in the read, enforcement seeds at the next boot). `enforcementActive` reports whether the boot composed a governance wrapper reading that source (a non-empty effective policy); when false a write's `enforcement_pending_restart` response flag is true.
func (*SetPosturePolicy) Close ¶ added in v1.17.0
func (p *SetPosturePolicy) Close(_ context.Context) error
Close marks the policy closed; subsequent Set fails with `ErrClosed`. Idempotent.
func (*SetPosturePolicy) EnforcementActive ¶ added in v1.17.0
func (p *SetPosturePolicy) EnforcementActive() bool
EnforcementActive reports whether a governance enforcement wrapper was composed at boot (a non-empty effective policy read the tier source). When false, a `Set` persists + swaps the source but no enforcer reads it, so the policy does not enforce until the next restart — the write response surfaces this via `enforcement_pending_restart`.
func (*SetPosturePolicy) Set ¶ added in v1.17.0
func (p *SetPosturePolicy) Set(ctx context.Context, actor identity.Quadruple, spec SetPostureSpec) (Snapshot, error)
Set validates the submitted table as a FULL REPLACE through the shared validator, rejects a widening / zeroing / empty write fail-closed (ErrPolicyWidening / ErrInvalidPosture) BEFORE any StateStore mutation, persists the record through the StateStore, and THEN emits `governance.posture_set` best-effort. Returns the resolved persisted posture (byte-faithful to what the next `governance.posture` read returns). `actor` is the admin caller (the audit anchor), server-derived from the verified session by the Protocol surface — never the request body.
type SetPostureSpec ¶ added in v1.17.0
type SetPostureSpec struct {
// DefaultTier is the default-tier assignment applied to an identity
// that resolves to no explicit tier. Required when any tier is
// enforced (an empty value is rejected in that case).
DefaultTier string
// IdentityTiers is the complete tier table keyed by tier name. A tier
// present in the current effective policy but ABSENT here — or present
// with a zeroed enforced ceiling — is a fail-closed rejection, not a
// silent widening.
IdentityTiers map[string]TierConfig
}
SetPostureSpec is the admin-set desired-state for the whole identity-tier policy table — the in-process projection of the `governance.set_posture` wire shape. It is a FULL REPLACE: the supplied table wholly replaces the prior effective policy (subject to the fail-closed widening check).
type Snapshot ¶
type Snapshot struct {
// DefaultTier is the operator-configured default tier name. Empty
// when no default is configured.
DefaultTier string
// ResolvedTier is the tier the caller's identity resolves to via the
// configured TierResolver (or DefaultTier when no resolver is set).
// Empty when no tier resolves.
ResolvedTier string
// IdentityTiers is a deep copy of the configured tier map. Always
// non-nil — an empty map signals no enforcement (latent default).
IdentityTiers map[string]TierConfig
}
Snapshot is a deep-copied, immutable view of the configured governance posture: the `IdentityTiers` map, the `DefaultTier` selector, and the tier the caller's identity resolves to. Mutating any field of a returned Snapshot (including the `IdentityTiers` map) does NOT mutate the provider's underlying Config — the snapshot is a defensive deep copy.
type Subsystem ¶
type Subsystem interface {
PreCall(ctx context.Context, req llm.CompleteRequest) error
PostCall(ctx context.Context, req llm.CompleteRequest, resp llm.CompleteResponse, callErr error) error
}
Subsystem is governance's enforcement seam — the interface `governance.Wrap` consumes to gate / observe an LLM call. Implementations MUST be safe for N concurrent invocations against a single shared instance.
`PreCall` is invoked before the wrapped `LLMClient.Complete`. A non-nil return short-circuits the call: the wrapper returns the error directly and does NOT invoke the inner client. Implementations emit the corresponding `governance.*` event from PreCall on rejection.
`PostCall` is invoked after the wrapped `LLMClient.Complete` returns, whether or not `callErr` is nil. It accumulates cost / token / latency state from `resp` and emits any observability events. A non-nil return from PostCall is an observability signal — it is logged at Warn level by the wrapper but does NOT replace the original call's `(resp, callErr)` outcome on the way back to the caller.
func NewCompound ¶
NewCompound bundles N Subsystems into a single Subsystem that fans PreCall + PostCall across all members. PreCall short-circuits on the first member that returns a non-nil error; subsequent members are NOT invoked. PostCall runs every member regardless of any individual failure (observability semantics) and joins their errors via `errors.Join` for the wrapper to log.
The fan-out order is the slice order — operators put cheap checks (MaxTokens) before expensive ones (cost-ceiling state lookup) to keep the rejection path fast. The binary order is:
- MaxTokensEnforcer — purely in-memory cap check; cheapest reject.
- RateLimiter — bucket drain (lock + small state lookup).
- CostAccumulator — accumulator lookup (state I/O).
Concurrent reuse: NewCompound's returned Subsystem is a thin adapter; concurrent reuse depends on each member honouring.
func NewSubsystemFromConfig ¶ added in v1.3.0
func NewSubsystemFromConfig(cfg Config, store state.StateStore, bus events.EventBus) (Subsystem, error)
NewSubsystemFromConfig composes the V1 enforcement Subsystem from an operator-shaped `Config`. The members fan out in the documented cheapest-reject-first order (see `NewCompound`'s godoc):
- MaxTokensEnforcer — purely in-memory cap check; cheapest reject.
- RateLimiter — bucket drain (lock + small state lookup).
- CostAccumulator — accumulator lookup (state I/O).
Latent default: an empty `cfg.IdentityTiers` map returns `(nil, nil)` — the ONE sanctioned "no enforcement" state. The `llm.Open` wrapper hook treats a nil Subsystem as pass-through, and the posture surface reports the empty map verbatim, so the latency is visible, never silent.
Fail-loud: a non-empty tier map with a nil store or nil bus returns a wrapped `ErrInvalidConfig` — enforcement without persistence or observability is not a degraded mode, it is a misconfiguration (CLAUDE.md §13, no silent degradation).
Lifecycle: the returned Subsystem owns no goroutines and no persistent handles beyond the caller-owned store/bus (each member's Close only flips a closed flag), so it rides the caller's lifecycle — the production assembly keeps it for the stack's lifetime; a headless embedder disposes it with the wrapped client.
Concurrent reuse: one returned Subsystem is safe to share across N concurrent goroutines — each member carries its own guarantee (atomics + per-key mutexes; per-run scope flows via ctx).
type TenantOverridePolicy ¶ added in v1.5.0
type TenantOverridePolicy struct {
// contains filtered or unexported fields
}
TenantOverridePolicy realises the RFC §6.15 `ModelOverride` governance seam: an admin-set, identity-scoped, StateStore-backed default for a tenant's LLM parameters that takes effect on every session's NEXT run with no redeploy. It is NOT a `Subsystem` (it does not gate per-call in PreCall — a per-call read would change the model mid-run, violating the next-turn-only invariant); instead the run loop reads it ONCE at run start and pins the snapshot into the run's `RunContext`.
Concurrent reuse: the policy is immutable after construction except for its per-tenant cache (a `sync.Map` of `*tenantOverrideEntry`, each guarded by its own load mutex) and the `closed` atomic flag. Set / Get are safe for N concurrent goroutines; per-call state lives in arguments and locals, never on the policy struct.
Freshness scope: the cache is write-through on Set and lazy-loaded once per tenant, with no TTL — the same per-process cache model the cost accumulator uses. Within ONE runtime process (the writer and the run loop share the same instance) a Set is immediately visible to the next run, so the "every session's next run" guarantee holds. Across multiple runtime replicas over a shared store, freshness holds too: every Get re-reads the StateStore (per-read freshness — there is NO permanent loaded cache), so a Set on replica A is visible to replica B on B's next Get (the run loop's run-start resolution). The per-tenant mutex serialises a tenant's read/write; different tenants are independent via the sync.Map. The run loop already does StateStore reads at run start, so the per-Get Load is in the existing cost envelope.
func NewTenantOverridePolicy ¶ added in v1.5.0
func NewTenantOverridePolicy(st state.StateStore, bus events.EventBus, validModels []string, clock Clock) (*TenantOverridePolicy, error)
NewTenantOverridePolicy constructs the policy. `state` (the persistence floor) and `bus` (the audit/observability bus) are mandatory; a nil either fails loud with `ErrInvalidConfig`. `validModels` is the set of model names with a configured `ModelProfile` — `Set` rejects a model outside it with `ErrUnknownModel`. An empty `validModels` means no model is acceptable (any model set fails loud) — the binary always configures at least the bound default model, so an empty set signals a misconfiguration, not a latent default.
func (*TenantOverridePolicy) Close ¶ added in v1.5.0
func (p *TenantOverridePolicy) Close(_ context.Context) error
Close marks the policy closed; subsequent Set / Get fail with `ErrClosed`. Idempotent.
func (*TenantOverridePolicy) Get ¶ added in v1.5.0
func (p *TenantOverridePolicy) Get(ctx context.Context, tenant string) (TenantOverrideSpec, bool, error)
Get returns the tenant's current default-override spec and whether a record exists. A tenant with no record returns (zero spec, false, nil) — the run loop then falls through to the config defaults. A StateStore read failure fails loud with wrapped `ErrStateUnavailable` (no silent "no overrides" on read failure — CLAUDE.md §13).
func (*TenantOverridePolicy) Set ¶ added in v1.5.0
func (p *TenantOverridePolicy) Set(ctx context.Context, actor identity.Quadruple, tenant string, spec TenantOverrideSpec) error
Set replaces tenant's default-override record with spec (desired-state replace: a nil field in spec clears that dimension; an all-nil spec clears the record entirely). It validates the spec, persists it, updates the cache, and emits `governance.tenant_overrides_set`.
Authorization (the admin-scope gate) is the Protocol surface's responsibility — this method assumes the caller is already authorized and focuses on validation + persistence. `tenant` MUST be non-empty (fail closed — identity is mandatory).
type TenantOverrideSpec ¶ added in v1.5.0
type TenantOverrideSpec struct {
Model *string
ExtraInstructions *string
Temperature *float64
MaxTokens *int
ReasoningEffort *string
}
TenantOverrideSpec is the admin-set desired-state for a tenant's default LLM parameters. Every field is optional (nil = "inherit the config default for this dimension"). It is the in-process projection of the `governance.set_tenant_overrides` wire shape and the value the run loop resolves at run start.
- Model overrides the default model name (validated against the configured ModelProfiles at set time).
- ExtraInstructions is ADDITIVE — appended to the agent's system prompt, never a replacement.
- Temperature / MaxTokens / ReasoningEffort override the matching LLM request fields.
func (TenantOverrideSpec) IsEmpty ¶ added in v1.5.0
func (s TenantOverrideSpec) IsEmpty() bool
IsEmpty reports whether the spec sets no dimension at all (a cleared desired-state). A cleared record means "the tenant inherits every config default."
type TenantOverridesSetPayload ¶ added in v1.5.0
type TenantOverridesSetPayload struct {
events.SafeSealed
// Actor is the admin-scoped caller that performed the set.
Actor identity.Quadruple
// Tenant is the tenant_id whose defaults were changed.
Tenant string
// Model is the new default model name, or "" when the model
// dimension was left unset / cleared. Non-secret.
Model string
// SetModel / SetExtraInstructions / SetTemperature / SetMaxTokens /
// SetReasoningEffort report which dimensions the new desired-state
// record carries (true) versus inherits from config (false).
SetModel bool
SetExtraInstructions bool
SetTemperature bool
SetMaxTokens bool
SetReasoningEffort bool
OccurredAt time.Time
}
TenantOverridesSetPayload is the typed payload for EventTypeTenantOverridesSet. SafePayload — the actor identity, the target tenant, the (non-secret) model name, and the per-dimension set/clear flags are operator-visible audit metadata. The extra-instructions PROSE is never carried here (only the boolean flag), so operator-supplied text never lands on the bus; the payload still runs through the audit Redactor before publish (CLAUDE.md §7 rule 6).
type TierConfig ¶
type TierConfig struct {
// BudgetCeilingUSD is the per-identity (per tier) cost ceiling in
// USD. PreCall blocks when the accumulator's total for the request's
// identity meets or exceeds this. 0 = no ceiling.
BudgetCeilingUSD float64
// RateLimit is the per-(identity, model) token-bucket config.
// Zero-valued = no rate limit (latent).
RateLimit RateLimitConfig
// MaxTokens is the per-call cap. Requests whose `MaxTokens` exceed
// this fail loudly. 0 = no cap (latent).
MaxTokens int
}
TierConfig is one tier's policy bundle. Each field zero = latent for that policy. An operator who wants to enforce cost only sets `BudgetCeilingUSD`; rate-limit + MaxTokens fields stay zero.
type TierResolver ¶
TierResolver maps an `identity.Identity` to a tier name. Resolvers MUST be pure and deterministic — the same identity always maps to the same tier. Non-deterministic resolvers race against the per-identity state caches.
type TierSource ¶ added in v1.17.0
type TierSource struct {
// contains filtered or unexported fields
}
TierSource holds the current effective identity-tier policy behind an atomic pointer. Built once at boot (seeded from the StateStore record layered over the config defaults) and shared — by reference — between the enforcement subsystem (which reads it per PreCall) and the `SetPosturePolicy` write path (which swaps it on a successful write). Safe for N concurrent readers + a concurrent writer.
func NewTierSource ¶ added in v1.17.0
func NewTierSource(defaultTier string, tiers map[string]TierConfig) *TierSource
NewTierSource builds a TierSource holding the given effective policy. The tier map is deep-copied so a later caller mutation cannot reach the published snapshot.
func NewTierSourceFromState ¶ added in v1.17.0
func NewTierSourceFromState(ctx context.Context, st state.StateStore, defaults Config) (*TierSource, error)
NewTierSourceFromState builds the hot-swappable enforcement TierSource seeded from the EFFECTIVE policy at boot — the persisted `set_posture` record layered over the config-declared defaults (record present ⇒ record wins; absent ⇒ config defaults). The enforcement assembly reads through the returned source; `SetPosturePolicy` swaps it on a write. A corrupt / forward-schema record fails loud (no silent reset).
func (*TierSource) DefaultTier ¶ added in v1.17.0
func (s *TierSource) DefaultTier() string
DefaultTier returns the current effective default-tier assignment.
func (*TierSource) Store ¶ added in v1.17.0
func (s *TierSource) Store(defaultTier string, tiers map[string]TierConfig)
Store atomically publishes a new effective policy. The incoming map is deep-copied before publish so the snapshot is immutable. Observed on the next `resolveTier` / `tierConfig` — the hot-reload swap.
func (*TierSource) Tiers ¶ added in v1.17.0
func (s *TierSource) Tiers() map[string]TierConfig
Tiers returns a deep copy of the current effective tier table (always non-nil). Used at boot for the latent-default decision and by tests.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package conformancetest exposes the canonical governance correctness suite that every supported `state.StateStore` driver must satisfy.
|
Package conformancetest exposes the canonical governance correctness suite that every supported `state.StateStore` driver must satisfy. |