3338ec1622
---ci--- project: acdl phase: 2 milestone: v1.28 status: execute persona: backend-engineer --- synthesize_local_env(contract_path, environment) reads a contract YAML and produces a purely synthetic local env dict (account_id=000000000000 placeholder, region='local', local state_backend, local network) that validates against schemas/environment.schema.json. Mirrors the shape of core/environments/*.json + core/onboarding.py:generate_env_file() (shape parity on the required env-binding keys). No cloud provisioning — purely synthetic for nova apply --local. tests/test_local_env.py: 13 tests (schema validation, region/account sentinels, env override, threshold per-env, shape parity, missing-file default).
125 lines
4.8 KiB
Python
125 lines
4.8 KiB
Python
"""Environment helper (D-108, REQ-159, REQ-164, REQ-330).
|
||
|
||
During the Nova rebrand transition window (P2–P4), `get_env` read
|
||
`NOVA_*` preferred with the legacy `ACDL_*` name as the fallback. **P5
|
||
(REQ-164) removed the fallback** — `get_env` now reads `NOVA_*` only.
|
||
|
||
`get_env(name, default=None)` resolves `NOVA_<name>`, then returns
|
||
`default` if unset. 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`) were updated to NOVA-only in P5
|
||
(the G-106 dual-read contract was retired with the fallback).
|
||
|
||
P2 (REQ-330): `synthesize_local_env(contract_path, environment)` produces
|
||
a purely synthetic local env dict (account_id placeholder, region
|
||
"local", no real AWS resources) from a contract YAML. Mirrors the shape
|
||
of core/environments/*.json (validates against
|
||
schemas/environment.schema.json) so `nova apply --local` can run the
|
||
contract resolver + Terraform adapter without provisioning cloud
|
||
resources. This is the local-tier counterpart of
|
||
core/onboarding.py:generate_env_file() (the request-path binding
|
||
generator).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
from pathlib import Path
|
||
from typing import Any, Dict, Optional
|
||
|
||
import yaml
|
||
|
||
__all__ = ["get_env", "synthesize_local_env"]
|
||
|
||
|
||
def get_env(name: str, default: Optional[str] = None) -> Optional[str]:
|
||
"""Resolve a config value from the `NOVA_*` environment.
|
||
|
||
`name` is the bare key WITHOUT the prefix (e.g. ``"AWS_ACCOUNT_ID"``).
|
||
Returns ``NOVA_<name>`` if set and non-empty, else ``default``.
|
||
"""
|
||
val = os.environ.get(f"NOVA_{name}")
|
||
if val:
|
||
return val
|
||
return default
|
||
|
||
|
||
# Default confidence thresholds per environment name (mirrors the schema
|
||
# description: dev 0.50, qa 0.75, prod 0.90, dr 0.95). Used by
|
||
# synthesize_local_env so the synthetic env matches the real env semantics.
|
||
_DEFAULT_THRESHOLDS: Dict[str, float] = {
|
||
"dev": 0.50,
|
||
"qa": 0.75,
|
||
"prod": 0.90,
|
||
"dr": 0.95,
|
||
}
|
||
|
||
|
||
def synthesize_local_env(
|
||
contract_path: str,
|
||
environment: Optional[str] = None,
|
||
) -> Dict[str, Any]:
|
||
"""Synthesize a local env dict from a contract YAML (REQ-330).
|
||
|
||
Reads the contract YAML (``yaml.safe_load``), derives a placeholder
|
||
environment binding that ``nova apply --local`` can use WITHOUT
|
||
provisioning real AWS resources. The produced dict:
|
||
|
||
- ``name`` — the environment name (from the arg or the contract's
|
||
``environment`` field, defaulting to ``"dev"``).
|
||
- ``account_id`` — ``"000000000000"`` (the schema-allowed placeholder
|
||
for an unbound environment; real account id filled by the platform).
|
||
- ``region`` — ``"local"`` (the local-tier sentinel; never a real
|
||
AWS region).
|
||
- ``state_backend`` — ``{bucket: "local-tfstate", lock_table:
|
||
"local-locks"}`` (local state; LocalS3StateBackend rewrites the
|
||
terraform backend to ``backend "local"`` using the stack name as
|
||
the state path, so no S3 bucket is used).
|
||
- ``network`` — a local RFC1918 CIDR + a single fake AZ.
|
||
- ``runner_role_arn`` — a placeholder ARN for the local tier.
|
||
- ``autonomy`` — ``"full"`` (the local tier is autonomous).
|
||
- ``confidence_threshold`` — the per-env default (0.50 for dev).
|
||
|
||
The dict mirrors the shape of ``core/environments/*.json`` and
|
||
validates against ``schemas/environment.schema.json``. No cloud
|
||
provisioning occurs — purely synthetic.
|
||
|
||
Args:
|
||
contract_path: Path to the contract YAML file.
|
||
environment: Optional environment name override (defaults to the
|
||
contract's ``environment`` field, or ``"dev"``).
|
||
|
||
Returns:
|
||
The synthetic local env dict.
|
||
"""
|
||
contract_path_obj = Path(contract_path)
|
||
contract: Dict[str, Any] = {}
|
||
if contract_path_obj.is_file():
|
||
with open(contract_path_obj) as fh:
|
||
contract = yaml.safe_load(fh) or {}
|
||
|
||
env_name = environment or contract.get("environment", "dev")
|
||
stack_name = contract.get("id", env_name)
|
||
threshold = _DEFAULT_THRESHOLDS.get(env_name, 0.50)
|
||
|
||
return {
|
||
"name": env_name,
|
||
"description": (
|
||
f"Synthetic local-tier environment for contract '{stack_name}' "
|
||
f"(environment={env_name}). No real AWS resources — generated "
|
||
f"by core.env.synthesize_local_env (REQ-330) for nova apply --local."
|
||
),
|
||
"account_id": "000000000000",
|
||
"region": "local",
|
||
"state_backend": {
|
||
"bucket": "local-tfstate",
|
||
"lock_table": "local-locks",
|
||
},
|
||
"network": {
|
||
"vpc_cidr": "10.250.0.0/16",
|
||
"azs": ["local-a"],
|
||
},
|
||
"runner_role_arn": "arn:aws:iam::000000000000:role/local-runner",
|
||
"autonomy": "full",
|
||
"confidence_threshold": threshold,
|
||
} |