Code-owned configuration
Workflow profiles, roles and their schemas, topics, MCP server definitions, agents, scan schemes and the domain dictionary can be owned by the database or by the code that declares them. One dial decides, and every entry can override it. With code ownership, the same git commit produces the same configuration locally, in staging and in production, without wiping a database.
Turn it on
Section titled “Turn it on”await start({ database: { connectionString: process.env.DATABASE_URL }, configSource: 'code', // declarations apply on every boot workers: WORKERS, roles: ROLES, topics: TOPICS, agents: AGENTS, scanSchemes: SCHEMES,});configSource |
Boot behavior | Who edits |
|---|---|---|
'db' (default) |
Insert each declared record if absent. When code and database disagree, log config drift: and leave the row alone. |
Admins, live, in the dashboard and API |
'code' |
Compare each declaration with the database and apply the difference. An unchanged declaration writes nothing. | Code, through git and CI/CD. Dashboard edits to declared fields are replaced on the next deploy. |
Per-entry reset
Section titled “Per-entry reset”Every entry accepts reset, which wins over the dial in either direction:
configSource: 'code',topics: [ { topic: 'app.orders.created' }, // follows the dial: code-owned { topic: 'app.ops.notes', reset: false }, // db-owned: admins keep editing it],The domain dictionary’s override is mcp.domainDictionaryReset.
What each surface writes
Section titled “What each surface writes”| Surface | Declared in | A code-owned apply writes | Left alone |
|---|---|---|---|
| Workflow profiles | workers[].config |
The full profile, including role lists, input_schema and input_lookups |
Task history, escalations |
| Roles | roles[] |
Declared fields only; escalation targets replace | Membership, undeclared fields |
| Topics | topics[] |
The whole catalog entry | last_seen_at |
| MCP servers | mcp.serverFactories[].config |
Description, tags, category, compile hints, credential providers | Tool manifest, connection status |
| Agents | agents[] |
Description, status, goals, rules, domain, schedules | Capabilities, metadata, run history |
| Agent subscriptions | agents[].subscriptions |
Reaction fields per agent and topic | enabled, the admin switch |
| Scan schemes | scanSchemes[] |
The scheme and its rules, as one unit | |
| Domain dictionary | mcp.domainDictionary or mcp.domainDictionaryPath |
The whole document, one version per change | |
| Graph workflows | graphWorkflows[] |
Always compare and apply; redeploy on a YAML version bump | Run history |
roles[] declares titles, versioned form, metadata and list schemas, escalation targets, upstream inputs, Pace Board dials, pins and portals:
export const ROLES = [ { role: 'packer', title: 'Packer', form_schema: PACKER_FORM, escalation_targets: ['lead'], sla_minutes: 30, target_per_hour: 40, properties: { kiosk: true }, },];Only declared fields are written. An omitted field keeps whatever the database holds, under either mode. Under 'db', the declaration is written once when the role is first created.
Schema versions advance
Section titled “Schema versions advance”Role schemas version through the same write path as the dashboard. A declaration whose form_schema or metadata_schema differs from the stored pair advances the role’s schema version and snapshots the new pair with the change summary startup apply (code).
In-flight escalations keep the version they were created against, so their forms keep working while new rows get the new shape. Version history only grows: an apply never rewinds or rewrites a version. list_schema versions on its own timeline.
Scan schemes
Section titled “Scan schemes”A scheme and its rules are one ownership unit, so one reset covers both:
scanSchemes: [ { version: 12, name: 'Serial locate', target_facet: 'serialNumber', encoding: 'fixed', target_length: 8, rules: [{ category: '1', name: 'Locate', steps: [{ verb: 'show-detail' }] }], },],The scheme applies before its rules, so rule validation sees the scheme’s kind. The pass runs whether or not features.scanCodes is on.
Orphans are reported, never deleted
Section titled “Orphans are reported, never deleted”Each surface logs one line per boot:
[long-tail] config apply (workflows): applied 2, unchanged 31, db-owned 0[long-tail] config apply (roles): applied 1, unchanged 11, db-owned 0, orphans: [old-station]An orphan is a database record of a declared surface with no matching declaration. Orphans are logged at warn level and left in place. Removing a workflow config, role, topic or agent stays a deliberate act through the dashboard or API.
| Surface | Orphan candidates |
|---|---|
| Roles | Rows with real configuration (a title or a form schema). Bare rows created for foreign keys and membership-only roles never appear. Skipped when examples: true. |
| Scan schemes | Rules in the database but absent from a code-owned scheme, reported as version/category |
| Agent subscriptions | Subscriptions not declared in code |
Concurrency
Section titled “Concurrency”Containers that boot together serialize their configuration pass on a dedicated Postgres advisory lock, separate from the migration lock. Each upsert also carries an IS DISTINCT FROM guard, so an identical declaration from two containers writes once and no-ops once.
Display time zone
Section titled “Display time zone”display.timeZone (an IANA name) renders every dashboard date in that zone: list columns, station facts and detail pages. Unset, dates follow the viewer’s browser. An unknown zone fails the boot.
await start({ database: { connectionString: process.env.DATABASE_URL }, display: { timeZone: 'America/Chicago' },});