Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e048acd4dd | |||
| 9421442afd | |||
| bb43d94563 |
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"phase": 0,
|
"phase": 1,
|
||||||
"stage": "plan",
|
"stage": "execute",
|
||||||
"milestone": "v1.16",
|
"milestone": "v1.16",
|
||||||
"phase_role": "pre_execution",
|
"phase_role": "execution",
|
||||||
"attempts": 0,
|
"attempts": 0,
|
||||||
"updated_at": "2026-07-30T15:15:00Z",
|
"updated_at": "2026-07-30T15:30:00Z",
|
||||||
"milestone_complete": false
|
"milestone_complete": false
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
# ACDL Adapters
|
# Nova Adapters
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Kyverno Adapter
|
# Kyverno Adapter
|
||||||
|
|
||||||
The Kyverno adapter translates Kyverno `PolicyReport` results to the
|
The Kyverno adapter translates Kyverno `PolicyReport` results to the
|
||||||
normalized ACDL
|
normalized Nova
|
||||||
[`PolicyCheckResult`](../../schemas/policy_check_result.schema.json) schema
|
[`PolicyCheckResult`](../../schemas/policy_check_result.schema.json) schema
|
||||||
(engine: `"kyverno"`), mirroring the Checkov/Wiz adapter pattern.
|
(engine: `"kyverno"`), mirroring the Checkov/Wiz adapter pattern.
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ publishes results to `PolicyReport` resources.
|
|||||||
## When to use it
|
## When to use it
|
||||||
|
|
||||||
Kyverno is the right engine **when the platform emits Kubernetes
|
Kyverno is the right engine **when the platform emits Kubernetes
|
||||||
manifests** (a K8s-native stack). The ACDL platform today emits Terraform
|
manifests** (a K8s-native stack). The Nova platform today emits Terraform
|
||||||
only (D-053), so this adapter is **ready but inactive**: it ships now so
|
only (D-053), so this adapter is **ready but inactive**: it ships now so
|
||||||
the schema path, severity/result mapping and sample policies are in place
|
the schema path, severity/result mapping and sample policies are in place
|
||||||
ahead of the GitOps reconciler that will emit K8s manifests (roadmap).
|
ahead of the GitOps reconciler that will emit K8s manifests (roadmap).
|
||||||
@@ -55,8 +55,8 @@ manifests (documentation-only today — the platform does not run them):
|
|||||||
|
|
||||||
- `disallow-privileged-containers.yml` — fail pods with
|
- `disallow-privileged-containers.yml` — fail pods with
|
||||||
`securityContext.privileged: true`.
|
`securityContext.privileged: true`.
|
||||||
- `require-resource-labels.yml` — require `acdl:owner` and
|
- `require-resource-labels.yml` — require `nova:owner` and
|
||||||
`acdl:environment` labels on all pods (mirrors the ACDL tagging standard
|
`nova:environment` labels on all pods (mirrors the Nova tagging standard
|
||||||
in [`schemas/tagging-standard.json`](../../schemas/tagging-standard.json)).
|
in [`schemas/tagging-standard.json`](../../schemas/tagging-standard.json)).
|
||||||
- `require-image-digests.yml` — require container images to reference a
|
- `require-image-digests.yml` — require container images to reference a
|
||||||
digest (`image@sha256:...`), not a mutable tag.
|
digest (`image@sha256:...`), not a mutable tag.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Kyverno adapter — translate Kyverno PolicyReport results to ACDL PolicyCheckResult records.
|
"""Kyverno adapter — translate Kyverno PolicyReport results to Nova PolicyCheckResult records.
|
||||||
|
|
||||||
Kyverno is a Kubernetes-native policy engine. It evaluates K8s manifests
|
Kyverno is a Kubernetes-native policy engine. It evaluates K8s manifests
|
||||||
and produces PolicyReport resources. This adapter translates those results
|
and produces PolicyReport resources. This adapter translates those results
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ kind: ClusterPolicy
|
|||||||
metadata:
|
metadata:
|
||||||
name: require-resource-labels
|
name: require-resource-labels
|
||||||
annotations:
|
annotations:
|
||||||
policies.kyverno.io/title: Require ACDL Resource Labels
|
policies.kyverno.io/title: Require Nova Resource Labels
|
||||||
policies.kyverno.io/category: Governance
|
policies.kyverno.io/category: Governance
|
||||||
policies.kyverno.io/severity: medium
|
policies.kyverno.io/severity: medium
|
||||||
policies.kyverno.io/subject: Pod
|
policies.kyverno.io/subject: Pod
|
||||||
@@ -11,27 +11,27 @@ spec:
|
|||||||
validationFailureAction: audit
|
validationFailureAction: audit
|
||||||
background: true
|
background: true
|
||||||
rules:
|
rules:
|
||||||
- name: require-acdl-owner-label
|
- name: require-nova-owner-label
|
||||||
match:
|
match:
|
||||||
any:
|
any:
|
||||||
- resources:
|
- resources:
|
||||||
kinds:
|
kinds:
|
||||||
- Pod
|
- Pod
|
||||||
validate:
|
validate:
|
||||||
message: "Pods must carry the acdl:owner label (ACDL tagging standard)."
|
message: "Pods must carry the nova:owner label (Nova tagging standard)."
|
||||||
pattern:
|
pattern:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
acdl:owner: "?*"
|
nova:owner: "?*"
|
||||||
- name: require-acdl-environment-label
|
- name: require-nova-environment-label
|
||||||
match:
|
match:
|
||||||
any:
|
any:
|
||||||
- resources:
|
- resources:
|
||||||
kinds:
|
kinds:
|
||||||
- Pod
|
- Pod
|
||||||
validate:
|
validate:
|
||||||
message: "Pods must carry the acdl:environment label (ACDL tagging standard)."
|
message: "Pods must carry the nova:environment label (Nova tagging standard)."
|
||||||
pattern:
|
pattern:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
acdl:environment: "?*"
|
nova:environment: "?*"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"""ACDL Terraform adapter — stateless assembler (v1.11 RESTART, P56a).
|
"""Nova Terraform adapter — stateless assembler (v1.11 RESTART, P56a).
|
||||||
|
|
||||||
A STATELESS ASSEMBLER. It owns no module content — no resource shape, no
|
A STATELESS ASSEMBLER. It owns no module content — no resource shape, no
|
||||||
nested HCL blocks, no defaults, no type-specific logic. It reads the
|
nested HCL blocks, no defaults, no type-specific logic. It reads the
|
||||||
@@ -114,7 +114,7 @@ def adapt(stack_instance, out_dir):
|
|||||||
stack_name = stack.get("name", "spike")
|
stack_name = stack.get("name", "spike")
|
||||||
environment = stack.get("environment", "dev")
|
environment = stack.get("environment", "dev")
|
||||||
account_id = env.get_env("AWS_ACCOUNT_ID", "581513795199")
|
account_id = env.get_env("AWS_ACCOUNT_ID", "581513795199")
|
||||||
state_bucket = f"acdl-tfstate-{account_id}-us-east-1"
|
state_bucket = f"nova-tfstate-{account_id}-us-east-1"
|
||||||
terraform_tf = (
|
terraform_tf = (
|
||||||
'terraform {\n'
|
'terraform {\n'
|
||||||
' required_version = ">= 1.9, < 1.10"\n'
|
' required_version = ">= 1.9, < 1.10"\n'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Wiz adapter — translate Wiz API results to ACDL PolicyCheckResult records.
|
"""Wiz adapter — translate Wiz API results to Nova PolicyCheckResult records.
|
||||||
|
|
||||||
Wiz is a SaaS security platform with a GraphQL API. This adapter
|
Wiz is a SaaS security platform with a GraphQL API. This adapter
|
||||||
translates Wiz issue records to the normalized PolicyCheckResult schema
|
translates Wiz issue records to the normalized PolicyCheckResult schema
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""ACDL Confidence Signal (REQ-19).
|
"""Nova Confidence Signal (REQ-19).
|
||||||
|
|
||||||
The platform's certified answer to "is this safe to proceed?" (vision
|
The platform's certified answer to "is this safe to proceed?" (vision
|
||||||
tenet: "Safety is Computed, Not Assumed"). Every delivery action produces
|
tenet: "Safety is Computed, Not Assumed"). Every delivery action produces
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""ACDL Contract Resolver — resolve a consumer contract to a Target Stack instance.
|
"""Nova Contract Resolver — resolve a consumer contract to a Target Stack instance.
|
||||||
|
|
||||||
The contract resolver is the bridge between the consumer's declared intent
|
The contract resolver is the bridge between the consumer's declared intent
|
||||||
(a contract YAML) and the platform's executable representation (a Target
|
(a contract YAML) and the platform's executable representation (a Target
|
||||||
@@ -471,7 +471,7 @@ def resolve(contract_path, repo_root=None, environment_override=None):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
contract_path: Path to the contract YAML file.
|
contract_path: Path to the contract YAML file.
|
||||||
repo_root: Root of the ACDL repo (defaults to two levels up from this file).
|
repo_root: Root of the Nova repo (defaults to two levels up from this file).
|
||||||
environment_override: When set (dev/qa/prod/dr), overrides the
|
environment_override: When set (dev/qa/prod/dr), overrides the
|
||||||
contract's 'environment' field BEFORE schema validation, so
|
contract's 'environment' field BEFORE schema validation, so
|
||||||
interpolation context is consistent (D-088). Used by
|
interpolation context is consistent (D-088). Used by
|
||||||
|
|||||||
@@ -56,9 +56,9 @@ def load(env_name, root=None):
|
|||||||
|
|
||||||
def _onboarding_message(env_name):
|
def _onboarding_message(env_name):
|
||||||
return (
|
return (
|
||||||
"=== ACDL Environment Onboarding ===\n"
|
"=== Nova Environment Onboarding ===\n"
|
||||||
f"No environment named '{env_name}' is bound to this repository.\n\n"
|
f"No environment named '{env_name}' is bound to this repository.\n\n"
|
||||||
"ACDL environments are platform-managed. The platform provisions on\n"
|
"Nova environments are platform-managed. The platform provisions on\n"
|
||||||
"your behalf:\n"
|
"your behalf:\n"
|
||||||
" - an AWS account (or a scoped partition of one)\n"
|
" - an AWS account (or a scoped partition of one)\n"
|
||||||
" - a network (VPC + subnets)\n"
|
" - a network (VPC + subnets)\n"
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ def _report_error(payload):
|
|||||||
raise RuntimeError(f"failed to read GitHub token from Secrets Manager: {e}")
|
raise RuntimeError(f"failed to read GitHub token from Secrets Manager: {e}")
|
||||||
|
|
||||||
owner, repo = PLATFORM_REPO.split("/")
|
owner, repo = PLATFORM_REPO.split("/")
|
||||||
title = f"[ACDL-ALERT] Deploy failure: {consumer_repo} / {contract_id}"
|
title = f"[NOVA-ALERT] Deploy failure: {consumer_repo} / {contract_id}"
|
||||||
|
|
||||||
# Check for an existing open issue with the same title (idempotency)
|
# Check for an existing open issue with the same title (idempotency)
|
||||||
# URL-encode the contract_id to prevent search-query injection (P1-1).
|
# URL-encode the contract_id to prevent search-query injection (P1-1).
|
||||||
@@ -188,7 +188,7 @@ def _report_error(payload):
|
|||||||
{stack_trace}
|
{stack_trace}
|
||||||
```
|
```
|
||||||
|
|
||||||
_This issue was auto-created by the ACDL platform Lambda (D-055). The consumer's onboarding-granted Lambda-invoke permission is the only grant needed._
|
_This issue was auto-created by the Nova platform Lambda (D-055). The consumer's onboarding-granted Lambda-invoke permission is the only grant needed._
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if existing:
|
if existing:
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ evidence event) runs end-to-end against the local tier with no AWS:
|
|||||||
Each adapter exposes the same interface as the live counterpart so the
|
Each adapter exposes the same interface as the live counterpart so the
|
||||||
caller code path is unchanged; only the I/O target swaps. Selection is
|
caller code path is unchanged; only the I/O target swaps. Selection is
|
||||||
gated on the NOVA_LOCAL_TIER env var (set by run_platform.sh --local).
|
gated on the NOVA_LOCAL_TIER env var (set by run_platform.sh --local).
|
||||||
Dual-read via core/env.py: NOVA_* preferred, ACDL_* fallback until P5.
|
Env vars read via core/env.py (NOVA_* only; the ACDL_* fallback was
|
||||||
|
removed in v1.15 P5, REQ-164).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -68,7 +69,7 @@ class FlatFileOutbox:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, dir: Optional[Path] = None) -> "FlatFileOutbox":
|
def create(cls, dir: Optional[Path] = None) -> "FlatFileOutbox":
|
||||||
d = Path(dir) if dir else Path(tempfile.mkdtemp(prefix="acdl_outbox_"))
|
d = Path(dir) if dir else Path(tempfile.mkdtemp(prefix="nova_outbox_"))
|
||||||
d.mkdir(parents=True, exist_ok=True)
|
d.mkdir(parents=True, exist_ok=True)
|
||||||
out = cls(dir=d)
|
out = cls(dir=d)
|
||||||
# Re-read the chain tail if the file already exists.
|
# Re-read the chain tail if the file already exists.
|
||||||
@@ -249,7 +250,7 @@ class LocalS3StateBackend:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, dir: Optional[Path] = None) -> "LocalS3StateBackend":
|
def create(cls, dir: Optional[Path] = None) -> "LocalS3StateBackend":
|
||||||
d = Path(dir) if dir else Path(tempfile.mkdtemp(prefix="acdl_tfstate_"))
|
d = Path(dir) if dir else Path(tempfile.mkdtemp(prefix="nova_tfstate_"))
|
||||||
d.mkdir(parents=True, exist_ok=True)
|
d.mkdir(parents=True, exist_ok=True)
|
||||||
return cls(state_dir=d)
|
return cls(state_dir=d)
|
||||||
|
|
||||||
@@ -499,9 +500,8 @@ def run_local_e2e(contract_path: str, repo_root: Optional[Path] = None) -> Dict[
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
contract = sys.argv[1] if len(sys.argv) > 1 else "contracts/microservice.yml"
|
contract = sys.argv[1] if len(sys.argv) > 1 else "contracts/microservice.yml"
|
||||||
# Set both so the dual-read in is_local_tier() finds NOVA_* (preferred);
|
# Set so is_local_tier() finds NOVA_LOCAL_TIER (NOVA_* only; the
|
||||||
# the ACDL_* alias stays for any unmigrated reader until P5.
|
# ACDL_* alias was removed in v1.15 P5, REQ-164).
|
||||||
os.environ["NOVA_LOCAL_TIER"] = "1"
|
os.environ["NOVA_LOCAL_TIER"] = "1"
|
||||||
# P5 (REQ-164): ACDL_LOCAL_TIER legacy alias removed (NOVA_* only)
|
|
||||||
result = run_local_e2e(contract)
|
result = run_local_e2e(contract)
|
||||||
print(json.dumps(result, indent=2))
|
print(json.dumps(result, indent=2))
|
||||||
@@ -180,7 +180,7 @@ def _check_resolver_microservice() -> Tuple[Status, str]:
|
|||||||
|
|
||||||
def _check_adapter_emits_terraform() -> Tuple[Status, str]:
|
def _check_adapter_emits_terraform() -> Tuple[Status, str]:
|
||||||
"""CAP-005: terraform adapter compiles a resolved stack to .tf files."""
|
"""CAP-005: terraform adapter compiles a resolved stack to .tf files."""
|
||||||
work = tempfile.mkdtemp(prefix="acdl_regr_")
|
work = tempfile.mkdtemp(prefix="nova_regr_")
|
||||||
stack_path = os.path.join(work, "stack.json")
|
stack_path = os.path.join(work, "stack.json")
|
||||||
tf_dir = os.path.join(work, "tf")
|
tf_dir = os.path.join(work, "tf")
|
||||||
os.makedirs(tf_dir, exist_ok=True)
|
os.makedirs(tf_dir, exist_ok=True)
|
||||||
@@ -211,7 +211,7 @@ def _check_interpolation() -> Tuple[Status, str]:
|
|||||||
"import sys; sys.path.insert(0,'.'); "
|
"import sys; sys.path.insert(0,'.'); "
|
||||||
"from core.contract_resolver import _expand_vars; "
|
"from core.contract_resolver import _expand_vars; "
|
||||||
"ctx={'env':{'environment':'qa','account_id':'123'},'contract':{'id':'assets'}}; "
|
"ctx={'env':{'environment':'qa','account_id':'123'},'contract':{'id':'assets'}}; "
|
||||||
"assert _expand_vars('acdl-${env.environment}-${contract.id}', ctx)=='acdl-qa-assets'; "
|
"assert _expand_vars('nova-${env.environment}-${contract.id}', ctx)=='nova-qa-assets'; "
|
||||||
"print('interpolation ok')",
|
"print('interpolation ok')",
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -231,7 +231,7 @@ def _check_confidence_signal() -> Tuple[Status, str]:
|
|||||||
|
|
||||||
def _check_outbox_writer() -> Tuple[Status, str]:
|
def _check_outbox_writer() -> Tuple[Status, str]:
|
||||||
"""CAP-008: outbox_writer writes a hash-chained event to a temp file."""
|
"""CAP-008: outbox_writer writes a hash-chained event to a temp file."""
|
||||||
work = tempfile.mkdtemp(prefix="acdl_outbox_")
|
work = tempfile.mkdtemp(prefix="nova_outbox_")
|
||||||
event_path = os.path.join(work, "event.json")
|
event_path = os.path.join(work, "event.json")
|
||||||
event = {
|
event = {
|
||||||
"contractId": "regression-test", "eventType": "CONFIDENCE_COMPUTED",
|
"contractId": "regression-test", "eventType": "CONFIDENCE_COMPUTED",
|
||||||
@@ -315,7 +315,7 @@ def _load_aws_env() -> Dict[str, str]:
|
|||||||
continue
|
continue
|
||||||
if "=" in line:
|
if "=" in line:
|
||||||
k, v = line.split("=", 1)
|
k, v = line.split("=", 1)
|
||||||
# P5 (REQ-164): dual-read fallback removed — NOVA_* only.
|
# NOVA_* only (ACDL_* fallback removed in v1.15 P5, REQ-164).
|
||||||
if k == "NOVA_AWS_ACCESS_KEY_ID":
|
if k == "NOVA_AWS_ACCESS_KEY_ID":
|
||||||
env["AWS_ACCESS_KEY_ID"] = v
|
env["AWS_ACCESS_KEY_ID"] = v
|
||||||
elif k == "NOVA_AWS_SECRET_ACCESS_KEY":
|
elif k == "NOVA_AWS_SECRET_ACCESS_KEY":
|
||||||
@@ -330,7 +330,7 @@ def _check_live_terraform_plan_microservice() -> Tuple[Status, str]:
|
|||||||
microservice stack (D-093 live-AWS tier of the headline E2E).
|
microservice stack (D-093 live-AWS tier of the headline E2E).
|
||||||
|
|
||||||
Requires AWS credentials (NOVA_AWS_ACCESS_KEY_ID etc. in .env.secrets;
|
Requires AWS credentials (NOVA_AWS_ACCESS_KEY_ID etc. in .env.secrets;
|
||||||
dual-read NOVA_* first, ACDL_* fallback per G-106).
|
NOVA_* only — the ACDL_* fallback was removed in v1.15 P5, REQ-164).
|
||||||
Runs in a temp dir; does NOT apply (plan only)."""
|
Runs in a temp dir; does NOT apply (plan only)."""
|
||||||
import tempfile, os
|
import tempfile, os
|
||||||
work = tempfile.mkdtemp(prefix="nova_regr_live_")
|
work = tempfile.mkdtemp(prefix="nova_regr_live_")
|
||||||
|
|||||||
@@ -36,14 +36,14 @@ import json, sys
|
|||||||
stage = '''$STAGE'''
|
stage = '''$STAGE'''
|
||||||
status = '''$STATUS'''
|
status = '''$STATUS'''
|
||||||
details = json.loads('''$DETAILS''')
|
details = json.loads('''$DETAILS''')
|
||||||
lines = [f'### ACDL Stage: {stage} — {status}', '']
|
lines = [f'### Nova Stage: {stage} — {status}', '']
|
||||||
if details:
|
if details:
|
||||||
lines.append('| Metric | Value |')
|
lines.append('| Metric | Value |')
|
||||||
lines.append('|--------|-------|')
|
lines.append('|--------|-------|')
|
||||||
for k, v in details.items():
|
for k, v in details.items():
|
||||||
lines.append(f'| {k} | {v} |')
|
lines.append(f'| {k} | {v} |')
|
||||||
lines.append('')
|
lines.append('')
|
||||||
lines.append('> _Auto-posted by the ACDL deploy pipeline (D-055)._')
|
lines.append('> _Auto-posted by the Nova deploy pipeline (D-055)._')
|
||||||
print('\n'.join(lines))
|
print('\n'.join(lines))
|
||||||
")
|
")
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -36,7 +36,7 @@ banner() {
|
|||||||
|
|
||||||
fail() { echo "FAIL: $*" >&2; exit 1; }
|
fail() { echo "FAIL: $*" >&2; exit 1; }
|
||||||
|
|
||||||
echo "=== ACDL CI Pipeline (local reproduction) ==="
|
echo "=== Nova CI Pipeline (local reproduction) ==="
|
||||||
echo "contract: pipelines/ci.yml (3 stages)"
|
echo "contract: pipelines/ci.yml (3 stages)"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,8 @@
|
|||||||
# parity with the L1 matrix, but $2 is accepted-but-ignored here (documented,
|
# parity with the L1 matrix, but $2 is accepted-but-ignored here (documented,
|
||||||
# not a bug).
|
# not a bug).
|
||||||
#
|
#
|
||||||
# Lifecycle mode (REQ-134): NOVA_LIFECYCLE_MODE (dual-read NOVA_* preferred,
|
# Lifecycle mode (REQ-134): NOVA_LIFECYCLE_MODE (NOVA_* only; ACDL_*
|
||||||
# ACDL_* fallback until P5) default "plan" = no-op
|
# fallback removed in v1.15 P5) default "plan" = no-op
|
||||||
# (plan mode never applies resources, so there is nothing to destroy).
|
# (plan mode never applies resources, so there is nothing to destroy).
|
||||||
# Set to "full" for the real `--destroy` against live AWS.
|
# Set to "full" for the real `--destroy` against live AWS.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -25,7 +25,7 @@ cd "$ROOT"
|
|||||||
MODULE="$1"
|
MODULE="$1"
|
||||||
|
|
||||||
# Lifecycle mode: "plan" (default) skips destroy; "full" runs the real destroy.
|
# Lifecycle mode: "plan" (default) skips destroy; "full" runs the real destroy.
|
||||||
# Dual-read: NOVA_* preferred, ACDL_* fallback (removed in P5).
|
# NOVA_* env vars only (ACDL_* fallback removed in v1.15 P5, REQ-164).
|
||||||
LIFECYCLE_MODE="${NOVA_LIFECYCLE_MODE:-plan}"
|
LIFECYCLE_MODE="${NOVA_LIFECYCLE_MODE:-plan}"
|
||||||
|
|
||||||
if [ "$LIFECYCLE_MODE" != "full" ]; then
|
if [ "$LIFECYCLE_MODE" != "full" ]; then
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
# positional args for parity with the L1 matrix, but $3 is accepted-but-
|
# positional args for parity with the L1 matrix, but $3 is accepted-but-
|
||||||
# ignored here (documented, not a bug).
|
# ignored here (documented, not a bug).
|
||||||
#
|
#
|
||||||
# Lifecycle mode (REQ-134): NOVA_LIFECYCLE_MODE (dual-read NOVA_* preferred,
|
# Lifecycle mode (REQ-134): NOVA_LIFECYCLE_MODE (NOVA_* only; ACDL_*
|
||||||
# ACDL_* fallback until P5) default "plan" runs
|
# fallback removed in v1.15 P5) default "plan" runs
|
||||||
# `run_platform.sh --plan-only` (fast, no AWS mutation). Set to "full" for
|
# `run_platform.sh --plan-only` (fast, no AWS mutation). Set to "full" for
|
||||||
# the real `--apply` against live AWS.
|
# the real `--apply` against live AWS.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -29,14 +29,12 @@ MODULE="$1"
|
|||||||
EXAMPLE="$2" # simple or complex
|
EXAMPLE="$2" # simple or complex
|
||||||
|
|
||||||
# Lifecycle mode: "plan" (default, fast) or "full" (real apply against AWS).
|
# Lifecycle mode: "plan" (default, fast) or "full" (real apply against AWS).
|
||||||
# Dual-read: NOVA_* preferred, ACDL_* fallback (removed in P5).
|
# NOVA_* env vars only (ACDL_* fallback removed in v1.15 P5, REQ-164).
|
||||||
LIFECYCLE_MODE="${NOVA_LIFECYCLE_MODE:-plan}"
|
LIFECYCLE_MODE="${NOVA_LIFECYCLE_MODE:-plan}"
|
||||||
|
|
||||||
CONTRACT="modules/l2/${MODULE}/examples/${EXAMPLE}.yml"
|
CONTRACT="modules/l2/${MODULE}/examples/${EXAMPLE}.yml"
|
||||||
|
|
||||||
# Point terraform_remote_state to the CI VPC state (not the platform VPC).
|
# Point terraform_remote_state to the CI VPC state (not the platform VPC).
|
||||||
# Set both NOVA_* (preferred by the dual-read helper) and ACDL_* (legacy
|
|
||||||
# fallback) so any unmigrated reader finds the key until P5.
|
|
||||||
export NOVA_REMOTE_STATE_KEY="spike/ci-vpc/terraform.tfstate"
|
export NOVA_REMOTE_STATE_KEY="spike/ci-vpc/terraform.tfstate"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
# For VPC-dependent modules, injects CI VPC outputs into the complex contract
|
# For VPC-dependent modules, injects CI VPC outputs into the complex contract
|
||||||
# before destroy (so terraform can find the resources in the right VPC).
|
# before destroy (so terraform can find the resources in the right VPC).
|
||||||
#
|
#
|
||||||
# Lifecycle mode (REQ-134): NOVA_LIFECYCLE_MODE (dual-read NOVA_* preferred,
|
# Lifecycle mode (REQ-134): NOVA_LIFECYCLE_MODE (NOVA_* only; ACDL_*
|
||||||
# ACDL_* fallback until P5) default "plan" = no-op
|
# fallback removed in v1.15 P5) default "plan" = no-op
|
||||||
# (plan mode never applies resources, so there is nothing to destroy; the
|
# (plan mode never applies resources, so there is nothing to destroy; the
|
||||||
# script exits 0 so the pipeline matrix cell stays green). Set to "full"
|
# script exits 0 so the pipeline matrix cell stays green). Set to "full"
|
||||||
# for the real `--destroy` against live AWS.
|
# for the real `--destroy` against live AWS.
|
||||||
@@ -20,7 +20,7 @@ CI_VPC_OUTPUTS="${2:-}"
|
|||||||
|
|
||||||
# Lifecycle mode: "plan" (default) skips destroy (nothing was applied);
|
# Lifecycle mode: "plan" (default) skips destroy (nothing was applied);
|
||||||
# "full" runs the real terraform destroy.
|
# "full" runs the real terraform destroy.
|
||||||
# Dual-read: NOVA_* preferred, ACDL_* fallback (removed in P5).
|
# NOVA_* env vars only (ACDL_* fallback removed in v1.15 P5, REQ-164).
|
||||||
LIFECYCLE_MODE="${NOVA_LIFECYCLE_MODE:-plan}"
|
LIFECYCLE_MODE="${NOVA_LIFECYCLE_MODE:-plan}"
|
||||||
|
|
||||||
if [ "$LIFECYCLE_MODE" != "full" ]; then
|
if [ "$LIFECYCLE_MODE" != "full" ]; then
|
||||||
@@ -33,7 +33,7 @@ CONTRACT="modules/l1/${MODULE}/examples/complex.yml"
|
|||||||
VPC_DEPENDENT="alb ecs-service rds uptime"
|
VPC_DEPENDENT="alb ecs-service rds uptime"
|
||||||
|
|
||||||
if echo "$VPC_DEPENDENT" | grep -qw "$MODULE" && [ -n "$CI_VPC_OUTPUTS" ] && [ -f "$CI_VPC_OUTPUTS" ]; then
|
if echo "$VPC_DEPENDENT" | grep -qw "$MODULE" && [ -n "$CI_VPC_OUTPUTS" ] && [ -f "$CI_VPC_OUTPUTS" ]; then
|
||||||
TMP_CONTRACT="/tmp/acdl-lifecycle-${MODULE}-complex.yml"
|
TMP_CONTRACT="/tmp/nova-lifecycle-${MODULE}-complex.yml"
|
||||||
python3 -c "
|
python3 -c "
|
||||||
import yaml, json
|
import yaml, json
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
# from the long-lived platform VPC.
|
# from the long-lived platform VPC.
|
||||||
#
|
#
|
||||||
# Lifecycle mode (REQ-134): the NOVA_LIFECYCLE_MODE env var selects the
|
# Lifecycle mode (REQ-134): the NOVA_LIFECYCLE_MODE env var selects the
|
||||||
# tier (dual-read NOVA_* preferred, ACDL_* fallback until P5). Default
|
# tier (NOVA_* only; ACDL_* fallback removed in v1.15 P5). Default
|
||||||
# "plan" runs `run_platform.sh --plan-only` (fast, no AWS
|
# "plan" runs `run_platform.sh --plan-only` (fast, no AWS
|
||||||
# mutation, validates the contract->resolver->adapter->plan chain for
|
# mutation, validates the contract->resolver->adapter->plan chain for
|
||||||
# every module). Set to "full" to run the real `--apply` (terraform apply
|
# every module). Set to "full" to run the real `--apply` (terraform apply
|
||||||
@@ -26,7 +26,7 @@ EXAMPLE="$2" # simple or complex
|
|||||||
CI_VPC_OUTPUTS="${3:-}"
|
CI_VPC_OUTPUTS="${3:-}"
|
||||||
|
|
||||||
# Lifecycle mode: "plan" (default, fast) or "full" (real apply against AWS).
|
# Lifecycle mode: "plan" (default, fast) or "full" (real apply against AWS).
|
||||||
# Dual-read: NOVA_* preferred, ACDL_* fallback (removed in P5).
|
# NOVA_* env vars only (ACDL_* fallback removed in v1.15 P5, REQ-164).
|
||||||
LIFECYCLE_MODE="${NOVA_LIFECYCLE_MODE:-plan}"
|
LIFECYCLE_MODE="${NOVA_LIFECYCLE_MODE:-plan}"
|
||||||
|
|
||||||
CONTRACT="modules/l1/${MODULE}/examples/${EXAMPLE}.yml"
|
CONTRACT="modules/l1/${MODULE}/examples/${EXAMPLE}.yml"
|
||||||
@@ -38,7 +38,7 @@ VPC_DEPENDENT="alb ecs-service rds uptime"
|
|||||||
# (only meaningful in full mode; plan mode ignores VPC outputs)
|
# (only meaningful in full mode; plan mode ignores VPC outputs)
|
||||||
if [ "$LIFECYCLE_MODE" = "full" ] && echo "$VPC_DEPENDENT" | grep -qw "$MODULE" && [ -n "$CI_VPC_OUTPUTS" ] && [ -f "$CI_VPC_OUTPUTS" ]; then
|
if [ "$LIFECYCLE_MODE" = "full" ] && echo "$VPC_DEPENDENT" | grep -qw "$MODULE" && [ -n "$CI_VPC_OUTPUTS" ] && [ -f "$CI_VPC_OUTPUTS" ]; then
|
||||||
# Generate a temporary contract with CI VPC outputs injected
|
# Generate a temporary contract with CI VPC outputs injected
|
||||||
TMP_CONTRACT="/tmp/acdl-lifecycle-${MODULE}-${EXAMPLE}.yml"
|
TMP_CONTRACT="/tmp/nova-lifecycle-${MODULE}-${EXAMPLE}.yml"
|
||||||
python3 -c "
|
python3 -c "
|
||||||
import yaml, json, sys
|
import yaml, json, sys
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ done
|
|||||||
CONTRACT="contracts/$MODULE.yaml"
|
CONTRACT="contracts/$MODULE.yaml"
|
||||||
[ -f "$CONTRACT" ] || { echo "FAIL: no sample contract at $CONTRACT for module '$MODULE'" >&2; exit 1; }
|
[ -f "$CONTRACT" ] || { echo "FAIL: no sample contract at $CONTRACT for module '$MODULE'" >&2; exit 1; }
|
||||||
|
|
||||||
WORK="/tmp/acdl_pattern_plan_$MODULE"
|
WORK="/tmp/nova_pattern_plan_$MODULE"
|
||||||
rm -rf "$WORK"; mkdir -p "$WORK"
|
rm -rf "$WORK"; mkdir -p "$WORK"
|
||||||
|
|
||||||
echo "=== Pattern plan: $MODULE ==="
|
echo "=== Pattern plan: $MODULE ==="
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# scripts/run_platform.sh - the ACDL platform pipeline.
|
# scripts/run_platform.sh - the Nova platform pipeline.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# run_platform.sh <contract.yml> (full e2e with AWS)
|
# run_platform.sh <contract.yml> (full e2e with AWS)
|
||||||
@@ -150,7 +150,6 @@ rm -rf "$WORK"; mkdir -p "$TF_DIR"
|
|||||||
echo "=== Step 0: environment onboarding check ==="
|
echo "=== Step 0: environment onboarding check ==="
|
||||||
if [ -n "$ENVIRONMENT_OVERRIDE" ]; then
|
if [ -n "$ENVIRONMENT_OVERRIDE" ]; then
|
||||||
export NOVA_ENVIRONMENT_OVERRIDE="$ENVIRONMENT_OVERRIDE"
|
export NOVA_ENVIRONMENT_OVERRIDE="$ENVIRONMENT_OVERRIDE"
|
||||||
export ACDL_ENVIRONMENT_OVERRIDE="$ENVIRONMENT_OVERRIDE" # legacy fallback, removed in P5
|
|
||||||
python3 core/environment_check.py --env="$ENVIRONMENT_OVERRIDE" || {
|
python3 core/environment_check.py --env="$ENVIRONMENT_OVERRIDE" || {
|
||||||
echo "FAIL: environment not bound — see the onboarding prompt above" >&2
|
echo "FAIL: environment not bound — see the onboarding prompt above" >&2
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ done
|
|||||||
INSTANCE="modules/l1/$PRIMITIVE/instance.json"
|
INSTANCE="modules/l1/$PRIMITIVE/instance.json"
|
||||||
[ -f "$INSTANCE" ] || { echo "FAIL: no instance.json for primitive '$PRIMITIVE'" >&2; exit 1; }
|
[ -f "$INSTANCE" ] || { echo "FAIL: no instance.json for primitive '$PRIMITIVE'" >&2; exit 1; }
|
||||||
|
|
||||||
WORK="/tmp/acdl_primitive_plan_$PRIMITIVE"
|
WORK="/tmp/nova_primitive_plan_$PRIMITIVE"
|
||||||
rm -rf "$WORK"; mkdir -p "$WORK"
|
rm -rf "$WORK"; mkdir -p "$WORK"
|
||||||
|
|
||||||
echo "=== Primitive plan: $PRIMITIVE ==="
|
echo "=== Primitive plan: $PRIMITIVE ==="
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
|
|
||||||
echo "=== Nova Regression VERIFY (D-091) ==="
|
echo "=== Nova Regression VERIFY (D-091) ==="
|
||||||
# Dual-read: NOVA_* preferred, ACDL_* fallback (removed in P5).
|
# NOVA_* env vars only (ACDL_* fallback removed in v1.15 P5, REQ-164).
|
||||||
echo "milestone: ${NOVA_REGRESSION_MILESTONE:-v1.10} phase: ${NOVA_REGRESSION_PHASE:-52}"
|
echo "milestone: ${NOVA_REGRESSION_MILESTONE:-v1.10} phase: ${NOVA_REGRESSION_PHASE:-52}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
|||||||
Executable
+46
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# scripts/ship_phase.sh — internal CIAgent per-phase ship helper (v1.16)
|
||||||
|
# Usage: bash scripts/ship_phase.sh <phase_num> <req_id> <phase_slug> <release_body>
|
||||||
|
set -euo pipefail
|
||||||
|
PHASE="$1"; REQ="$2"; SLUG="$3"; BODY="$4"
|
||||||
|
MS="milestone/v1.16-nova-simplification"
|
||||||
|
BR="phase/$(printf '%02d' "$PHASE")-${SLUG}"
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
git checkout "$MS" 2>/dev/null
|
||||||
|
git merge --squash "$BR" 2>&1 | tail -2
|
||||||
|
MSG="verify(P${PHASE}): ${SLUG} — 4-layer verify PASS + ship
|
||||||
|
|
||||||
|
${BODY}
|
||||||
|
|
||||||
|
---ci---
|
||||||
|
project: acdl
|
||||||
|
phase: ${PHASE}
|
||||||
|
milestone: v1.16
|
||||||
|
status: complete
|
||||||
|
phase_role: execution
|
||||||
|
requirements:
|
||||||
|
covered: [${REQ}]
|
||||||
|
partial: []
|
||||||
|
---/ci---"
|
||||||
|
git commit -q -m "$MSG"
|
||||||
|
PREV=$(git tag -l "v1.15.*" --sort=-version:refname | head -1)
|
||||||
|
PATCH=$(($(echo "$PREV" | sed 's/v1.15.//')))
|
||||||
|
NEWPATCH=$((PATCH + 1))
|
||||||
|
TAG="v1.15.${NEWPATCH}"
|
||||||
|
git tag -a "$TAG" -m "${TAG}: v1.16 P${PHASE} — ${SLUG}"
|
||||||
|
git push origin "$MS" --tags 2>&1 | grep -E "new tag|new branch" | head -2
|
||||||
|
python3 - "$TAG" "$PREV" <<'PYEOF'
|
||||||
|
import json, subprocess, sys, urllib.request, urllib.error
|
||||||
|
tag, prev = sys.argv[1], sys.argv[2]
|
||||||
|
tok = [l.split("=",1)[1].strip() for l in open(".env.secrets") if l.startswith("NOVA_GITEA_TOKEN=")][0]
|
||||||
|
body = subprocess.check_output(["git","log",f"{prev}..{tag}","--oneline"]).decode()
|
||||||
|
payload = {"tag_name":tag,"name":f"Nova {tag} — v1.16 P{tag.split('.')[-1]}","body":body}
|
||||||
|
req = urllib.request.Request("https://git.cloudinit.dev/api/v1/repos/continuous-intelligence/acdl/releases", data=json.dumps(payload).encode(), headers={"Authorization":f"token {tok}","Content-Type":"application/json"}, method="POST")
|
||||||
|
try:
|
||||||
|
r = urllib.request.urlopen(req, timeout=30); d = json.loads(r.read()); print(f"release_id: {d.get('id')} tag: {tag}")
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
if e.code == 409: print(f"release exists for {tag}")
|
||||||
|
else: print(f"HTTP {e.code}: {e.read().decode()[:120]}")
|
||||||
|
except Exception as e: print(f"ERROR: {e}")
|
||||||
|
PYEOF
|
||||||
|
echo "SHIPPED ${TAG}"
|
||||||
@@ -90,6 +90,15 @@ class TestModuleAssembly:
|
|||||||
assert 'backend "s3"' in terraform_tf
|
assert 'backend "s3"' in terraform_tf
|
||||||
assert 'spike/s3/dev/terraform.tfstate' in terraform_tf
|
assert 'spike/s3/dev/terraform.tfstate' in terraform_tf
|
||||||
|
|
||||||
|
def test_adapt_emits_nova_state_bucket(self, tmp_path):
|
||||||
|
"""P1 (REQ-165): the emitted backend references nova-tfstate-*
|
||||||
|
(not acdl-tfstate-*); the live bucket was renamed in v1.15 P4."""
|
||||||
|
instance = json.load(open(ROOT / "modules/l1/s3/instance.json"))
|
||||||
|
adapt(instance, str(tmp_path))
|
||||||
|
terraform_tf = (tmp_path / "terraform.tf").read_text()
|
||||||
|
assert "nova-tfstate-" in terraform_tf
|
||||||
|
assert "acdl-tfstate-" not in terraform_tf
|
||||||
|
|
||||||
def test_adapt_emits_root_outputs(self, tmp_path):
|
def test_adapt_emits_root_outputs(self, tmp_path):
|
||||||
instance = json.load(open(ROOT / "modules/l1/s3/instance.json"))
|
instance = json.load(open(ROOT / "modules/l1/s3/instance.json"))
|
||||||
instance["outputs"] = {
|
instance["outputs"] = {
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ def test_run_platform_sh_has_environment_flag():
|
|||||||
text = (ROOT / "scripts" / "run_platform.sh").read_text()
|
text = (ROOT / "scripts" / "run_platform.sh").read_text()
|
||||||
assert "--environment" in text
|
assert "--environment" in text
|
||||||
assert "ENVIRONMENT_OVERRIDE" in text
|
assert "ENVIRONMENT_OVERRIDE" in text
|
||||||
# P2 (REQ-159): NOVA_* preferred; ACDL_* kept as dual-read fallback until P5.
|
# P3 (REQ-167): NOVA_* only; the dead ACDL_ENVIRONMENT_OVERRIDE export
|
||||||
|
# (comment said "removed in P5" but the line was present) is gone.
|
||||||
assert "NOVA_ENVIRONMENT_OVERRIDE" in text
|
assert "NOVA_ENVIRONMENT_OVERRIDE" in text
|
||||||
assert "ACDL_ENVIRONMENT_OVERRIDE" in text # legacy fallback, removed in P5
|
assert "ACDL_ENVIRONMENT_OVERRIDE" not in text
|
||||||
@@ -31,6 +31,12 @@ class TestEnvironmentCheck:
|
|||||||
assert "state backend" in msg.lower()
|
assert "state backend" in msg.lower()
|
||||||
assert "IAM role" in msg
|
assert "IAM role" in msg
|
||||||
|
|
||||||
|
def test_onboarding_message_says_nova_not_acdl(self):
|
||||||
|
"""P2 (REQ-166): the onboarding message is rebranded Nova."""
|
||||||
|
msg = _onboarding_message("qa")
|
||||||
|
assert "Nova Environment Onboarding" in msg
|
||||||
|
assert "ACDL" not in msg
|
||||||
|
|
||||||
def test_contract_with_dev_environment_passes(self):
|
def test_contract_with_dev_environment_passes(self):
|
||||||
ok, msg = check(contract_path=str(ROOT / "contracts/static-assets.yml"), root=ROOT)
|
ok, msg = check(contract_path=str(ROOT / "contracts/static-assets.yml"), root=ROOT)
|
||||||
assert ok is True
|
assert ok is True
|
||||||
|
|||||||
Reference in New Issue
Block a user