← Back to Skills

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…

Ruleconfigure

About

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.

Skill Content

This is the raw markdown that gets installed as a Claude Code rule.

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

```typescript
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>`).

| Route | Purpose |
|---|---|
| `GET <prefix>/admin/settings` | List every declared setting |
| `PUT <prefix>/admin/settings/:key` | Set 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:

```json
{
  "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.

| Service | Console page |
|---|---|
| iec-janus | Console → Janus Switches |
| iec-wallet | Console → 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.

Install

Copy the skill content and save it to:

~/.claude/rules/tawa-runtime-settings.md
Download .md

Coming soon via CLI:

tawa chaac install tawa-runtime-settings

Details

Format
Rule
Category
configure
Version
1.0.85406
Tokens
~1,166
Updated
2026-10-13
platformsettingsfeature-flagsconfigconsolesuper-admintawa-web