"""Dual-read environment helper (D-108, REQ-159, G-106). During the Nova rebrand transition window (P2–P4), every `NOVA_*` environment variable is the preferred source, with the legacy `ACDL_*` name as the fallback. This keeps deployments from breaking while the keys are rotated across `.env`, `.env.secrets`, Gitea repo secrets, and operator-managed process environments. `get_env(name, default=None)` resolves `NOVA_` first, then falls back to `ACDL_`, then returns `default` if neither is set. This helper is removed (NOVA-only) in P5 (REQ-164). Direct-read paths that bypass this helper (the `.env.secrets` shell export in `scripts/run_platform.sh` and the Python parser in `core/regression_verify.py`) mirror this contract inline per the G-106 binding — see those sites for the dual-read shell/Python forms. """ from __future__ import annotations import os from typing import Optional __all__ = ["get_env"] def get_env(name: str, default: Optional[str] = None) -> Optional[str]: """Resolve a config value with a NOVA-preferred / ACDL-fallback read. `name` is the bare key WITHOUT the prefix (e.g. ``"AWS_ACCOUNT_ID"``). The lookup order is: 1. ``NOVA_`` (preferred) 2. ``ACDL_`` (legacy fallback, removed in P5) 3. ``default`` Returns the first value that is present and non-empty, or ``default`` if neither env var is set. An explicitly-set empty string is treated as "unset" so an operator cannot accidentally shadow the fallback with a blank NOVA key. """ nova_val = os.environ.get(f"NOVA_{name}") if nova_val: return nova_val acdl_val = os.environ.get(f"ACDL_{name}") if acdl_val: return acdl_val return default