ADR-076 made a database section's normalization errors address themselves — ConfigError.Field reads databases.reporting.host or multitenant.tenants.acme.database.host instead of the root spelling. It deliberately stopped at the startup door. The result is a config package where a consumer can address some section failures and not others, and the split does not follow any line a consumer can see.
Where the spelling still doesn't match
The runtime door. database/manager.go (pool defaults for a dynamically-resolved tenant) and tools/migration/internal/commands/dbtls.go both hold the tenant key and both attach it by wrapping, leaving Field root-spelled. So the same tenant reports two different fields depending on how it was declared:
| tenant declared |
Field |
statically, in multitenant.tenants.acme.database |
multitenant.tenants.acme.database.tls |
dynamically, via DBConfigProvider |
database.tls (key only in the message) |
C60.16 tells consumers to route per-tenant failures with strings.HasPrefix(cfgErr.Field, "multitenant.tenants."). That advice works for static tenants and silently never fires for dynamic ones — the worst shape for a routing rule.
The rest of the tenant tree. Three more spellings for sibling failures of the same tenant:
- tenant cache: wrapped as
tenant <id> cache: …, Field left cache.*
checkTenantMessagingConsistency: Field: "multitenant.tenants messaging" — a space, not a path
NewMultiTenantError: Field: "tenant 'acme' database"
So Field is a koanf path for one failure class and prose for its neighbours.
Why it wasn't fixed in ADR-076
The connect door genuinely has no section — that is ADR-050's asymmetry, and #1025's brief explicitly excluded it. But the callers of the connect door do have the key, which is why this is worth deciding rather than assuming.
ADR-076's "Alternatives considered" names the shape that would dissolve this: give normalizeDatabaseValues the section (or a field prefix) so its errors are born addressed — one parameter at one seam, no prefix surgery, and Action gets built from the right head too (see the Action half in the companion issue). That refactor is the natural home for fixing the runtime door as well.
Suggested scope
- Decide whether
Field is a koanf path always, or prose sometimes — then make the tenant tree consistent with that answer.
- If it is a path always, qualify at the two runtime-door call sites that already hold the key, and update C60.16's routing advice.
ADR-076 made a database section's normalization errors address themselves —
ConfigError.Fieldreadsdatabases.reporting.hostormultitenant.tenants.acme.database.hostinstead of the root spelling. It deliberately stopped at the startup door. The result is a config package where a consumer can address some section failures and not others, and the split does not follow any line a consumer can see.Where the spelling still doesn't match
The runtime door.
database/manager.go(pool defaults for a dynamically-resolved tenant) andtools/migration/internal/commands/dbtls.goboth hold the tenant key and both attach it by wrapping, leavingFieldroot-spelled. So the same tenant reports two different fields depending on how it was declared:Fieldmultitenant.tenants.acme.databasemultitenant.tenants.acme.database.tlsDBConfigProviderdatabase.tls(key only in the message)C60.16 tells consumers to route per-tenant failures with
strings.HasPrefix(cfgErr.Field, "multitenant.tenants."). That advice works for static tenants and silently never fires for dynamic ones — the worst shape for a routing rule.The rest of the tenant tree. Three more spellings for sibling failures of the same tenant:
tenant <id> cache: …,Fieldleftcache.*checkTenantMessagingConsistency:Field: "multitenant.tenants messaging"— a space, not a pathNewMultiTenantError:Field: "tenant 'acme' database"So
Fieldis a koanf path for one failure class and prose for its neighbours.Why it wasn't fixed in ADR-076
The connect door genuinely has no section — that is ADR-050's asymmetry, and #1025's brief explicitly excluded it. But the callers of the connect door do have the key, which is why this is worth deciding rather than assuming.
ADR-076's "Alternatives considered" names the shape that would dissolve this: give
normalizeDatabaseValuesthe section (or a field prefix) so its errors are born addressed — one parameter at one seam, no prefix surgery, andActiongets built from the right head too (see theActionhalf in the companion issue). That refactor is the natural home for fixing the runtime door as well.Suggested scope
Fieldis a koanf path always, or prose sometimes — then make the tenant tree consistent with that answer.