Raxx · internal docs

internal · gated ↑ index

ADR-0026 — Feature Flag Persistence: DB table vs external store vs env-var-only

Status: Accepted Date: 2026-04-28 UTC Refs: #146, console-feature-flags.md


Context

The console needs a mechanism to persist feature flag values that: - Survives Heroku dyno restarts and deploys (i.e., is not just in-process state) - Is auditable (who changed it, when, from what to what) - Is env-scoped (prod and staging can diverge) - Is accessible to the console UI without requiring Heroku CLI access

Three persistence candidates were evaluated:

  1. Existing console Postgres DB — new console_feature_flags table
  2. Infisical (vault) — store flag values as secrets
  3. Env-var-only — keep Heroku config vars, add a Heroku API wrapper in the console

Decision

Option 1: new console_feature_flags table in the console Postgres DB.

Schema: (flag_key TEXT, env TEXT, value INTEGER 0/1, updated_at, updated_by) with a UNIQUE (flag_key, env) constraint.

Resolution order: DB row wins over env var, which wins over YAML default.


Consequences

Positive: - Auditable within the existing console_audit_log infrastructure. No new logging system needed. - Env-scoped trivially via the env column — the same flag key can have different values for prod and staging. - Transactional: flips are atomic DB writes. No partial state. - Rollback is a table drop: the existing env-var fallback re-activates automatically. - The updated_by FK provides operator attribution without any new identity infrastructure. - Consistent with the env-switcher pattern (ADR-0024), which also uses the console DB for session-resident state.

Negative: - One more table to migrate and maintain. - Cache invalidation across multiple dynos: a flip on dyno A's in-process cache does not immediately invalidate dyno B's cache. Mitigated by a short TTL (30s). If sub-30s propagation is required, a pub/sub mechanism would be needed (deferred to open question in the design doc). - If the console DB is unavailable, flag reads fall back to env vars / YAML (fail-safe, not fail-closed).


Alternatives Considered

Option 2: Infisical (vault)

Infisical already holds sensitive config values. Storing feature flags there would co-locate all runtime config in one place.

Rejected for the following reasons: - Infisical is designed for secrets; feature flag values are not sensitive. Treating them as secrets conflates the two concerns and means flag reads require vault credentials everywhere a flag is checked. - Audit log in Infisical is external to the console's own audit trail. Flag flip events would appear in two different audit systems (Infisical + console_audit_log), creating reconciliation burden. - The Infisical client has latency and dependency overhead inappropriate for a hot path that is checked on nearly every request. - Env-scoping in Infisical is possible but requires Infisical environment separation to be correctly wired for every consumer — an operational dependency that does not exist today.

Option 3: Heroku config vars via Heroku API

The console could call the Heroku Platform API to read/write config vars, replacing the CLI.

Rejected for the following reasons: - Requires storing a Heroku API token in the console, which is a rotatable secret but also a significant blast-radius credential. Not appropriate for a flag flip path. - Heroku config var changes restart the dyno — a flag flip would cause a brief outage on the console itself. Unacceptable for a UI-driven toggle. - Audit log would require custom implementation (Heroku API calls are not audit-logged in console_audit_log automatically). - Couples the console's operational state to Heroku's API availability.