ADR-0019: Unified org_module_configs Table for Per-Org Module Configuration
Status
AcceptedTags
fieldforce, org-config, jsonb, multi-tenancy, modulesDecision
Per-org module configuration is stored in a singleorg_module_configs(org_id, module, config JSONB) table with a unique index on (org_id, module). Each module owns its config shape as a JSONB blob. Missing rows return Go-hardcoded defaults. Platform-wide defaults are version-controlled in Go code, not a database table.
Why
Every new module that introduces org-configurable settings would otherwise require its own migration and table (org_fieldforce_configs, org_leave_configs, etc.). A unified table allows any module to store its config without schema changes — only a new module value is needed. The JSONB shape per module is validated by the Go service layer, not the database, keeping the schema open for evolution.
Rejected alternatives:
- Per-module tables:
org_fieldforce_configs,org_ai_configs, etc. One migration per new module, no unified API, no consistent query pattern.org_ai_configsalready exists with this pattern and stays as-is (it handles encrypted keys that require special treatment). Rejected for new modules. - EAV
org_configs(org_id, key, value text): Maximum flexibility, zero type safety. Structured config values become awkward string blobs. Rejected. - Platform-editable defaults in DB (
platform_module_configstable): Adds admin UI complexity with no urgency. Defaults belong in code where they are version-controlled and reviewed. Deferred.
How it works
Known limitations
- Dual config patterns coexist:
org_ai_configs(legacy, encrypted key column) andorg_module_configs(new, general-purpose JSONB). Future cleanup should foldorg_ai_configsintoorg_module_configs. Until then, two patterns exist in the codebase. - No DB-level schema enforcement: JSONB accepts any structure — an incorrect config shape silently stores garbage. The Go service layer is the only validation point. Future modules must implement their own validation on write.
Rules for agents
- New modules adding org-configurable settings MUST use
org_module_configs— do not create new per-module config tables - Go services MUST merge stored config with hardcoded defaults field-by-field — a partial row (org set only some fields) must return full defaults for unset fields
PUT .../configs/:moduleMUST be restricted to Admin/Owner role only- Never return raw JSONB to the client — always unmarshal, merge with defaults, and return a typed struct
org_ai_configsremains separate — do not attempt to move encrypted API keys intoorg_module_configs