Description
Adopt the layered multi-config-file pattern from #2865 in the AI Workspace BFF (portals/ai-workspace/bff) so configuration can be a complete base file plus thin environment/deployment overlays, and unify its config-flag behavior with the rest of the platform's Go components.
Behavior
- The
-config flag becomes repeatable (e.g. -config base.toml -config prod.toml). Files are merged in the order given with last-wins precedence (a key set in a later file overrides the same key from an earlier file).
- Merge semantics follow koanf: nested tables (maps) deep-merge; list/array values are replaced, not appended. This must be documented so overlays that set list keys are understood to replace the base list.
- Use koanf
StrictMerge so a type-mismatched override across files fails loudly rather than silently.
{{ env }} / {{ file }} interpolation runs once, after all files are merged (and after the existing Cut(ai_workspace) subtree narrowing), so a token declared in the base can be resolved by a later overlay. Secrets stay in interpolation sources, not in the overlay TOMLs.
-config becomes required and there is no default config file path: remove the DefaultConfigFile = "/etc/ai-workspace/config.toml" fallback and the "missing file is a no-op → defaults apply" behavior. Omitting -config, or passing a path that does not exist/parse, must fail fast (non-zero exit) before serving — never silently boot on built-in defaults. This matches the fail-closed startup posture (GO-AUTH-011) and the "required, no default" decision standardized across all Go components.
Rationale for unification
All four Go components (gateway-controller, gateway policy-engine, platform-api, AI Workspace BFF) currently disagree on what happens when -config is omitted (required vs. optional-defaults vs. default-path). This unifies them on: repeatable -config, at least one file required, no default path, no silent-defaults fallback. Env values reach config only through explicit {{ env }} interpolation tokens (there is no koanf env provider) — which is exactly why a missing config file must fail fast rather than start with no deployment configuration at all.
Affected code
portals/ai-workspace/bff/main.go — replace the single flag.String("config", ...) with a repeatable flag.Var slice; require at least one file.
portals/ai-workspace/bff/internal/config/config.go — Load becomes variadic; drop the DefaultConfigFile fallback.
portals/ai-workspace/bff/internal/config/settings.go — loadConfigKoanf loops k.Load(file.Provider(p), toml.Parser()) over all paths in order using a StrictMerge koanf instance; error on a missing/unreadable explicitly-passed file, then Cut/interpolate/unmarshal on the merged result.
Version
No response
Related Issue
#2865
Description
Adopt the layered multi-config-file pattern from #2865 in the AI Workspace BFF (
portals/ai-workspace/bff) so configuration can be a complete base file plus thin environment/deployment overlays, and unify its config-flag behavior with the rest of the platform's Go components.Behavior
-configflag becomes repeatable (e.g.-config base.toml -config prod.toml). Files are merged in the order given with last-wins precedence (a key set in a later file overrides the same key from an earlier file).StrictMergeso a type-mismatched override across files fails loudly rather than silently.{{ env }}/{{ file }}interpolation runs once, after all files are merged (and after the existingCut(ai_workspace)subtree narrowing), so a token declared in the base can be resolved by a later overlay. Secrets stay in interpolation sources, not in the overlay TOMLs.-configbecomes required and there is no default config file path: remove theDefaultConfigFile = "/etc/ai-workspace/config.toml"fallback and the "missing file is a no-op → defaults apply" behavior. Omitting-config, or passing a path that does not exist/parse, must fail fast (non-zero exit) before serving — never silently boot on built-in defaults. This matches the fail-closed startup posture (GO-AUTH-011) and the "required, no default" decision standardized across all Go components.Rationale for unification
All four Go components (gateway-controller, gateway policy-engine, platform-api, AI Workspace BFF) currently disagree on what happens when
-configis omitted (required vs. optional-defaults vs. default-path). This unifies them on: repeatable-config, at least one file required, no default path, no silent-defaults fallback. Env values reach config only through explicit{{ env }}interpolation tokens (there is no koanf env provider) — which is exactly why a missing config file must fail fast rather than start with no deployment configuration at all.Affected code
portals/ai-workspace/bff/main.go— replace the singleflag.String("config", ...)with a repeatableflag.Varslice; require at least one file.portals/ai-workspace/bff/internal/config/config.go—Loadbecomes variadic; drop theDefaultConfigFilefallback.portals/ai-workspace/bff/internal/config/settings.go—loadConfigKoanfloopsk.Load(file.Provider(p), toml.Parser())over all paths in order using aStrictMergekoanf instance; error on a missing/unreadable explicitly-passed file, thenCut/interpolate/unmarshal on the merged result.Version
No response
Related Issue
#2865