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.mdComing soon via CLI:
tawa chaac install tawa-runtime-settingsDetails
- Format
- Rule
- Category
- configure
- Version
- 1.0.85406
- Tokens
- ~1,166
- Updated
- 2026-10-13
platformsettingsfeature-flagsconfigconsolesuper-admintawa-web