-
Notifications
You must be signed in to change notification settings - Fork 1
Configuration Model
TMI configuration is divided into two distinct categories: bootstrap and operational. Understanding this split is the single most important concept for operating or developing against TMI. Bootstrap settings must be present before the server can start; operational settings live in the database and can be changed while the server is running.
This page explains the model. For task-specific guides, see Configuring-Local-Development, Bootstrapping-Production, Managing-Operational-Settings, and Configuration-Reference.
| Property | Bootstrap | Operational |
|---|---|---|
| Where stored | Config file and/or environment variables |
system_settings database table |
| When read | Once, at startup | At use time (hot) or once at startup (static) |
| How to change | Edit file or env var, then restart |
PUT /admin/settings/{key} — no restart needed for hot settings |
| Who seeds initial values | Built-in defaults, overridden by file/env | Built-in registry defaults written to DB on first run |
| Secrets handling | Inline literals or secret references (vault/env/file) | Encrypted at rest in the DB; rotated via /admin/settings/reencrypt
|
| Example keys |
database.url, auth.jwt.secret, server.port
|
OAuth providers, SAML providers, Timmy settings, feature flags |
The split is not arbitrary — it reflects a chicken-and-egg reality. You cannot store the database connection string inside the database. Any setting that the server needs in order to reach its data stores must be a bootstrap setting.
Built-in default → config-*.yml file → TMI_* environment variable
↑
(highest precedence)
Resolution happens exactly once, at startup. Environment variables always win. If a required setting has no effective value after resolution, Config.ValidateRequired() aborts the boot with a clear error naming the missing key.
First run (system_settings table is empty):
Built-in registry defaults → written to system_settings table
At read time (env > file > db precedence):
TMI_* environment variable ← wins if present
↓ else
config-*.yml value ← wins if present
↓ else
system_settings table → runtime-editable value (the common case)
Registry defaults seed the system_settings table on first run, and that table is the runtime-editable store you change through /admin/settings. However, the env > file > db precedence is still live and it applies to operational keys too. On every read the settings service checks its config provider (which merges environment variables over config-file values) first; only if no env/config value is present for the key does it fall back to the database value.
In the common case an operational key has no env var and no config-file entry, so the database value wins and is what you edit at runtime. But if an operator has set that key via a TMI_* environment variable or a YAML entry, that value overrides the database at read time, and PUT /admin/settings/{key} returns 409 Conflict with a message like Setting '<key>' is controlled by <source> and cannot be modified via the API.
Think of it as two independent rules working together: the category decides where a value is allowed to live (bootstrap keys are file/env only — the settings service refuses to serve them from the database; operational keys live in system_settings), and the precedence decides which value wins when more than one source supplies a value for the same key.
Pinning an operational setting. Because env/config wins over the database, you can deliberately "pin" any operational setting to a fixed value by declaring it in a
TMI_*environment variable or a config file. This is useful for locking a value into a reproducible deployment. The trade-off: while it is pinned, that key cannot be edited through the admin API (it returns409 Conflict) until you remove the env/config value, at which point the database value takes over again.
A bootstrap setting that holds a secret does not have to store the secret inline. TMI supports three reference schemes that are resolved at startup:
| Scheme | Format | What happens |
|---|---|---|
| Vault | vault://PATH |
Secret is fetched from the configured secrets provider (secrets.provider) |
| Environment variable | env://NAME |
The value of environment variable NAME is used |
| File | file://PATH |
The whitespace-trimmed contents of the file at PATH are used |
| Inline | (no prefix) | The value is used as-is |
Any value that does not begin with a recognized scheme prefix is treated as an inline literal.
Example bootstrap config file showing all four forms:
auth:
jwt:
secret: vault://secret/tmi/jwt-secret # fetched from Vault at startup
signing_method: HS256 # inline literal
database:
url: env://DATABASE_URL # read from environment variable
redis:
password: file:///run/secrets/redis-password # read from a mounted secret file
secrets:
provider: vaultThe secrets.provider key selects which backend handles vault:// references. Supported providers: env, aws, azure, gcp, oci, vault.
"Secret" and "Bootstrap" are orthogonal properties. Confusing them leads to misconfiguration.
- A bootstrap secret is a secret that must be known before the server can start (e.g., the DB password). It is resolved from env/file/vault at startup and never stored in the database.
- An operational secret is a secret that is stored encrypted in the
system_settingstable, managed through the admin API, and rotated via/admin/settings/reencrypt. It never appears in a config file or environment variable.
| Key | Why it must be bootstrap |
|---|---|
database.url |
Needed to open the database connection |
auth.jwt.secret |
Needed to verify JWTs before any request can be authenticated |
database.redis.password |
Needed to connect to Redis |
| Key | Where it lives |
|---|---|
OAuth provider client_secret
|
Encrypted in system_settings
|
timmy.llm_api_key |
Encrypted in system_settings
|
timmy.text_embedding_api_key |
Encrypted in system_settings
|
timmy.code_embedding_api_key |
Encrypted in system_settings
|
timmy.rerank_api_key |
Encrypted in system_settings
|
Timmy (the AI assistant) is entirely operational. There is no timmy: block in any bootstrap config file, and no TMI_TIMMY_* environment variables are required. Every timmy.* key — including the model selection, the provider, all API keys, and all tuning knobs — lives in system_settings.
This means:
- You can enable or disable Timmy at runtime through the admin API without restarting the server.
- You can rotate Timmy API keys through
/admin/settings/reencryptwithout touching config files. - A fresh development instance with no Timmy-related environment variables will pick up Timmy's configuration from the database defaults seeded on first run.
The broader principle: do not assume that because a setting holds a sensitive value, it must be a bootstrap setting. Check Configuration-Reference for each key's actual category.
Operational settings have a mutability property that tells you what happens when you change a value via the admin API.
| Mutability | Behavior |
|---|---|
| hot | The new value is read at the next use. No restart required. |
| static | The value was read once at boot. A restart is required for the change to take effect. |
If you edit a static operational setting via /admin/settings, the database row is updated immediately, but the running server continues using the value it loaded at startup until you restart it. The Configuration-Reference page lists the mutability of each setting.
Each setting has a visibility that controls where and to whom the value is exposed:
| Visibility | Where it appears |
|---|---|
internal |
Server-side only. Never included in any API response. |
admin-only |
Visible to administrators via the /admin config endpoints. |
public |
Exposed on the unauthenticated discovery/config endpoint. Examples: operator name, OAuth provider display data, server.base_url. |
A setting cannot be both public and a secret. This constraint is enforced at build time by a validation suite — if a registry entry is marked both public and secret, the server will refuse to start. This prevents accidental secret exposure through the discovery endpoint.
Three files hold bootstrap configuration. Each targets a deployment environment:
| File | Environment |
|---|---|
config-development.yml |
Local development |
config-test.yml |
Automated test runs |
config-production.yml |
Production deployment |
These files contain only bootstrap keys. Operational settings do not belong here and will be ignored if placed here.
All three working files are gitignored because they contain real secrets (database passwords, JWT signing secrets, etc.). Never commit them.
config-example.yml is generated from the classification registry by make generate-config-example. It contains all bootstrap keys with vault:// placeholders for secrets. Use it as a template when setting up a new environment:
cp config-example.yml config-development.yml
# Then replace the vault:// placeholders with real values or other reference schemesThe database backend is selected automatically from the URL scheme of database.url:
| URL scheme | Backend |
|---|---|
postgres:// |
PostgreSQL |
oracle:// |
Oracle Autonomous Database |
sqlite:// |
SQLite |
mysql:// |
MySQL |
sqlserver:// |
SQL Server |
See Database-Setup for backend-specific setup instructions.
Some bootstrap settings are marked Required in the registry. If any required setting has an empty effective value after the bootstrap cascade resolves, Config.ValidateRequired() aborts startup with a clear error that names the missing key.
Required settings include database.url, auth.jwt.secret, and server.port. You will not get a silently mis-configured server — a missing required value is a hard startup failure.
BOOTSTRAP settings
──────────────────
At rest: config-*.yml + TMI_* env vars
(working files are gitignored; secrets may be vault/env/file references)
At startup: built-in default
↓ overridden by
config-*.yml value
↓ overridden by
TMI_* environment variable ← wins
↓
secret reference resolved (vault:// / env:// / file://)
↓
ValidateRequired() — abort if any required key is empty
↓
value held in memory for server lifetime
OPERATIONAL settings
────────────────────
At rest: system_settings table (secrets encrypted at rest)
First run: built-in registry defaults → written to system_settings
At read: TMI_* environment variable ← wins if present ┐
↓ else │ env > file > db
config-*.yml value ← wins if present │ precedence
↓ else ┘
system_settings table ← the common case; runtime-editable
hot setting → precedence re-evaluated on each access
static setting → precedence evaluated at boot, held in memory
(edit DB value via PUT /admin/settings/{key} — returns 409 if the key
is pinned by an env/config value; rotate secrets via /admin/settings/reencrypt)
-
Configuring-Local-Development — set up
config-development.ymland the development environment - Bootstrapping-Production — production bootstrap config, secrets providers, TLS
- Managing-Operational-Settings — admin API for runtime config, secret rotation
- Configuration-Reference — full key listing with category, mutability, visibility, and description
- Using TMI for Threat Modeling
- Accessing TMI
- Authentication
- Identity Linking
- Creating Your First Threat Model
- Understanding the User Interface
- Working with Data Flow Diagrams
- Managing Threats
- Collaborative Threat Modeling
- Using Notes and Documentation
- Timmy AI Assistant
- Metadata and Extensions
- Planning Your Deployment
- Terraform Deployment (AWS, OCI, GCP, Azure)
- Deploying TMI Server
- OCI Container Deployment
- Certificate Automation
- Deploying TMI Web Application
- Setting Up Authentication
- Database Setup
- Bootstrapping Production
- Component Integration
- Post-Deployment
- Branding and Customization
- Monitoring and Health
- Cloud Logging
- Configuring Local Development
- Managing Operational Settings
- Content Extractors - Limits and Overrides
- Database Operations
- Database Security Strategies
- Transaction Isolation
- Oracle Content Feedback FK Cleanup
- Security Operations
- Performance and Scaling
- Maintenance Tasks
- Getting Started with Development
- Local Development Cluster
- Architecture and Design
- API Integration
- Testing
- Contributing
- Extending TMI
- Dependency Upgrade Plans
- DFD Graphing Library Reference
- Migration Instructions
- Issue Tracker Integration
- Webhook Integration
- Addon System
- MCP Integration
- Delegated Content Providers
- Setting Up Google Content Providers
- API Clients
- API Client Maintenance
- Database Tool Reference
- TMI Terraform Analyzer
- TMI Promtail Logger
- WebSocket Test Harness