flags

package
v0.27.1 Latest Latest
Warning

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

Go to latest
Published: Jul 12, 2026 License: MIT Imports: 9 Imported by: 0

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

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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

type SignupCapState struct {
	Limit int    `json:"limit"`
	Actor string `json:"actor,omitempty"`
}

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

func OpenStore(ctx context.Context, d *sql.DB, logger *slog.Logger) (*Store, error)

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

func (s *Store) SetAICallCap(ctx context.Context, dailyLimit int, actor string) error

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

func (s *Store) SetKillSwitch(ctx context.Context, paused bool, reason, actor string) error

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

func (s *Store) SetSignupCap(ctx context.Context, limit int, actor string) error

SetSignupCap persists a new signup cap and refreshes the local snapshot so the caller observes their own write immediately.

func (*Store) SetTripPlanner

func (s *Store) SetTripPlanner(ctx context.Context, enabled bool, actor string) error

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

func (s *Store) Start(ctx context.Context)

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.

Jump to

Keyboard shortcuts

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