Description
Adopt the layered multi-config-file pattern from #2865 in the Platform API (platform-api) 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(platform_api) 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: omitting -config, or passing a path that does not exist/parse, must fail fast (non-zero exit) before serving — never silently boot on env + built-in defaults. This matches the fail-closed startup posture (GO-AUTH-011) and the "required, no default" decision standardized across all Go components.
- Correct the now-misleading flag help text ("optional; env vars take priority over the file"): there is no koanf env provider, so env values reach config only through explicit
{{ env }} interpolation tokens, not as an independent override source.
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.
Affected code
platform-api/cmd/main.go — replace the single flag.String("config", ...) with a repeatable flag.Var slice; require at least one file.
platform-api/config/config.go — LoadConfig becomes variadic, looping k.Load(file.Provider(p), toml.Parser()) over all paths in order using a StrictMerge koanf instance before the existing Cut/interpolate/unmarshal; the configFilePath singleton var and SetConfigPath setter become slice-based.
platform-api/platform/options.go — add a repeatable/variadic WithConfigPaths option alongside WithConfigPath.
Version
No response
Related Issue
#2865
Description
Adopt the layered multi-config-file pattern from #2865 in the Platform API (
platform-api) 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(platform_api)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: omitting-config, or passing a path that does not exist/parse, must fail fast (non-zero exit) before serving — never silently boot on env + built-in defaults. This matches the fail-closed startup posture (GO-AUTH-011) and the "required, no default" decision standardized across all Go components.{{ env }}interpolation tokens, not as an independent override source.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.Affected code
platform-api/cmd/main.go— replace the singleflag.String("config", ...)with a repeatableflag.Varslice; require at least one file.platform-api/config/config.go—LoadConfigbecomes variadic, loopingk.Load(file.Provider(p), toml.Parser())over all paths in order using aStrictMergekoanf instance before the existingCut/interpolate/unmarshal; theconfigFilePathsingleton var andSetConfigPathsetter become slice-based.platform-api/platform/options.go— add a repeatable/variadicWithConfigPathsoption alongsideWithConfigPath.Version
No response
Related Issue
#2865