Status: superseded · ADR-19 · Filed 2026-04-19
Decision
Split all environment configuration into two named buckets: HOST_ENV (discovered facts about the host machine) and PRISM_ENV (Prism’s chosen wiring). Resolve each via a 4-tier cascade: (1) system facts, (2) environment variables, (3) config files, (4) MODE_PROFILES in-code constant. Log the resolved source (tier + key) for every value at backend startup. Application code never branches on PRISM_MODE — all mode-awareness concentrates in MODE_PROFILES. Full contract in SPEC-019.
Rationale
Prior code mixed host-discovery and Prism-wiring inline, using platform.system() at runtime (violates the project_host_contract: install owns detection, runtime reads env). Install-time branches taught the codebase about modes rather than treating mode as data. Two-bucket + tiered resolution (a) concentrates mode-awareness to exactly one place (MODE_PROFILES), (b) eliminates runtime OS probing, (c) makes every config value’s source visible at startup via provenance logs, (d) gives developers two semantically-distinct names (HOST_ENV / PRISM_ENV) that are self-documenting — Prism’s ‘soft’ principle (env-first, config-aware, one codebase) is now structurally enforced rather than just documented. Wave 4B’s FQDN gotcha (ADR-18) is an instance of what this prevents in the future: no silent resolver-search-path dependencies, no bare hostnames slipping through, no cloud-mode plain-http misconfig.
Alternatives Considered
(a) Single pydantic BaseSettings class as today: fails to distinguish discovered vs chosen, hides tier precedence, no provenance. (b) One giant .env per mode: rigid, hand-maintained, no system-fact tier, duplicates lines across modes. (c) External config service (consul/etcd): operational overhead unjustified at current scale. (d) Keep mixed model + document rules: fails Explicit-Over-Implicit (rules documented but not enforced by structure). (e) Feature-flag library (LaunchDarkly, flagd): wrong abstraction — we have modes, not experiments.