Documentation
¶
Overview ¶
Package flags owns the DB-backed operational flag table (migration 0003_flags.sql). It exposes one real flag today — rivian_upstream_paused, the kill switch wired into every Rivian-facing code path — but the shape generalises: add a new row, add a new accessor.
Design ¶
The hot path must never block on Postgres. A Store holds an in-process snapshot of the flag row and refreshes it on a ticker (default 10s). Every caller reads the snapshot, which is a plain atomic load — no DB round-trip, no mutex for readers.
Operator writes (HTTP admin endpoint, future CLI) go straight to Postgres and immediately refresh the snapshot so the writer sees their own effect. Other replicas see the change on their next poll (p99 ≤ poll interval). That gap is intentional: a kill switch is a "within ~10 seconds the storm stops" contract, not a "within 10ms" one — the architecture doc sizes it that way on purpose.
Why not LISTEN/NOTIFY ¶
LISTEN/NOTIFY would close the cross-replica propagation gap to sub-second. It also doubles our Postgres connection assumptions (every pod needs a dedicated listen connection) and our failure surface (reconnect logic, missed notifies). 10s polling at 3-8 pods is one indexed SELECT per pod per 10s — cheap enough that the simplicity wins. Revisit when a pod count gets into the dozens.
Index ¶
- Constants
- type AICallCapState
- type KillSwitchState
- type SignupCapState
- type SleepSafeState
- type Store
- func (s *Store) AICallCap() AICallCapState
- func (s *Store) KillSwitch() KillSwitchState
- func (s *Store) SetAICallCap(ctx context.Context, dailyLimit int, actor string) error
- func (s *Store) SetKillSwitch(ctx context.Context, paused bool, reason, actor string) error
- func (s *Store) SetSignupCap(ctx context.Context, limit int, actor string) error
- func (s *Store) SetTripPlanner(ctx context.Context, enabled bool, actor string) error
- func (s *Store) SignupCap() SignupCapState
- func (s *Store) SleepSafe() SleepSafeState
- func (s *Store) Start(ctx context.Context)
- func (s *Store) TripPlanner() TripPlannerState
- type TripPlannerState
Constants ¶
const AICallCapName = "ai_call_cap"
AICallCapName bounds how many LLM-backed calls a single user can make per day (trip advice, drive efficiency). Read by the AI budget gate before each call. Fail-open: a missing row or a non-positive limit means "no cap" — this is a cost backstop, not a security control, so it must never brick the feature.
const DefaultPollInterval = 10 * time.Second
DefaultPollInterval is how often the Store refreshes its in-memory snapshot from Postgres. Chosen so a flipped flag takes effect in roughly the time a human can notice — on the same order as a Prometheus scrape interval.
const KillSwitchName = "rivian_upstream_paused"
KillSwitchName is the flags.name value for the Rivian upstream kill switch. Exported so tests and admin handlers can reference the same string the poller does.
const SignupCapName = "signup_cap"
SignupCapName gates how many user accounts can exist at once. Read in the OIDC callback before users.insert; existing users signing back in are exempt.
const SleepSafeName = "sleep_safe_vehicles"
SleepSafeName lists the vehicles whose non-essential Rivian subscriptions are parked while the car reports it is asleep (see rivian.StateMonitor's sleep gate). An operator switch, flipped over psql with no deploy, e.g.:
INSERT INTO flags (name, value, updated_by)
VALUES ('sleep_safe_vehicles', '{"vehicles":["01-242521064"]}', 'ops')
ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value, updated_at = now();
{"all": true} applies it to every vehicle.
const TripPlannerEnabledName = "trip_planner_enabled"
TripPlannerEnabledName is the flags.name value gating the trip planner UI + endpoints. Default-off: when the row is missing or the JSON decodes to {enabled:false}, the planner stays hidden.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AICallCapState ¶ added in v0.24.0
type AICallCapState struct {
DailyLimit int `json:"daily_limit"`
Actor string `json:"actor,omitempty"`
}
AICallCapState is the JSONB payload for the per-user daily AI call cap. DailyLimit <= 0 means "no cap" — fail-open, the opposite of SignupCap, because the row being absent should leave the AI features working rather than refusing every request.
type KillSwitchState ¶
type KillSwitchState struct {
Paused bool `json:"paused"`
Reason string `json:"reason,omitempty"`
Actor string `json:"actor,omitempty"`
}
KillSwitchState is the JSONB payload stored in flags.value for rivian_upstream_paused. The extra fields let us track who flipped it and why without a schema migration per axis.
type SignupCapState ¶ added in v0.21.0
SignupCapState is the JSONB payload for the signup cap. Zero (Limit=0) means "block all new signups" — fail-closed when the row is missing or malformed.
type SleepSafeState ¶ added in v0.31.2
type SleepSafeState struct {
All bool `json:"all,omitempty"`
Vehicles []string `json:"vehicles,omitempty"`
}
SleepSafeState is the JSONB payload for SleepSafeName. Vehicles are Rivian vehicle IDs (e.g. "01-242521064").
func (SleepSafeState) Covers ¶ added in v0.31.2
func (s SleepSafeState) Covers(vehicleID string) bool
Covers reports whether vehicleID is in sleep-safe mode.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the runtime flag cache. Start() kicks off the background refresh; callers read via KillSwitch(). The type is safe for concurrent use: all readers hit atomic.Value, only the refresh goroutine mutates.
func OpenStore ¶
OpenStore builds a Store against the shared Postgres pool and primes the snapshot with a blocking read so the first KillSwitch() call after New never returns a zero value. A DB outage at startup is non-fatal — the snapshot defaults to "not paused" and the background loop keeps trying.
func (*Store) AICallCap ¶ added in v0.24.0
func (s *Store) AICallCap() AICallCapState
AICallCap returns the current cached per-user daily AI call cap. Cheap atomic load; safe to call before every LLM-backed request. Returns the zero value (DailyLimit: 0 = no cap) when the row is absent — the fail-open posture the gate relies on.
func (*Store) KillSwitch ¶
func (s *Store) KillSwitch() KillSwitchState
KillSwitch returns the current cached kill-switch state. Cheap — one atomic load. Safe to call on every outbound request. Returns the zero value (Paused: false) when the Store was opened against a DB where the migration hasn't been applied.
func (*Store) SetAICallCap ¶ added in v0.24.0
SetAICallCap persists a new per-user daily AI call cap and refreshes the local snapshot so the caller observes their own write immediately. A non-positive limit disables the cap.
func (*Store) SetKillSwitch ¶
SetKillSwitch persists a new kill-switch state and immediately refreshes the local snapshot so the caller observes their own write. `actor` is stored on the row for audit trails; pass the username of whoever flipped the switch (or "system:reason" for automated trips).
func (*Store) SetSignupCap ¶ added in v0.21.0
SetSignupCap persists a new signup cap and refreshes the local snapshot so the caller observes their own write immediately.
func (*Store) SetTripPlanner ¶
SetTripPlanner persists a new trip-planner flag state.
func (*Store) SignupCap ¶ added in v0.21.0
func (s *Store) SignupCap() SignupCapState
SignupCap returns the current cached signup-cap state. Cheap atomic load; safe on the OIDC callback hot path.
func (*Store) SleepSafe ¶ added in v0.31.2
func (s *Store) SleepSafe() SleepSafeState
SleepSafe returns the cached sleep-safe vehicle list. Lock-free; the monitor's sleep gate reads it every second per vehicle.
func (*Store) Start ¶
Start launches the refresh loop. Safe to call multiple times; only the first invocation takes effect. The loop exits when ctx is cancelled.
func (*Store) TripPlanner ¶
func (s *Store) TripPlanner() TripPlannerState
TripPlanner returns the current cached trip-planner flag state. Same lock-free read shape as KillSwitch.
type TripPlannerState ¶
type TripPlannerState struct {
Enabled bool `json:"enabled"`
Actor string `json:"actor,omitempty"`
}
TripPlannerState is the JSONB payload for the trip-planner feature flag. Default zero value (Enabled=false) hides the planner UI and 404s its endpoints — chosen so missing rows or fresh installs stay locked down.