Runtime Settings

Tunable values are runtime settings: stored in the service's own database and edited by super admins in the Tawa Console. A redeploy or a code change is never needed to flip a value.

The Short Version

  • A tunable is a setting, not an env var or config var.
  • Each service declares its settings in a typed registry; the Console renders them with no UI work.
  • Changes apply within about 15 seconds, without a restart.
  • Only an owner, using their own Bio-ID JWT, can write. Every write is audited.

What Is a Setting

  • Feature flags
  • URLs shown to users (for example a "buy gas" link)
  • Rates
  • Limits
  • Intervals
  • Allow-lists
  • Rollout modes (for example off / shadow / enforce)

What Stays Env / Config

Use tawa config (see config) for values that wiring needs before the database is reachable or that must never be visible in a UI:

  • Secrets and credentials
  • Connection strings
  • Service URLs used for infrastructure wiring
  • The settings-owners list itself (the sub values allowed to write settings)

If changing the value is a product or operations decision, it is a setting. If changing it reconnects the service to something, it is config.

The Pattern

Reference implementation: iec-janus, src/services/settings.ts and src/routes/adminSettings.ts.

  • Typed registry. One entry per dotted lowercase key (authz, gas.buyUrl, promo.launchGrantsEnabled), each with a zod schema, a defaultValue, a one-sentence description, and an editor hint: 'boolean' | 'number' | 'string' | 'url' | 'json'.
  • Storage. Values live in a Mongo collection. An audit collection holds one row per write with before, after, by and at.
  • Cache. An in-memory cache refreshes about every 15 seconds, so a change applies without a restart. If Mongo is unreachable during a refresh, the previous cached values are kept.
  • getSetting(key) is synchronous, never throws, and returns the registry default when no value is stored or the stored value fails its schema.
  • Optimistic concurrency. Each setting has a version. A write may send expectedVersion; a mismatch is rejected so two admins cannot overwrite each other.
  • Jobs read flags at run time. A cron handler or queue worker calls getSetting on every run, never once at boot, so a flag flipped in the Console takes effect on the next run.
const SETTINGS = {
  'promo.launchGrantsEnabled': {
    schema: z.boolean(),
    defaultValue: () => false,
    description: 'Run the launch promo-grant cycle for eligible agency orgs.',
    editor: 'boolean',
  },
} as const

if (getSetting('promo.launchGrantsEnabled')) await runLaunchGrantCycle()

API

Mounted under the service's own prefix (<prefix>).

RoutePurpose
GET <prefix>/admin/settingsList every declared setting
PUT <prefix>/admin/settings/:keySet one value. Body: { "value": ..., "expectedVersion": 3 } (expectedVersion optional)
GET <prefix>/admin/settings/:key/history?limit=Audit rows for one key, newest first

The list response:

{
  "settings": [
    {
      "key": "gas.buyUrl",
      "value": "https://example.com/buy",
      "default": "",
      "version": 4,
      "source": "stored",
      "updatedBy": "<bio-id sub>",
      "updatedAt": "2026-10-04T12:00:00.000Z",
      "description": "Where the 402 INSUFFICIENT_GAS response sends a caller to buy gas. Empty = no link.",
      "editor": "url"
    }
  ],
  "canWrite": true,
  "actor": "<bio-id sub>"
}

source is stored (a value is in the database) or default (the registry default; version 0). updatedBy and updatedAt are absent for a key that has never been written.

Auth

  • Reads accept the internal key or a user JWT.
  • Writes require the owner's own Bio-ID JWT, with sub in the service's owners list.
  • The internal key is refused on writes with 403. A service, script or agent holding the internal key cannot change a setting.

canWrite and actor in the list response tell the UI whether to render fields as editable.

UI

The Tawa Console (tawa-web) renders every declared setting generically from description and editor. Adding a key to the registry is all it takes; no Console change or deploy is needed.

ServiceConsole page
iec-janusConsole → Janus Switches
iec-walletConsole → Wallet Switches

Review Rule

A PR that adds a tunable env var or config var is changed to a setting before merge. Reviewers ask one question: could someone want to change this without a deploy? If yes, it goes in the registry.

Last updated: October 6, 2026