domains / orchestration / apple/container
Config Resolution Discipline
When adding/updating configuration handling, resolve configuration once into a single immutable runtime context, and make precedence/fallback behavior explicit.
When adding/updating configuration handling, resolve configuration once into a single immutable runtime context, and make precedence/fallback behavior explicit.
Rules
- Never use
try!for config loading. Preferthrowsat the boundary and fail fast with a clear error message. - Avoid threading raw config through long call chains. Instead, resolve a final config once (including user overrides) and inject it at module boundaries as an immutable context.
- Define precedence explicitly (document it in code): e.g.
environment variables -> user overrides file -> system config.- If env values may change during process lifetime, either (a) read env only once when resolving the immutable config, or (b) explicitly implement re-resolution/hot reload—don’t accidentally mix both.
- Don’t couple unrelated utilities to system config. If a helper doesn’t conceptually need system config, refactor it to accept only the minimal parameters it truly requires.
- Keep models OS-agnostic. Move OS/Linux-specific settings into runtime/OS-specific payloads (encoded/decoded via runtime configuration), not into generic cross-platform configuration types.
- Respect explicit user intent for derived behavior. Only set derived environment variables/forwarding behavior when the corresponding flag/config input was explicitly provided; otherwise define clear fallback behavior (and only when input is absent).
Sketch
struct RuntimeConfigContext: Sendable {
let finalConfig: ContainerSystemConfig
}
enum ConfigError: Error {
case loadFailed(String)
}
func resolveRuntimeConfig() throws -> RuntimeConfigContext {
do {
let system = try SystemRuntimeOptions.loadConfig() // throws, no try!
// precedence: env -> user file -> system
let userOverrides = try loadUserOverrides() // throws
let envOverrides = loadEnvOverrides() // evaluate now
let merged = merge(system: system, user: userOverrides, env: envOverrides)
return RuntimeConfigContext(finalConfig: merged)
} catch {
throw ConfigError.loadFailed("Failed to resolve runtime config: \(error)")
}
}
// Inject at boundaries
let ctx = try resolveRuntimeConfig()
startServices(with: ctx) // services/utilities can use ctx.finalConfig without plumb-through
Apply these rules to new config flows and refactors: they prevent unsafe initialization, reduce coupling, make override behavior predictable, and keep platform-specific concerns contained.