Help us shape the future of Go! Take the 2026 Go Developer Survey

flags

package
v0.40.22 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 10 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 (
	DefaultHoldCheckEvery = 5 * time.Minute
	DefaultHoldMax        = 30 * time.Minute
)

Hold timing defaults and bounds.

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 DCFCAutoNetworkName = "dcfc_auto_network"

DCFCAutoNetworkName gates DC fast charge network auto-detection with user verification (verify states, confirm, push). Same shape as AudienceState; off when the row is missing or malformed.

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 InactiveAccountPurgeName = "inactive_account_purge"

InactiveAccountPurgeName gates the deletion of accounts that never connected a vehicle (warned at day 90, deleted at day 120). Off when the row is missing or malformed. Enable it only after the privacy policy that promises the rule is published:

{"dry_run": true}        count candidates in the log, send and delete nothing
{"users": ["<uuid>"]}    act on those accounts only (a rehearsal)
{"all": true}            act on every eligible account
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 RivianReceiptsName = "rivian_receipts"

RivianReceiptsName gates the sync of Rivian's charging receipts (what Rivian billed per session) into charges.receipt_*. Same shape as AudienceState; off when the row is missing or malformed, so the extra upstream calls start only for the users the operator names:

INSERT INTO flags (name, value, updated_by)
VALUES ('rivian_receipts', '{"users":["<user uuid>"]}', 'ops')
ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value, updated_at = now();
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 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-000000000"]}', 'ops')
ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value, updated_at = now();

{"all": true} applies it to every vehicle; "except" opts vehicles back out (a control group): {"all": true, "except": ["01-…"]}.

View Source
const StatementEmailName = "statement_email_enabled"

StatementEmailName gates the monthly statement email (operator flag; the user's own opt-in is separate). Same shape as AudienceState.

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.

View Source
const WakeSafetyName = "wake_safety"

WakeSafetyName holds the battery-drain experiment's wake-safety switches (D2). All off when the row is missing. Each switch selects vehicles like sleep_safe_vehicles (all / vehicles / except), so an experiment can run on a treatment group with the rest as control. Flipped over psql with no deploy, picked up within the poll interval:

INSERT INTO flags (name, value, updated_by)
VALUES ('wake_safety', '{"capture_off":{"all":true},
        "hold_asleep":{"vehicles":["01-…"]},"hold_check_every":"5m"}', 'ops')
ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value, updated_at = now();

A malformed value is logged and read as all-off, never blocking the other flags. See WakeSafetyState.

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 AudienceState added in v0.38.26

type AudienceState struct {
	All   bool     `json:"all,omitempty"`
	Users []string `json:"users,omitempty"`
	Actor string   `json:"actor,omitempty"`
}

AudienceState is the JSONB payload of a per-user flag (statement email, DCFC auto-detection, Rivian receipts). Users are rivolt user UUIDs; All turns the feature on for everyone.

func (AudienceState) Enabled added in v0.38.26

func (s AudienceState) Enabled(userID string) bool

Enabled reports whether the feature is on for userID.

type InactivePurgeState added in v0.36.8

type InactivePurgeState struct {
	// DryRun makes the job log candidate counts only: it sends no email and
	// deletes nothing, whatever All and Users say.
	DryRun bool     `json:"dry_run,omitempty"`
	All    bool     `json:"all,omitempty"`
	Users  []string `json:"users,omitempty"`
}

InactivePurgeState is the JSONB payload for InactiveAccountPurgeName.

func (InactivePurgeState) Enabled added in v0.36.8

func (s InactivePurgeState) Enabled(userID string) bool

Enabled reports whether the job may act (warn, delete) on userID: not in a dry run, and the user is named or All is set.

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 SleepSafeState added in v0.31.2

type SleepSafeState struct {
	All      bool     `json:"all,omitempty"`
	Vehicles []string `json:"vehicles,omitempty"`
	// Except opts vehicles out, even under All.
	Except []string `json:"except,omitempty"`
}

SleepSafeState is the JSONB payload for SleepSafeName. Vehicles are Rivian vehicle IDs (e.g. "01-000000000").

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

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) DCFCAutoNetwork added in v0.33.0

func (s *Store) DCFCAutoNetwork() AudienceState

DCFCAutoNetwork returns the cached DC fast charge auto-detection flag (Enabled(userID)). Lock-free; off when the row is missing.

func (*Store) InactiveAccountPurge added in v0.36.8

func (s *Store) InactiveAccountPurge() InactivePurgeState

InactiveAccountPurge returns the cached purge flag. Lock-free; off (not even a dry run) when the row is missing.

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) RivianReceipts added in v0.36.4

func (s *Store) RivianReceipts() AudienceState

RivianReceipts returns the cached per-user receipts-sync flag (Enabled(userID)). Lock-free; off when the row is missing.

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) SetStatementEmail added in v0.33.0

func (s *Store) SetStatementEmail(ctx context.Context, st AudienceState, actor string) error

SetStatementEmail persists the operator flag and refreshes the local snapshot.

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

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) StatementEmail added in v0.33.0

func (s *Store) StatementEmail() AudienceState

StatementEmail returns the cached operator flag for the monthly statement email. Lock-free; off when the row is missing.

func (*Store) TripPlanner

func (s *Store) TripPlanner() TripPlannerState

TripPlanner returns the current cached trip-planner flag state. Same lock-free read shape as KillSwitch.

func (*Store) WakeSafety added in v0.32.0

func (s *Store) WakeSafety() WakeSafetyState

WakeSafety returns the cached wake-safety switches. Lock-free.

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.

type VehicleSet added in v0.32.0

type VehicleSet struct {
	All      bool     `json:"all,omitempty"`
	Vehicles []string `json:"vehicles,omitempty"`
	Except   []string `json:"except,omitempty"`
}

VehicleSet selects vehicles by Rivian vehicle ID: every vehicle when All, else those in Vehicles; Except always wins.

func (VehicleSet) Covers added in v0.32.0

func (v VehicleSet) Covers(vehicleID string) bool

Covers reports whether vehicleID is selected.

type WakeSafetyState added in v0.32.0

type WakeSafetyState struct {
	// CaptureOff drops the Parallax capture topics (CaptureRVMs) from
	// the drive-dynamics subscription, keeping GPS and drive dynamics.
	// Takes effect at the subscription's next reconnect (≤ ~3 min).
	CaptureOff VehicleSet `json:"capture_off"`
	// RestQuiet skips the periodic REST GetVehicleState poll while the
	// car reports asleep (drain experiment A). Don't combine with
	// HoldAsleep, whose wake checks are REST calls.
	RestQuiet VehicleSet `json:"rest_quiet"`
	// HoldAsleep stops vehicleState from resubscribing after Rivian's
	// ~3-min connection close while the car reports asleep. It
	// resubscribes as soon as anything shows the car awake, a REST
	// check every HoldCheckEvery finds it awake or moved, or after
	// HoldMax.
	HoldAsleep VehicleSet `json:"hold_asleep"`
	// Go durations; empty or invalid means the default, out-of-range
	// values are clamped (see CheckEvery, MaxHold).
	HoldCheckEvery string `json:"hold_check_every,omitempty"`
	HoldMax        string `json:"hold_max,omitempty"`
	Actor          string `json:"actor,omitempty"`
}

WakeSafetyState is the JSONB payload for WakeSafetyName.

func (WakeSafetyState) CheckEvery added in v0.32.0

func (w WakeSafetyState) CheckEvery() time.Duration

CheckEvery is the REST wake-check cadence while held, in [1m, 15m].

func (WakeSafetyState) MaxHold added in v0.32.0

func (w WakeSafetyState) MaxHold() time.Duration

MaxHold caps one hold, in [5m, 2h].

Jump to

Keyboard shortcuts

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