flags

package
v0.18.57 Latest Latest
Warning

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

Go to latest
Published: May 14, 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 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 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 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 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) 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) 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) SetTripPlanner

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

SetTripPlanner persists a new trip-planner flag state.

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