A flat, typed registry of every config key your product reads — with a real, enforced answer to "what value is this tenant actually getting, and why."
Every key can be set at up to four scopes: global, environment, template, and tenant. Resolving a key for a given tenant walks those layers in order and returns the most specific one that's actually set — global is the fallback everyone gets by default; a tenant override always wins over its template, which always wins over its environment, which always wins over global.
That order isn't a convention your team has to remember and apply consistently by hand — it's enforced by the resolution engine itself, the same code path for the dashboard, the REST API, and the SDK. There's exactly one place that decides what's effective, and it's not a person's memory of which override shipped last.
Every resolved value comes with a source: which scope set it, its version, and whether it's the layer that's currently winning. When a tenant reports a value that looks wrong, the question "where did this actually come from" has a direct answer in the Registry's provenance rail — not a grep across four systems and a Slack thread.
Keys are typed (string, number, boolean, JSON, enum) so a value can't silently drift into the wrong shape. Every write at every scope is versioned independently, so a tenant's override history and a template's default history are each their own timeline — and either one can be rolled back to a prior version without guessing what the old value even was, because the ledger already has it.
Not everything is a single value. Configs extends the same scope hierarchy to named, nested JSON documents — routing rules, SLA policies, whatever your product needs as structured config rather than a flat key — resolved with the exact same global → environment → template → tenant precedence, so the mental model never has to change just because the shape of the data did.