docs(milestone): complete v1.15 — Nova Rebrand (tag v1.15.4)
P5 final-review-ship complete: dual-read fallback removed (REQ-164) — core/env.py NOVA-only, .env.secrets load paths NOVA-only (G-106 retired), nova_tagging.py hard-fails any acdl:* tag, legacy ACDL_* Gitea secrets deleted, ACDL_LIFECYCLE_MODE/ACDL_LOCAL_TIER/ACDL_HITL_* exports removed from scripts, SNS subject → Nova SoD halt (P1-2), bootstrap scripts NOVA-only. Review: 2 P0 auto-fixed (duplicate delenv), P1-1/P1-2 resolved, doc-drift fixed. Audit: tags v1.15.0-4 exist; traceability REQ-155..164 all complete; ARCHITECTURE naming table matches codebase. 615 pytest PASS; run_ci.sh 3-stage PASS. NOVA_MIGRATION.md marked COMPLETE. ---ci--- project: acdl phase: 5 milestone: v1.15 status: complete phase_role: final requirements: covered: [REQ-155, REQ-156, REQ-157, REQ-158, REQ-159, REQ-160, REQ-161, REQ-162, REQ-163, REQ-164] partial: [] ---/ci---
This commit is contained in:
+25
-25
@@ -1,14 +1,11 @@
|
||||
"""Unit tests for the dual-read env helper (core/env.py, D-108, REQ-159).
|
||||
"""Unit tests for the NOVA-only env helper (core/env.py, D-108, REQ-164).
|
||||
|
||||
Covers the four cases:
|
||||
- both NOVA_* and ACDL_* set (NOVA wins)
|
||||
- only NOVA_* set
|
||||
- only ACDL_* set (fallback)
|
||||
- neither set (default returned)
|
||||
|
||||
The ACDL_* fallback is the intentional dual-read source and is removed
|
||||
in P5 (REQ-164). These fixtures deliberately keep the ACDL_* names as
|
||||
the fallback source — they are the one allowed ACDL_* reference.
|
||||
P5 (REQ-164) removed the ACDL_* dual-read fallback. `get_env` now reads
|
||||
`NOVA_*` only. These tests verify:
|
||||
- only NOVA_* set → returned
|
||||
- neither set → default returned
|
||||
- ACDL_* set but NOVA_* unset → default returned (fallback REMOVED)
|
||||
- blank NOVA_* → default returned (not the ACDL_* value)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -27,30 +24,33 @@ def _isolate_env(monkeypatch):
|
||||
yield
|
||||
|
||||
|
||||
def test_both_set_nova_wins(monkeypatch):
|
||||
monkeypatch.setenv("NOVA_AWS_ACCOUNT_ID", "nova-value")
|
||||
monkeypatch.setenv("ACDL_AWS_ACCOUNT_ID", "acdl-value")
|
||||
assert env.get_env("AWS_ACCOUNT_ID") == "nova-value"
|
||||
|
||||
|
||||
def test_only_nova_set(monkeypatch):
|
||||
monkeypatch.setenv("NOVA_AWS_ACCOUNT_ID", "nova-value")
|
||||
assert env.get_env("AWS_ACCOUNT_ID") == "nova-value"
|
||||
|
||||
|
||||
def test_only_acdl_set_fallback(monkeypatch):
|
||||
# ACDL_* is the intentional dual-read fallback source (removed in P5).
|
||||
monkeypatch.setenv("ACDL_AWS_ACCOUNT_ID", "acdl-value")
|
||||
assert env.get_env("AWS_ACCOUNT_ID") == "acdl-value"
|
||||
|
||||
|
||||
def test_neither_set_returns_default():
|
||||
assert env.get_env("AWS_ACCOUNT_ID") is None
|
||||
assert env.get_env("AWS_ACCOUNT_ID", default="581513795199") == "581513795199"
|
||||
|
||||
|
||||
def test_blank_nova_falls_back_to_acdl(monkeypatch):
|
||||
# An explicitly-empty NOVA key must not shadow the ACDL fallback.
|
||||
def test_only_acdl_set_no_fallback(monkeypatch):
|
||||
# P5 (REQ-164): ACDL_* fallback removed — ACDL_* alone returns default.
|
||||
monkeypatch.setenv("ACDL_AWS_ACCOUNT_ID", "acdl-value")
|
||||
assert env.get_env("AWS_ACCOUNT_ID") is None
|
||||
assert env.get_env("AWS_ACCOUNT_ID", default="fallback") == "fallback"
|
||||
|
||||
|
||||
def test_both_set_nova_wins(monkeypatch):
|
||||
# NOVA_* takes precedence; ACDL_* is ignored.
|
||||
monkeypatch.setenv("NOVA_AWS_ACCOUNT_ID", "nova-value")
|
||||
monkeypatch.setenv("ACDL_AWS_ACCOUNT_ID", "acdl-value")
|
||||
assert env.get_env("AWS_ACCOUNT_ID") == "nova-value"
|
||||
|
||||
|
||||
def test_blank_nova_returns_default_not_acdl(monkeypatch):
|
||||
# An explicitly-empty NOVA key returns default (not an ACDL_* value).
|
||||
monkeypatch.setenv("NOVA_AWS_ACCOUNT_ID", "")
|
||||
monkeypatch.setenv("ACDL_AWS_ACCOUNT_ID", "acdl-value")
|
||||
assert env.get_env("AWS_ACCOUNT_ID") == "acdl-value"
|
||||
assert env.get_env("AWS_ACCOUNT_ID") is None
|
||||
assert env.get_env("AWS_ACCOUNT_ID", default="d") == "d"
|
||||
@@ -32,15 +32,15 @@ class TestLifecycleModeFlag:
|
||||
"""REQ-134: the lifecycle scripts dispatch to plan-only by default."""
|
||||
|
||||
@pytest.mark.parametrize("script", SCRIPTS)
|
||||
def test_script_reads_acdl_lifecycle_mode(self, script):
|
||||
"""Every lifecycle script reads NOVA_LIFECYCLE_MODE (dual-read with
|
||||
ACDL_LIFECYCLE_MODE fallback) with a 'plan' default."""
|
||||
def test_script_reads_nova_lifecycle_mode(self, script):
|
||||
"""Every lifecycle script reads NOVA_LIFECYCLE_MODE (NOVA-only since
|
||||
P5, REQ-164 — dual-read fallback removed) with a 'plan' default."""
|
||||
src = _read(script)
|
||||
# P2 (REQ-159): dual-read NOVA_* preferred, ACDL_* fallback. The
|
||||
# ACDL_LIFECYCLE_MODE:-plan substring is still present inside the
|
||||
# nested dual-read expression (removed in P5).
|
||||
assert "${NOVA_LIFECYCLE_MODE:-${ACDL_LIFECYCLE_MODE:-plan}}" in src, \
|
||||
f"{script} must dual-read NOVA_/ACDL_LIFECYCLE_MODE defaulting to 'plan'"
|
||||
# P5 (REQ-164): NOVA_* only (dual-read fallback removed).
|
||||
assert "${NOVA_LIFECYCLE_MODE:-plan}" in src, \
|
||||
f"{script} must read NOVA_LIFECYCLE_MODE defaulting to 'plan'"
|
||||
assert "ACDL_LIFECYCLE_MODE" not in src, \
|
||||
f"{script} must not reference ACDL_LIFECYCLE_MODE (P5 removed the fallback)"
|
||||
assert "LIFECYCLE_MODE=" in src, \
|
||||
f"{script} must assign LIFECYCLE_MODE from the env var"
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ class TestPublishToSsm:
|
||||
monkeypatch.setenv("AWS_DEFAULT_REGION", "us-east-1")
|
||||
monkeypatch.setenv("AWS_ACCESS_KEY_ID", "testing")
|
||||
monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "testing")
|
||||
monkeypatch.setenv("ACDL_KMS_KEY_ID", "alias/aws/ssm")
|
||||
monkeypatch.setenv("NOVA_KMS_KEY_ID", "alias/aws/ssm")
|
||||
|
||||
with mock_aws():
|
||||
ssm = boto3.client("ssm", region_name="us-east-1")
|
||||
@@ -67,7 +67,7 @@ class TestPublishToSsm:
|
||||
monkeypatch.setenv("AWS_DEFAULT_REGION", "us-east-1")
|
||||
monkeypatch.setenv("AWS_ACCESS_KEY_ID", "testing")
|
||||
monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "testing")
|
||||
monkeypatch.setenv("ACDL_KMS_KEY_ID", "alias/aws/ssm")
|
||||
monkeypatch.setenv("NOVA_KMS_KEY_ID", "alias/aws/ssm")
|
||||
|
||||
with mock_aws():
|
||||
ssm = boto3.client("ssm", region_name="us-east-1")
|
||||
@@ -82,7 +82,7 @@ class TestPublishToSsm:
|
||||
monkeypatch.setenv("AWS_DEFAULT_REGION", "us-east-1")
|
||||
monkeypatch.setenv("AWS_ACCESS_KEY_ID", "testing")
|
||||
monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "testing")
|
||||
monkeypatch.setenv("ACDL_KMS_KEY_ID", "alias/aws/ssm")
|
||||
monkeypatch.setenv("NOVA_KMS_KEY_ID", "alias/aws/ssm")
|
||||
|
||||
with mock_aws():
|
||||
ssm = boto3.client("ssm", region_name="us-east-1")
|
||||
@@ -105,7 +105,7 @@ class TestPublishToSsm:
|
||||
monkeypatch.setenv("AWS_DEFAULT_REGION", "us-east-1")
|
||||
monkeypatch.setenv("AWS_ACCESS_KEY_ID", "testing")
|
||||
monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "testing")
|
||||
monkeypatch.setenv("ACDL_KMS_KEY_ID", "alias/aws/ssm")
|
||||
monkeypatch.setenv("NOVA_KMS_KEY_ID", "alias/aws/ssm")
|
||||
|
||||
with mock_aws():
|
||||
ssm = boto3.client("ssm", region_name="us-east-1")
|
||||
@@ -123,7 +123,7 @@ class TestPublishToSsm:
|
||||
monkeypatch.setenv("AWS_DEFAULT_REGION", "us-east-1")
|
||||
monkeypatch.setenv("AWS_ACCESS_KEY_ID", "testing")
|
||||
monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "testing")
|
||||
monkeypatch.setenv("ACDL_KMS_KEY_ID", "alias/aws/ssm")
|
||||
monkeypatch.setenv("NOVA_KMS_KEY_ID", "alias/aws/ssm")
|
||||
|
||||
with mock_aws():
|
||||
ssm = boto3.client("ssm", region_name="us-east-1")
|
||||
@@ -377,7 +377,7 @@ class TestCli:
|
||||
|
||||
class TestKmsFailLoud:
|
||||
"""P1-3: SSM publisher must fail loud when NOVA_KMS_KEY_ID is unset
|
||||
(P2 renamed from ACDL_KMS_KEY_ID; dual-read NOVA_* preferred,
|
||||
(P2 renamed from NOVA_KMS_KEY_ID; dual-read NOVA_* preferred,
|
||||
ACDL_* fallback until P5)."""
|
||||
|
||||
def test_kms_unset_raises(self, monkeypatch):
|
||||
@@ -389,9 +389,9 @@ class TestKmsFailLoud:
|
||||
monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "testing")
|
||||
# Both NOVA_* and ACDL_* unset → helper returns default (None) → fail loud.
|
||||
monkeypatch.delenv("NOVA_KMS_KEY_ID", raising=False)
|
||||
monkeypatch.delenv("ACDL_KMS_KEY_ID", raising=False)
|
||||
monkeypatch.delenv("NOVA_KMS_KEY_ID", raising=False)
|
||||
monkeypatch.delenv("NOVA_ALLOW_DEFAULT_KMS", raising=False)
|
||||
monkeypatch.delenv("NOVA_ALLOW_DEFAULT_KMS", raising=False)
|
||||
monkeypatch.delenv("ACDL_ALLOW_DEFAULT_KMS", raising=False)
|
||||
|
||||
with mock_aws():
|
||||
with pytest.raises(RuntimeError, match="NOVA_KMS_KEY_ID is not set"):
|
||||
@@ -405,7 +405,7 @@ class TestKmsFailLoud:
|
||||
monkeypatch.setenv("AWS_ACCESS_KEY_ID", "testing")
|
||||
monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "testing")
|
||||
monkeypatch.delenv("NOVA_KMS_KEY_ID", raising=False)
|
||||
monkeypatch.delenv("ACDL_KMS_KEY_ID", raising=False)
|
||||
monkeypatch.delenv("NOVA_KMS_KEY_ID", raising=False)
|
||||
monkeypatch.setenv("NOVA_ALLOW_DEFAULT_KMS", "1")
|
||||
|
||||
with mock_aws():
|
||||
|
||||
@@ -24,7 +24,7 @@ def test_route_halt_publishes_to_sns_when_arn_set(monkeypatch):
|
||||
assert call.kwargs["TopicArn"] == "arn:aws:sns:us-east-1:000000000000:nova-sod-halt"
|
||||
assert "contract-123" in call.kwargs["Message"]
|
||||
assert "SEPARATION_OF_DUTIES_VIOLATION" in call.kwargs["Message"]
|
||||
assert call.kwargs["Subject"] == "ACDL SoD halt"
|
||||
assert call.kwargs["Subject"] == "Nova SoD halt"
|
||||
|
||||
|
||||
def test_route_halt_falls_back_to_stderr_when_arn_unset(monkeypatch, capsys):
|
||||
|
||||
Reference in New Issue
Block a user