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:
Jon Chery
2026-07-30 02:23:55 +00:00
parent eb7634da28
commit e15eea067b
20 changed files with 106 additions and 137 deletions
+25 -25
View File
@@ -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"
+8 -8
View File
@@ -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"
+9 -9
View File
@@ -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():
+1 -1
View File
@@ -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):