Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ea58ec1c9 | |||
| d5bae868a4 | |||
| 0bc70a3d95 | |||
| adce478e09 | |||
| 63f3a2b66c | |||
| 1ff942684e | |||
| 6c25ce3900 |
@@ -1,9 +1,11 @@
|
||||
{
|
||||
"phase": 0,
|
||||
"stage": "ship",
|
||||
"phase": 2,
|
||||
"stage": "complete",
|
||||
"milestone": "v1.15",
|
||||
"phase_role": "pre_execution",
|
||||
"phase_role": "execution",
|
||||
"attempts": 0,
|
||||
"updated_at": "2026-07-30T00:05:00Z",
|
||||
"milestone_complete": false
|
||||
"updated_at": "2026-07-30T00:08:00Z",
|
||||
"milestone_complete": false,
|
||||
"requirements": ["REQ-158", "REQ-159", "REQ-160"],
|
||||
"tag": "v1.15.2"
|
||||
}
|
||||
@@ -819,12 +819,12 @@ IDEATE-01..IDEATE-10, mapped to REQ-155..REQ-164.
|
||||
|
||||
| Requirement | Phase | Status |
|
||||
|-------------|-------|--------|
|
||||
| REQ-155 | P1 | pending |
|
||||
| REQ-156 | P1 | pending |
|
||||
| REQ-157 | P1 | pending |
|
||||
| REQ-158 | P2 | pending |
|
||||
| REQ-159 | P2 | pending |
|
||||
| REQ-160 | P2 | pending |
|
||||
| REQ-155 | P1 | complete |
|
||||
| REQ-156 | P1 | complete |
|
||||
| REQ-157 | P1 | complete |
|
||||
| REQ-158 | P2 | complete |
|
||||
| REQ-159 | P2 | complete |
|
||||
| REQ-160 | P2 | complete |
|
||||
| REQ-161 | P3 | pending |
|
||||
| REQ-162 | P3 | pending |
|
||||
| REQ-163 | P4 | pending |
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
# platform log) for auditability.
|
||||
#
|
||||
# Inputs:
|
||||
# contract — path to the consumer's contract YAML (default .acdl/contract.yml)
|
||||
# contract — path to the consumer's contract YAML (default .nova/contract.yml)
|
||||
# mode — full | plan-only | check-only (default full; dev = full apply,
|
||||
# higher environments hold for HITL — the calling repo or the
|
||||
# forge environment gate enforces that)
|
||||
@@ -39,7 +39,7 @@
|
||||
# policy restricts view/update to resources tagged acdl:owner=<consumer-repo>.
|
||||
#
|
||||
# Override (where OIDC is unavailable, e.g. Gitea pending
|
||||
# go-gitea/gitea#36988): set ACDL_AWS_ACCESS_KEY_ID + ACDL_AWS_SECRET_ACCESS_KEY
|
||||
# go-gitea/gitea#36988): set NOVA_AWS_ACCESS_KEY_ID + NOVA_AWS_SECRET_ACCESS_KEY
|
||||
# as repository secrets. The platform-managed scheduled pipeline rotates
|
||||
# the key on a daily cadence. When .env.secrets is used locally instead,
|
||||
# rotating the key out of band is the consumer's responsibility.
|
||||
@@ -51,7 +51,7 @@ on:
|
||||
contract:
|
||||
description: Path to the consumer contract YAML (in the consumer repo)
|
||||
type: string
|
||||
default: .acdl/contract.yml
|
||||
default: .nova/contract.yml
|
||||
mode:
|
||||
description: Pipeline mode — full (apply), plan-only, check-only, or decommission
|
||||
type: string
|
||||
@@ -102,10 +102,14 @@ jobs:
|
||||
- name: Configure AWS credentials (OIDC default + static-key override)
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID == '' && format('arn:aws:iam::{0}:role/acdl-deploy-{1}', secrets.ACDL_AWS_ACCOUNT_ID, github.repository_id) || '' }}
|
||||
# TODO(P4, REQ-163): rename the IAM role acdl-deploy- → nova-deploy-.
|
||||
# The role ARN string is left as acdl-deploy- until P4 (IAM role
|
||||
# rename territory); only the secret REFERENCES are updated to
|
||||
# NOVA_* in P2 (G-108 binding).
|
||||
role-to-assume: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID == '' && format('arn:aws:iam::{0}:role/acdl-deploy-{1}', secrets.NOVA_AWS_ACCOUNT_ID, github.repository_id) || '' }}
|
||||
aws-region: us-east-1
|
||||
access-key-id: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||
secret-access-key: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||
access-key-id: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
||||
secret-access-key: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
||||
|
||||
- name: Run the platform pipeline
|
||||
working-directory: ${{ github.workspace }}
|
||||
@@ -145,7 +149,7 @@ jobs:
|
||||
AWS_DEFAULT_REGION: us-east-1
|
||||
run: |
|
||||
aws lambda invoke-function-url \
|
||||
--function-url "${{ secrets.ACDL_LAMBDA_URL }}" \
|
||||
--function-url "${{ secrets.NOVA_LAMBDA_URL }}" \
|
||||
--cli-binary-format raw-in-base64-out \
|
||||
--payload "$(python3 -c "import json,os; print(json.dumps({'action':'report_error','consumerRepo':os.environ.get('GITHUB_REPOSITORY',''),'contractId':'${{ github.run_id }}','error':'Deploy pipeline failed. See run logs.','runUrl':'${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}','environment':'dev'}))")" \
|
||||
/dev/null || true
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# "plan" — the lifecycle scripts run `run_platform.sh --plan-only` (fast,
|
||||
# no AWS mutation, validates the contract->resolver->adapter->plan chain
|
||||
# for every module on every PR, with no AWS credentials or cost). Set to
|
||||
# "full" via workflow_dispatch (or the ACDL_LIFECYCLE_MODE repo variable)
|
||||
# "full" via workflow_dispatch (or the NOVA_LIFECYCLE_MODE repo variable)
|
||||
# to run the real apply→modify→destroy against live AWS. In plan mode the
|
||||
# short-lived CI VPC apply/destroy jobs are skipped (nothing is applied).
|
||||
#
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
ci-vpc-apply:
|
||||
name: CI VPC apply
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.inputs.lifecycle_mode != 'plan' && vars.ACDL_LIFECYCLE_MODE != 'plan' }}
|
||||
if: ${{ github.event.inputs.lifecycle_mode != 'plan' && vars.NOVA_LIFECYCLE_MODE != 'plan' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Terraform 1.9.*
|
||||
@@ -60,8 +60,8 @@ jobs:
|
||||
- name: Apply CI VPC
|
||||
working-directory: terraform/ci-vpc
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: us-east-1
|
||||
run: |
|
||||
terraform init -input=false -lock=false
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
matrix:
|
||||
module: [s3, kms-key, ecr, ecs-cluster, iam-role, cloudfront, waf, vpc, alb, ecs-service, rds, uptime]
|
||||
env:
|
||||
ACDL_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.ACDL_LIFECYCLE_MODE || 'plan' }}
|
||||
NOVA_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.NOVA_LIFECYCLE_MODE || 'plan' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Free disk space
|
||||
@@ -97,31 +97,31 @@ jobs:
|
||||
echo "deb [signed-by=/usr/share/keyrings/hashicorp.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
|
||||
sudo apt-get update && sudo apt-get install -y terraform=1.9.*
|
||||
- name: Read CI VPC outputs
|
||||
if: ${{ env.ACDL_LIFECYCLE_MODE == 'full' }}
|
||||
if: ${{ env.NOVA_LIFECYCLE_MODE == 'full' }}
|
||||
working-directory: terraform/ci-vpc
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: us-east-1
|
||||
run: |
|
||||
terraform init -input=false -lock=false
|
||||
terraform output -json > /tmp/ci-vpc-outputs.json
|
||||
- name: Apply (simple)
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: us-east-1
|
||||
run: bash scripts/run_lifecycle_test.sh ${{ matrix.module }} simple /tmp/ci-vpc-outputs.json
|
||||
- name: Modify (complex)
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: us-east-1
|
||||
run: bash scripts/run_lifecycle_test.sh ${{ matrix.module }} complex /tmp/ci-vpc-outputs.json
|
||||
- name: Destroy
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: us-east-1
|
||||
run: bash scripts/run_lifecycle_destroy.sh ${{ matrix.module }} /tmp/ci-vpc-outputs.json
|
||||
|
||||
@@ -136,7 +136,7 @@ jobs:
|
||||
matrix:
|
||||
module: [static-assets, microservice]
|
||||
env:
|
||||
ACDL_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.ACDL_LIFECYCLE_MODE || 'plan' }}
|
||||
NOVA_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.NOVA_LIFECYCLE_MODE || 'plan' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Free disk space
|
||||
@@ -155,31 +155,31 @@ jobs:
|
||||
echo "deb [signed-by=/usr/share/keyrings/hashicorp.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
|
||||
sudo apt-get update && sudo apt-get install -y terraform=1.9.*
|
||||
- name: Read CI VPC outputs
|
||||
if: ${{ env.ACDL_LIFECYCLE_MODE == 'full' }}
|
||||
if: ${{ env.NOVA_LIFECYCLE_MODE == 'full' }}
|
||||
working-directory: terraform/ci-vpc
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: us-east-1
|
||||
run: |
|
||||
terraform init -input=false -lock=false
|
||||
terraform output -json > /tmp/ci-vpc-outputs.json
|
||||
- name: Apply (simple)
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: us-east-1
|
||||
run: bash scripts/run_l2_lifecycle_test.sh ${{ matrix.module }} simple /tmp/ci-vpc-outputs.json
|
||||
- name: Modify (complex)
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: us-east-1
|
||||
run: bash scripts/run_l2_lifecycle_test.sh ${{ matrix.module }} complex /tmp/ci-vpc-outputs.json
|
||||
- name: Destroy
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: us-east-1
|
||||
run: bash scripts/run_l2_lifecycle_destroy.sh ${{ matrix.module }} /tmp/ci-vpc-outputs.json
|
||||
|
||||
@@ -188,7 +188,7 @@ jobs:
|
||||
name: CI VPC destroy
|
||||
needs: [lifecycle, l2-lifecycle]
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ always() && github.event.inputs.lifecycle_mode != 'plan' && vars.ACDL_LIFECYCLE_MODE != 'plan' }}
|
||||
if: ${{ always() && github.event.inputs.lifecycle_mode != 'plan' && vars.NOVA_LIFECYCLE_MODE != 'plan' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Terraform 1.9.*
|
||||
@@ -199,8 +199,8 @@ jobs:
|
||||
- name: Destroy CI VPC
|
||||
working-directory: terraform/ci-vpc
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: us-east-1
|
||||
run: |
|
||||
terraform init -input=false -lock=false
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
# platform log) for auditability.
|
||||
#
|
||||
# Inputs:
|
||||
# contract — path to the consumer's contract YAML (default .acdl/contract.yml)
|
||||
# contract — path to the consumer's contract YAML (default .nova/contract.yml)
|
||||
# mode — full | plan-only | check-only (default full; dev = full apply,
|
||||
# higher environments hold for HITL — the calling repo or the
|
||||
# forge environment gate enforces that)
|
||||
@@ -39,7 +39,7 @@
|
||||
# policy restricts view/update to resources tagged acdl:owner=<consumer-repo>.
|
||||
#
|
||||
# Override (where OIDC is unavailable, e.g. Gitea pending
|
||||
# go-gitea/gitea#36988): set ACDL_AWS_ACCESS_KEY_ID + ACDL_AWS_SECRET_ACCESS_KEY
|
||||
# go-gitea/gitea#36988): set NOVA_AWS_ACCESS_KEY_ID + NOVA_AWS_SECRET_ACCESS_KEY
|
||||
# as repository secrets. The platform-managed scheduled pipeline rotates
|
||||
# the key on a daily cadence. When .env.secrets is used locally instead,
|
||||
# rotating the key out of band is the consumer's responsibility.
|
||||
@@ -51,7 +51,7 @@ on:
|
||||
contract:
|
||||
description: Path to the consumer contract YAML (in the consumer repo)
|
||||
type: string
|
||||
default: .acdl/contract.yml
|
||||
default: .nova/contract.yml
|
||||
mode:
|
||||
description: Pipeline mode — full (apply), plan-only, check-only, or decommission
|
||||
type: string
|
||||
@@ -102,10 +102,14 @@ jobs:
|
||||
- name: Configure AWS credentials (OIDC default + static-key override)
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
role-to-assume: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID == '' && format('arn:aws:iam::{0}:role/acdl-deploy-{1}', secrets.ACDL_AWS_ACCOUNT_ID, github.repository_id) || '' }}
|
||||
# TODO(P4, REQ-163): rename the IAM role acdl-deploy- → nova-deploy-.
|
||||
# The role ARN string is left as acdl-deploy- until P4 (IAM role
|
||||
# rename territory); only the secret REFERENCES are updated to
|
||||
# NOVA_* in P2 (G-108 binding).
|
||||
role-to-assume: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID == '' && format('arn:aws:iam::{0}:role/acdl-deploy-{1}', secrets.NOVA_AWS_ACCOUNT_ID, github.repository_id) || '' }}
|
||||
aws-region: us-east-1
|
||||
access-key-id: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||
secret-access-key: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||
access-key-id: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
||||
secret-access-key: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
||||
|
||||
- name: Run the platform pipeline
|
||||
working-directory: ${{ github.workspace }}
|
||||
@@ -145,7 +149,7 @@ jobs:
|
||||
AWS_DEFAULT_REGION: us-east-1
|
||||
run: |
|
||||
aws lambda invoke-function-url \
|
||||
--function-url "${{ secrets.ACDL_LAMBDA_URL }}" \
|
||||
--function-url "${{ secrets.NOVA_LAMBDA_URL }}" \
|
||||
--cli-binary-format raw-in-base64-out \
|
||||
--payload "$(python3 -c "import json,os; print(json.dumps({'action':'report_error','consumerRepo':os.environ.get('GITHUB_REPOSITORY',''),'contractId':'${{ github.run_id }}','error':'Deploy pipeline failed. See run logs.','runUrl':'${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}','environment':'dev'}))")" \
|
||||
/dev/null || true
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# "plan" — the lifecycle scripts run `run_platform.sh --plan-only` (fast,
|
||||
# no AWS mutation, validates the contract->resolver->adapter->plan chain
|
||||
# for every module on every PR, with no AWS credentials or cost). Set to
|
||||
# "full" via workflow_dispatch (or the ACDL_LIFECYCLE_MODE repo variable)
|
||||
# "full" via workflow_dispatch (or the NOVA_LIFECYCLE_MODE repo variable)
|
||||
# to run the real apply→modify→destroy against live AWS. In plan mode the
|
||||
# short-lived CI VPC apply/destroy jobs are skipped (nothing is applied).
|
||||
#
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
ci-vpc-apply:
|
||||
name: CI VPC apply
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.inputs.lifecycle_mode != 'plan' && vars.ACDL_LIFECYCLE_MODE != 'plan' }}
|
||||
if: ${{ github.event.inputs.lifecycle_mode != 'plan' && vars.NOVA_LIFECYCLE_MODE != 'plan' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Terraform 1.9.*
|
||||
@@ -60,8 +60,8 @@ jobs:
|
||||
- name: Apply CI VPC
|
||||
working-directory: terraform/ci-vpc
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: us-east-1
|
||||
run: |
|
||||
terraform init -input=false -lock=false
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
matrix:
|
||||
module: [s3, kms-key, ecr, ecs-cluster, iam-role, cloudfront, waf, vpc, alb, ecs-service, rds, uptime]
|
||||
env:
|
||||
ACDL_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.ACDL_LIFECYCLE_MODE || 'plan' }}
|
||||
NOVA_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.NOVA_LIFECYCLE_MODE || 'plan' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Free disk space
|
||||
@@ -97,31 +97,31 @@ jobs:
|
||||
echo "deb [signed-by=/usr/share/keyrings/hashicorp.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
|
||||
sudo apt-get update && sudo apt-get install -y terraform=1.9.*
|
||||
- name: Read CI VPC outputs
|
||||
if: ${{ env.ACDL_LIFECYCLE_MODE == 'full' }}
|
||||
if: ${{ env.NOVA_LIFECYCLE_MODE == 'full' }}
|
||||
working-directory: terraform/ci-vpc
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: us-east-1
|
||||
run: |
|
||||
terraform init -input=false -lock=false
|
||||
terraform output -json > /tmp/ci-vpc-outputs.json
|
||||
- name: Apply (simple)
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: us-east-1
|
||||
run: bash scripts/run_lifecycle_test.sh ${{ matrix.module }} simple /tmp/ci-vpc-outputs.json
|
||||
- name: Modify (complex)
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: us-east-1
|
||||
run: bash scripts/run_lifecycle_test.sh ${{ matrix.module }} complex /tmp/ci-vpc-outputs.json
|
||||
- name: Destroy
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: us-east-1
|
||||
run: bash scripts/run_lifecycle_destroy.sh ${{ matrix.module }} /tmp/ci-vpc-outputs.json
|
||||
|
||||
@@ -136,7 +136,7 @@ jobs:
|
||||
matrix:
|
||||
module: [static-assets, microservice]
|
||||
env:
|
||||
ACDL_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.ACDL_LIFECYCLE_MODE || 'plan' }}
|
||||
NOVA_LIFECYCLE_MODE: ${{ github.event.inputs.lifecycle_mode || vars.NOVA_LIFECYCLE_MODE || 'plan' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Free disk space
|
||||
@@ -155,31 +155,31 @@ jobs:
|
||||
echo "deb [signed-by=/usr/share/keyrings/hashicorp.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
|
||||
sudo apt-get update && sudo apt-get install -y terraform=1.9.*
|
||||
- name: Read CI VPC outputs
|
||||
if: ${{ env.ACDL_LIFECYCLE_MODE == 'full' }}
|
||||
if: ${{ env.NOVA_LIFECYCLE_MODE == 'full' }}
|
||||
working-directory: terraform/ci-vpc
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: us-east-1
|
||||
run: |
|
||||
terraform init -input=false -lock=false
|
||||
terraform output -json > /tmp/ci-vpc-outputs.json
|
||||
- name: Apply (simple)
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: us-east-1
|
||||
run: bash scripts/run_l2_lifecycle_test.sh ${{ matrix.module }} simple /tmp/ci-vpc-outputs.json
|
||||
- name: Modify (complex)
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: us-east-1
|
||||
run: bash scripts/run_l2_lifecycle_test.sh ${{ matrix.module }} complex /tmp/ci-vpc-outputs.json
|
||||
- name: Destroy
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: us-east-1
|
||||
run: bash scripts/run_l2_lifecycle_destroy.sh ${{ matrix.module }} /tmp/ci-vpc-outputs.json
|
||||
|
||||
@@ -188,7 +188,7 @@ jobs:
|
||||
name: CI VPC destroy
|
||||
needs: [lifecycle, l2-lifecycle]
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ always() && github.event.inputs.lifecycle_mode != 'plan' && vars.ACDL_LIFECYCLE_MODE != 'plan' }}
|
||||
if: ${{ always() && github.event.inputs.lifecycle_mode != 'plan' && vars.NOVA_LIFECYCLE_MODE != 'plan' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Terraform 1.9.*
|
||||
@@ -199,8 +199,8 @@ jobs:
|
||||
- name: Destroy CI VPC
|
||||
working-directory: terraform/ci-vpc
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ACDL_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ACDL_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.NOVA_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.NOVA_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: us-east-1
|
||||
run: |
|
||||
terraform init -input=false -lock=false
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ACDL Release Pipeline — GitHub Actions (production)
|
||||
# Nova Release Pipeline — GitHub Actions (production)
|
||||
#
|
||||
# Runs on push to main. Computes the next semver tag from the latest tag +
|
||||
# commit history, creates the tag, updates floating MAJOR.MINOR and MAJOR tags,
|
||||
@@ -8,7 +8,7 @@
|
||||
# - Regular phase commit -> bump PATCH (v1.6.0 -> v1.6.1)
|
||||
# - Milestone completion ("docs(milestone): complete") -> bump MINOR (v1.6.1 -> v1.7.0)
|
||||
# - Major bumps are manual (not implemented here).
|
||||
name: acdl-release
|
||||
name: nova-release
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -87,6 +87,6 @@ jobs:
|
||||
BODY=$(git log --format='- %s' HEAD)
|
||||
fi
|
||||
gh release create ${{ steps.version.outputs.new_tag }} \
|
||||
--title "ACDL ${{ steps.version.outputs.new_tag }}" \
|
||||
--title "Nova ${{ steps.version.outputs.new_tag }}" \
|
||||
--notes "$BODY" \
|
||||
--generate-notes || true
|
||||
@@ -1,4 +1,6 @@
|
||||
# ACDL — Agentic Cloud Delivery Platform
|
||||
# Nova
|
||||
|
||||
> **Nova — The New Dawn of DevSecOps.** Security as a seamless enabler of fast deployments — not a bottleneck, not a "no" department.
|
||||
|
||||
Consumers declare intent; the platform delivers safe production deployment
|
||||
through an agentic stack — automatically, safely, and with a complete audit
|
||||
@@ -18,7 +20,7 @@ a configuration file, or an infrastructure module.
|
||||
|
||||
## Repository roles
|
||||
|
||||
There are two kinds of repository in the ACDL model:
|
||||
There are two kinds of repository in the Nova model:
|
||||
|
||||
- **Platform repo (this one).** This is the **source code of the platform**.
|
||||
It owns `modules/`, `adapters/`, `core/`, `schemas/`, `pipelines/`,
|
||||
@@ -93,7 +95,7 @@ intent via a contract; the platform delivers the deployment through the
|
||||
same contract schema, the same policy envelope, and the same evidence
|
||||
stream.
|
||||
|
||||
Consumers have their own repos and consume ACDL by writing a contract that
|
||||
Consumers have their own repos and consume Nova by writing a contract that
|
||||
declares infrastructure. A consumer declares a contract (id + name +
|
||||
environment + infrastructure); the platform resolves it to a stack instance,
|
||||
compiles it, runs security + policy checks, computes a confidence signal,
|
||||
@@ -223,7 +225,7 @@ The workflow implements the same stages as `pipelines/contract.yml`
|
||||
→ policy checks → confidence → evidence event → apply). A consumer repo
|
||||
invokes the reusable workflow via a **versioned tag** (floating MAJOR +
|
||||
MINOR, e.g. `acdl/.github/workflows/deploy.yml@v1.13`). The workflow checks
|
||||
out the consumer repo, then checks out the ACDL platform repo into the
|
||||
out the consumer repo, then checks out the Nova platform repo into the
|
||||
runner workspace, and runs `scripts/run_platform.sh` against the consumer's
|
||||
contract — the consumer never clones the platform repo or invokes its
|
||||
scripts locally. See the [Consumer guide](docs/consumer-guide.md) for the
|
||||
@@ -247,7 +249,7 @@ backwards-compatible log-only mode.
|
||||
## Consumer guide
|
||||
|
||||
A step-by-step guide for a consumer to create their pipeline and define a
|
||||
contract that deploys any ACDL module to AWS is at
|
||||
contract that deploys any Nova module to AWS is at
|
||||
[`docs/consumer-guide.md`](docs/consumer-guide.md). The guide is generic
|
||||
across all modules; `static-assets` is the worked example.
|
||||
|
||||
|
||||
@@ -10,9 +10,10 @@ lives in the per-module terraform/ subdir, NOT in this file.
|
||||
CLI: adapter.py <instance.json> <out_dir>
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import json, os, sys
|
||||
_R = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
sys.path.insert(0, _R) if _R not in sys.path else None
|
||||
from core import env
|
||||
|
||||
|
||||
def _load_registry(repo_root):
|
||||
@@ -112,7 +113,7 @@ def adapt(stack_instance, out_dir):
|
||||
|
||||
stack_name = stack.get("name", "spike")
|
||||
environment = stack.get("environment", "dev")
|
||||
account_id = os.environ.get("ACDL_AWS_ACCOUNT_ID", "581513795199")
|
||||
account_id = env.get_env("AWS_ACCOUNT_ID", "581513795199")
|
||||
state_bucket = f"acdl-tfstate-{account_id}-us-east-1"
|
||||
terraform_tf = (
|
||||
'terraform {\n'
|
||||
@@ -134,7 +135,7 @@ def adapt(stack_instance, out_dir):
|
||||
data_source_names = stack_instance.get("data_sources", [])
|
||||
parts = []
|
||||
if data_source_names:
|
||||
remote_state_key = os.environ.get("ACDL_REMOTE_STATE_KEY", "platform/terraform.tfstate")
|
||||
remote_state_key = env.get_env("REMOTE_STATE_KEY", "platform/terraform.tfstate")
|
||||
parts.append(
|
||||
'data "terraform_remote_state" "platform" {\n'
|
||||
' backend = "s3"\n'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Translate Checkov JSON output to ACDL PolicyCheckResult records.
|
||||
"""Translate Checkov JSON output to Nova PolicyCheckResult records.
|
||||
|
||||
Reads Checkov's JSON output (one framework key, e.g. terraform_plan),
|
||||
emits a list of PolicyCheckResult dicts conforming to
|
||||
@@ -6,10 +6,13 @@ schemas/policy_check_result.schema.json. Run Checkov with --soft-fail so
|
||||
Checkov never exits non-zero; the confidence signal decides the gate, not
|
||||
Checkov's exit code.
|
||||
|
||||
The ACDL tagging standard (D-054, D-043 closure) is enforced by a custom
|
||||
Checkov rule at adapters/terraform/policy/custom_rules/acdl_tagging.py,
|
||||
loaded via --external-checks-dir. The adapter therefore maps
|
||||
ACDL_TAG_NAMING as a real rule (no synthetic SKIPPED record is emitted).
|
||||
The Nova tagging standard (D-054, D-043 closure, D-109 warn mode in P2)
|
||||
is enforced by a custom Checkov rule at
|
||||
adapters/terraform/policy/custom_rules/nova_tagging.py, loaded via
|
||||
--external-checks-dir. The adapter therefore maps NOVA_TAG_NAMING as a
|
||||
real rule (no synthetic SKIPPED record is emitted). Renamed from
|
||||
ACDL_TAG_NAMING in P2 (REQ-158); the rule is in warn mode for P2
|
||||
(legacy acdl:* tag-key values stay until P3).
|
||||
"""
|
||||
|
||||
import datetime
|
||||
@@ -29,10 +32,12 @@ RULE_MAP = {
|
||||
"CKV_AWS_40": ("iam-wildcard", "medium"),
|
||||
"CKV_AWS_7": ("kms-key-reference", "medium"),
|
||||
"CKV_AWS_33": ("kms-key-reference", "medium"),
|
||||
# D-054 / D-043 closure: ACDL_TAG_NAMING is now a real custom Checkov
|
||||
# rule (adapters/terraform/policy/custom_rules/acdl_tagging.py), loaded
|
||||
# via --external-checks-dir. No synthetic SKIPPED record is emitted.
|
||||
"ACDL_TAG_NAMING": ("tagging-standard", "medium"),
|
||||
# D-054 / D-043 closure, D-109 warn mode (P2): NOVA_TAG_NAMING is a real
|
||||
# custom Checkov rule (adapters/terraform/policy/custom_rules/nova_tagging.py),
|
||||
# loaded via --external-checks-dir. No synthetic SKIPPED record is emitted.
|
||||
# Renamed from ACDL_TAG_NAMING in P2 (REQ-158). Warn mode treats legacy
|
||||
# acdl:*-only tags as a warning (P3 flips to hard-fail).
|
||||
"NOVA_TAG_NAMING": ("tagging-standard", "medium"),
|
||||
}
|
||||
|
||||
_RESULT_MAP = {"PASSED": "pass", "FAILED": "fail", "SKIPPED": "skipped"}
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
# ACDL Custom Checkov Rules
|
||||
# Nova Custom Checkov Rules
|
||||
|
||||
This directory holds ACDL-authored Checkov custom rules, written in the
|
||||
This directory holds Nova-authored Checkov custom rules, written in the
|
||||
[Checkov Python custom-rule framework](https://www.checkov.io/4.Contributing/Custom%20Policies.html).
|
||||
|
||||
## Files
|
||||
|
||||
- `acdl_tagging.py` — `ACDL_TAG_NAMING` (D-054): ensures every taggable AWS
|
||||
resource carries the four required ACDL tags
|
||||
(`acdl:owner`, `acdl:contract`, `acdl:environment`, `acdl:cost-center`).
|
||||
This rule replaces the synthetic SKIPPED `ACDL_TAG_NAMING` record that the
|
||||
Checkov adapter previously emitted (D-043 closure). The canonical tag set
|
||||
is declared in [`schemas/tagging-standard.json`](../../../schemas/tagging-standard.json).
|
||||
- `nova_tagging.py` — `NOVA_TAG_NAMING` (D-054, D-109 warn mode in P2):
|
||||
ensures every taggable AWS resource carries the four required Nova tags
|
||||
(`nova:owner`, `nova:contract`, `nova:environment`, `nova:cost-center`).
|
||||
This rule replaces the synthetic SKIPPED `NOVA_TAG_NAMING` record that the
|
||||
Checkov adapter previously emitted (D-043 closure). Renamed from
|
||||
`acdl_tagging.py` / `ACDL_TAG_NAMING` in P2 (REQ-158). The canonical tag
|
||||
set is declared in [`schemas/tagging-standard.json`](../../../schemas/tagging-standard.json).
|
||||
|
||||
**P2 warn mode (D-109):** existing resources still carry `acdl:*` tag-key
|
||||
values (left for P3). When a resource has only `acdl:*`-style tags and no
|
||||
`nova:*` tags, the rule logs a WARNING instead of failing, so the
|
||||
regression gate stays green during the parallel-tag transition window.
|
||||
P3 flips to hard-fail once `nova:*` tags are emitted in parallel and the
|
||||
ABAC policy is swapped.
|
||||
|
||||
## How Checkov loads them
|
||||
|
||||
@@ -23,12 +31,12 @@ checkov -f terraform/spike/main.tf --framework terraform -o json --soft-fail \
|
||||
```
|
||||
|
||||
Checkov imports each `*.py` file in the directory and instantiates the
|
||||
module-level `check` object (see the `check = AcdlTaggingStandard()` line at
|
||||
the bottom of `acdl_tagging.py`).
|
||||
module-level `check` object (see the `check = NovaTaggingStandard()` line at
|
||||
the bottom of `nova_tagging.py`).
|
||||
|
||||
## Severity / result mapping
|
||||
|
||||
The Checkov adapter (`adapters/terraform/policy/checkov_adapter.py`)
|
||||
maps `ACDL_TAG_NAMING` to `(tagging-standard, medium)` in `RULE_MAP`. The
|
||||
maps `NOVA_TAG_NAMING` to `(tagging-standard, medium)` in `RULE_MAP`. The
|
||||
custom rule therefore produces real `PASS`/`FAIL` PolicyCheckResult records,
|
||||
feeding the confidence signal instead of the old SKIPPED placeholder.
|
||||
@@ -1,54 +0,0 @@
|
||||
"""ACDL tagging standard custom Checkov rule (D-054).
|
||||
|
||||
Checks that all taggable AWS resources have the required ACDL tags:
|
||||
acdl:owner, acdl:contract, acdl:environment, acdl:cost-center
|
||||
|
||||
Fails (severity medium) when any required tag is missing.
|
||||
Closes the D-043 deferral (the SKIPPED ACDL_TAG_NAMING placeholder
|
||||
becomes a real check).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
|
||||
from checkov.common.models.enums import CheckResult, CheckCategories
|
||||
|
||||
REQUIRED_TAGS = ("acdl:owner", "acdl:contract", "acdl:environment", "acdl:cost-center")
|
||||
|
||||
# Resources that support tags (exclude resources that have no tags attribute)
|
||||
NON_TAGGABLE_TYPES = (
|
||||
"aws_cloudfront_origin_access_control",
|
||||
"aws_lambda_function_url",
|
||||
"aws_route_table_association",
|
||||
"aws_internet_gateway",
|
||||
)
|
||||
|
||||
class AcdlTaggingStandard(BaseResourceCheck):
|
||||
def __init__(self):
|
||||
name = "Ensure all taggable AWS resources have required ACDL tags"
|
||||
check_id = "ACDL_TAG_NAMING"
|
||||
supported_resources = ["*"] # all resources
|
||||
categories = [CheckCategories.GENERAL_SECURITY]
|
||||
super().__init__(name=name, check_id=check_id, categories=categories, supported_resources=supported_resources)
|
||||
|
||||
def scan_resource_conf(self, conf, entity_type):
|
||||
# Skip non-taggable resources
|
||||
if entity_type in NON_TAGGABLE_TYPES:
|
||||
return CheckResult.PASSED
|
||||
# Check for a tags block
|
||||
tags = conf.get("tags")
|
||||
if not tags:
|
||||
return CheckResult.FAILED
|
||||
tag_keys = set()
|
||||
if isinstance(tags, list) and tags:
|
||||
tag_block = tags[0]
|
||||
if isinstance(tag_block, dict):
|
||||
tag_keys = set(tag_block.keys())
|
||||
elif isinstance(tags, dict):
|
||||
tag_keys = set(tags.keys())
|
||||
missing = [t for t in REQUIRED_TAGS if t not in tag_keys]
|
||||
if missing:
|
||||
return CheckResult.FAILED
|
||||
return CheckResult.PASSED
|
||||
|
||||
check = AcdlTaggingStandard()
|
||||
@@ -0,0 +1,88 @@
|
||||
"""Nova tagging standard custom Checkov rule (D-054, D-109 warn mode).
|
||||
|
||||
Checks that all taggable AWS resources have the required Nova tags:
|
||||
nova:owner, nova:contract, nova:environment, nova:cost-center
|
||||
|
||||
In **warn mode** (P2, REQ-158): existing resources still carry `acdl:*`
|
||||
tags (the legacy tag-key VALUES stay until P3). When a resource has
|
||||
only `acdl:*`-style tags and no `nova:*` tags, the rule logs a WARNING
|
||||
instead of failing, so the regression gate stays green during the
|
||||
parallel-tag transition window. P3 flips this to hard-fail (D-109 hard
|
||||
mode) once `nova:*` tags are emitted in parallel and the ABAC policy is
|
||||
swapped.
|
||||
|
||||
Closes the D-043 deferral (the SKIPPED NOVA_TAG_NAMING placeholder
|
||||
becomes a real check). Renamed from acdl_tagging.py in P2 (REQ-158);
|
||||
the Checkov rule ID ACDL_TAG_NAMING → NOVA_TAG_NAMING.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
|
||||
from checkov.common.models.enums import CheckResult, CheckCategories
|
||||
|
||||
REQUIRED_TAGS = ("nova:owner", "nova:contract", "nova:environment", "nova:cost-center")
|
||||
|
||||
# Legacy acdl:* tag keys — the parallel-tag period (P3) emits both nova:*
|
||||
# and acdl:*; P2 warn mode treats acdl:*-only tags as a warning, not a
|
||||
# failure. The acdl:* VALUES in tagging-standard.json are left for P3.
|
||||
LEGACY_TAGS = ("acdl:owner", "acdl:contract", "acdl:environment", "acdl:cost-center")
|
||||
|
||||
# Resources that support tags (exclude resources that have no tags attribute)
|
||||
NON_TAGGABLE_TYPES = (
|
||||
"aws_cloudfront_origin_access_control",
|
||||
"aws_lambda_function_url",
|
||||
"aws_route_table_association",
|
||||
"aws_internet_gateway",
|
||||
)
|
||||
|
||||
# P2 warn mode (D-109): emit a warning (not a hard FAIL) when a resource
|
||||
# carries only legacy acdl:* tags and no nova:* tags. P3 flips this to
|
||||
# False (hard-fail). Set NOVA_TAGGING_HARD=1 to opt into hard mode early
|
||||
# (used by P3 tests before the P3 flip lands).
|
||||
_WARN_MODE = True
|
||||
|
||||
|
||||
class NovaTaggingStandard(BaseResourceCheck):
|
||||
def __init__(self):
|
||||
name = "Ensure all taggable AWS resources have required Nova tags"
|
||||
check_id = "NOVA_TAG_NAMING"
|
||||
supported_resources = ["*"] # all resources
|
||||
categories = [CheckCategories.GENERAL_SECURITY]
|
||||
super().__init__(name=name, check_id=check_id, categories=categories, supported_resources=supported_resources)
|
||||
|
||||
def scan_resource_conf(self, conf, entity_type):
|
||||
# Skip non-taggable resources
|
||||
if entity_type in NON_TAGGABLE_TYPES:
|
||||
return CheckResult.PASSED
|
||||
# Check for a tags block
|
||||
tags = conf.get("tags")
|
||||
if not tags:
|
||||
return CheckResult.FAILED
|
||||
tag_keys = set()
|
||||
if isinstance(tags, list) and tags:
|
||||
tag_block = tags[0]
|
||||
if isinstance(tag_block, dict):
|
||||
tag_keys = set(tag_block.keys())
|
||||
elif isinstance(tags, dict):
|
||||
tag_keys = set(tags.keys())
|
||||
missing = [t for t in REQUIRED_TAGS if t not in tag_keys]
|
||||
if not missing:
|
||||
return CheckResult.PASSED
|
||||
# Warn mode (D-109, P2): if the resource carries the legacy acdl:*
|
||||
# tag keys for every required tag, emit a warning rather than a
|
||||
# hard fail — existing resources still carry acdl:* until P3.
|
||||
if _WARN_MODE:
|
||||
has_all_legacy = all(t in tag_keys for t in LEGACY_TAGS)
|
||||
if has_all_legacy:
|
||||
sys.stderr.write(
|
||||
f"[nova_tagging] WARN: {entity_type} has legacy acdl:* tags "
|
||||
f"but no nova:* tags (P2 warn mode, D-109). Migrate to "
|
||||
f"nova:* tags before P5.\n"
|
||||
)
|
||||
return CheckResult.PASSED
|
||||
return CheckResult.FAILED
|
||||
|
||||
check = NovaTaggingStandard()
|
||||
@@ -1,4 +1,4 @@
|
||||
# ACDL sample consumer contract — microservice module (dev)
|
||||
# Nova sample consumer contract — microservice module (dev)
|
||||
# Per-environment contract (REQ-105). Promotion = running the dev job;
|
||||
# no environment field editing. Interpolation resolves against dev.json.
|
||||
id: msvc
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ACDL sample consumer contract — microservice module (dr)
|
||||
# Nova sample consumer contract — microservice module (dr)
|
||||
# Per-environment contract (REQ-105). Promotion = running the dr job;
|
||||
# no environment field editing. Interpolation resolves against dr.json.
|
||||
id: msvc
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ACDL sample consumer contract — microservice module (prod)
|
||||
# Nova sample consumer contract — microservice module (prod)
|
||||
# Per-environment contract (REQ-105). Promotion = running the prod job;
|
||||
# no environment field editing. Interpolation resolves against prod.json.
|
||||
id: msvc
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ACDL sample consumer contract — microservice module (qa)
|
||||
# Nova sample consumer contract — microservice module (qa)
|
||||
# Per-environment contract (REQ-105). Promotion = running the qa job;
|
||||
# no environment field editing. Interpolation resolves against qa.json.
|
||||
id: msvc
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ACDL sample consumer contract — microservice module (dev)
|
||||
# Nova sample consumer contract — microservice module (dev)
|
||||
#
|
||||
# Reference example for an ECS Fargate microservice deployment.
|
||||
# Interpolation (D-081): bucket_name uses the naming pattern that includes
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ACDL sample consumer contract — static-assets module (dev)
|
||||
# Nova sample consumer contract — static-assets module (dev)
|
||||
# Per-environment contract (REQ-105). The dev default
|
||||
# (contracts/static-assets.yml) remains for backwards compat; this file
|
||||
# is the explicit per-env dev contract. Interpolation resolves against dev.json.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ACDL sample consumer contract — static-assets module (dr)
|
||||
# Nova sample consumer contract — static-assets module (dr)
|
||||
# Per-environment contract (REQ-105). Promotion = running the dr job;
|
||||
# no environment field editing. Interpolation resolves against dr.json.
|
||||
id: assets
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ACDL sample consumer contract — static-assets module (prod)
|
||||
# Nova sample consumer contract — static-assets module (prod)
|
||||
# Per-environment contract (REQ-105). Promotion = running the prod job;
|
||||
# no environment field editing. Interpolation resolves against prod.json.
|
||||
id: assets
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ACDL sample consumer contract — static-assets module (qa)
|
||||
# Nova sample consumer contract — static-assets module (qa)
|
||||
# Per-environment contract (REQ-105). Promotion = running the qa job;
|
||||
# no environment field editing. Interpolation resolves against qa.json.
|
||||
id: assets
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ACDL sample consumer contract — static-assets module (dev)
|
||||
# Nova sample consumer contract — static-assets module (dev)
|
||||
#
|
||||
# This is the reference example for a consumer contract. It declares:
|
||||
# id: short operational acronym (becomes stack.name for state, tags, evidence)
|
||||
|
||||
@@ -14,7 +14,8 @@ concerns split into two tiers:
|
||||
The operator-supplied evidence artifact is a JSON blob with `timestamp`,
|
||||
`type`, `payload`, and an optional `signature` (JWS detached). Freshness
|
||||
is validated against the window from §10.4. Signature verification runs
|
||||
when `ACDL_ATTESTATION_SIGNING_KEY_ID` is set; it is skipped + logged
|
||||
when `NOVA_ATTESTATION_SIGNING_KEY_ID` is set (dual-read via core/env.py:
|
||||
NOVA_* preferred, ACDL_* fallback until P5); it is skipped + logged
|
||||
when unset (dev/CI — D-089). The matrix fails loud if an operator-supplied
|
||||
concern is missing or expired for prod/dr.
|
||||
"""
|
||||
@@ -24,6 +25,14 @@ import os
|
||||
import sys
|
||||
from typing import Optional, Tuple
|
||||
|
||||
# Repo root on sys.path so `from core import env` resolves to THIS package
|
||||
# when run as a script (avoids editable-installed third-party `core` shadow).
|
||||
_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if _REPO_ROOT not in sys.path:
|
||||
sys.path.insert(0, _REPO_ROOT)
|
||||
|
||||
from core import env
|
||||
|
||||
|
||||
# Freshness windows (days) from hitl_matrix_design.md §10.4.
|
||||
FRESHNESS_DAYS = {
|
||||
@@ -81,14 +90,15 @@ def _is_fresh(artifact: dict, concern: str) -> bool:
|
||||
|
||||
|
||||
def _verify_signature(artifact: dict) -> bool:
|
||||
"""Verify the JWS detached signature when ACDL_ATTESTATION_SIGNING_KEY_ID is set.
|
||||
"""Verify the JWS detached signature when NOVA_ATTESTATION_SIGNING_KEY_ID is set.
|
||||
|
||||
When unset (dev/CI — D-089), signature verification is skipped + logged.
|
||||
Dual-read via core/env.py: NOVA_* preferred, ACDL_* fallback until P5.
|
||||
"""
|
||||
key_id = os.environ.get("ACDL_ATTESTATION_SIGNING_KEY_ID", "")
|
||||
key_id = env.get_env("ATTESTATION_SIGNING_KEY_ID", "") or ""
|
||||
if not key_id:
|
||||
sys.stderr.write(
|
||||
"[attestation] ACDL_ATTESTATION_SIGNING_KEY_ID unset — "
|
||||
"[attestation] NOVA_ATTESTATION_SIGNING_KEY_ID unset — "
|
||||
"signature verification skipped (dev/CI, D-089)\n"
|
||||
)
|
||||
return True
|
||||
|
||||
@@ -36,6 +36,16 @@ import sys
|
||||
import yaml
|
||||
import jsonschema
|
||||
|
||||
# Ensure the repo root (parent of core/) is on sys.path so `from core
|
||||
# import env` resolves to THIS package when contract_resolver.py is run
|
||||
# as a script (python3 core/contract_resolver.py) — otherwise an
|
||||
# editable-installed third-party `core` package can shadow it.
|
||||
_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if _REPO_ROOT not in sys.path:
|
||||
sys.path.insert(0, _REPO_ROOT)
|
||||
|
||||
from core import env
|
||||
|
||||
|
||||
def _load_env(env_name, repo_root):
|
||||
"""Load the environment onboarding JSON for env_name.
|
||||
@@ -620,9 +630,10 @@ if __name__ == "__main__":
|
||||
idx = sys.argv.index("--environment")
|
||||
if idx + 1 < len(sys.argv):
|
||||
env_override = sys.argv[idx + 1]
|
||||
# Also honor the ACDL_ENVIRONMENT_OVERRIDE env var (used by run_platform.sh).
|
||||
if env_override is None and os.environ.get("ACDL_ENVIRONMENT_OVERRIDE"):
|
||||
env_override = os.environ["ACDL_ENVIRONMENT_OVERRIDE"]
|
||||
# Also honor the NOVA_ENVIRONMENT_OVERRIDE env var (used by run_platform.sh).
|
||||
# Dual-read via core/env.py: NOVA_* preferred, ACDL_* fallback until P5.
|
||||
if env_override is None and env.get_env("ENVIRONMENT_OVERRIDE"):
|
||||
env_override = env.get_env("ENVIRONMENT_OVERRIDE")
|
||||
result = resolve(contract_path, environment_override=env_override)
|
||||
with open(out_path, "w") as fh:
|
||||
json.dump(result, fh, indent=2)
|
||||
@@ -0,0 +1,48 @@
|
||||
"""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_<name>` first, then falls
|
||||
back to `ACDL_<name>`, 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_<name>`` (preferred)
|
||||
2. ``ACDL_<name>`` (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
|
||||
@@ -93,7 +93,7 @@ The full table (lifted verbatim from §10.4):
|
||||
The operator-supplied evidence artifact is a JSON blob with `timestamp`,
|
||||
`type`, `payload`, and an optional `signature` (JWS detached). Freshness
|
||||
is validated against the window above. Signature verification runs when
|
||||
`ACDL_ATTESTATION_SIGNING_KEY_ID` is set; it is skipped + logged when
|
||||
`NOVA_ATTESTATION_SIGNING_KEY_ID` is set; it is skipped + logged when
|
||||
unset (dev/CI — D-089). The matrix fails loud if an operator-supplied
|
||||
concern is missing or expired for prod/dr.
|
||||
|
||||
@@ -140,7 +140,7 @@ not Kyverno (in v1). Sequence:
|
||||
in the same process that has authority to block the promotion.
|
||||
|
||||
v1.9 implements `route_halt_artifact` as a real SNS publish (topic
|
||||
`acdl-sod-halt`, ARN from `ACDL_SOD_HALT_TOPIC_ARN`) with an outbox-event
|
||||
`acdl-sod-halt`, ARN from `NOVA_SOD_HALT_TOPIC_ARN`) with an outbox-event
|
||||
fallback when the topic ARN is unset (REQ-107). The attestation gate
|
||||
itself is `core/hitl_gates.py` (`attest(contract_id, env, approver,
|
||||
evidence)`), which records the approver to the outbox, runs the SoD
|
||||
@@ -171,5 +171,5 @@ v1.9 (Phase 41 + Phase 42) wires the gates end-to-end:
|
||||
concerns run for real; operator-supplied concerns accept signed
|
||||
evidence artifacts validated for freshness + schema.
|
||||
- **D-089** (v1.9) — attestation artifact signature verification is
|
||||
skipped when `ACDL_ATTESTATION_SIGNING_KEY_ID` is unset (dev/CI);
|
||||
skipped when `NOVA_ATTESTATION_SIGNING_KEY_ID` is unset (dev/CI);
|
||||
required for prod/dr.
|
||||
@@ -12,7 +12,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
|
||||
caller code path is unchanged; only the I/O target swaps. Selection is
|
||||
gated on the ACDL_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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -32,12 +33,20 @@ from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
# Repo root on sys.path so `from core import env` resolves to THIS package
|
||||
# when run as a script (avoids editable-installed third-party `core` shadow).
|
||||
_REPO_ROOT = str(Path(__file__).resolve().parent.parent)
|
||||
if _REPO_ROOT not in sys.path:
|
||||
sys.path.insert(0, _REPO_ROOT)
|
||||
|
||||
from core import env
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def is_local_tier() -> bool:
|
||||
"""True when the local emulating tier is active."""
|
||||
return os.environ.get("ACDL_LOCAL_TIER", "") == "1"
|
||||
return env.get_env("LOCAL_TIER", "") == "1"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -286,7 +295,7 @@ class LocalLambdaStub:
|
||||
|
||||
Returns the handler's response dict
|
||||
({statusCode, body}). The handler's DynamoDB calls are
|
||||
intercepted via the ACDL_LOCAL_TIER env var (the handler checks
|
||||
intercepted via the NOVA_LOCAL_TIER env var (the handler checks
|
||||
_get_dynamodb(); under local tier it would need patching - we
|
||||
patch the module's _get_dynamodb to return a local stub)."""
|
||||
# Import the handler module (the dir is named `lambda`, a Python
|
||||
@@ -490,6 +499,9 @@ def run_local_e2e(contract_path: str, repo_root: Optional[Path] = None) -> Dict[
|
||||
|
||||
if __name__ == "__main__":
|
||||
contract = sys.argv[1] if len(sys.argv) > 1 else "contracts/microservice.yml"
|
||||
os.environ["ACDL_LOCAL_TIER"] = "1"
|
||||
# Set both so the dual-read in is_local_tier() finds NOVA_* (preferred);
|
||||
# the ACDL_* alias stays for any unmigrated reader until P5.
|
||||
os.environ["NOVA_LOCAL_TIER"] = "1"
|
||||
os.environ["ACDL_LOCAL_TIER"] = "1" # legacy alias (dual-read fallback), removed in P5
|
||||
result = run_local_e2e(contract)
|
||||
print(json.dumps(result, indent=2))
|
||||
@@ -21,8 +21,16 @@ try:
|
||||
except ImportError:
|
||||
boto3 = None
|
||||
|
||||
# Repo root on sys.path so `from core import env` resolves to THIS package
|
||||
# when run as a script (avoids editable-installed third-party `core` shadow).
|
||||
_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if _REPO_ROOT not in sys.path:
|
||||
sys.path.insert(0, _REPO_ROOT)
|
||||
|
||||
from core import env as _envhelper
|
||||
|
||||
SSM_PREFIX = "/acdl"
|
||||
KMS_KEY_ID_ENV = "ACDL_KMS_KEY_ID"
|
||||
KMS_KEY_ID_ENV = "NOVA_KMS_KEY_ID"
|
||||
|
||||
# Outputs that are safe to display in a PR comment (no secrets).
|
||||
SAFE_OUTPUT_NAMES = {
|
||||
@@ -54,20 +62,22 @@ def _ssm_client():
|
||||
def _kms_key_id():
|
||||
"""Return the KMS key ID for SSM SecureString encryption.
|
||||
|
||||
P1-3: Fail loud when ACDL_KMS_KEY_ID is not set — silently falling back
|
||||
P1-3: Fail loud when NOVA_KMS_KEY_ID is not set — silently falling back
|
||||
to the AWS-managed key (`alias/aws/ssm`) was a security gap. The platform
|
||||
CMK must be explicitly configured. Set ACDL_ALLOW_DEFAULT_KMS=1 to use
|
||||
the AWS-managed key as an escape hatch for local testing.
|
||||
CMK must be explicitly configured. Set NOVA_ALLOW_DEFAULT_KMS=1 to use
|
||||
the AWS-managed key as an escape hatch for local testing. (Dual-read
|
||||
via core/env.py: NOVA_* preferred, ACDL_* fallback until P5.)
|
||||
"""
|
||||
key_id = os.environ.get(KMS_KEY_ID_ENV)
|
||||
key_id = _envhelper.get_env("KMS_KEY_ID")
|
||||
if key_id:
|
||||
return key_id
|
||||
if os.environ.get("ACDL_ALLOW_DEFAULT_KMS") == "1":
|
||||
if _envhelper.get_env("ALLOW_DEFAULT_KMS") == "1":
|
||||
return "alias/aws/ssm"
|
||||
raise RuntimeError(
|
||||
f"{KMS_KEY_ID_ENV} is not set — refusing to use the AWS-managed SSM key "
|
||||
f"silently. Set {KMS_KEY_ID_ENV} to your platform CMK ARN, or set "
|
||||
f"ACDL_ALLOW_DEFAULT_KMS=1 to use alias/aws/ssm (escape hatch for local testing)."
|
||||
f"NOVA_ALLOW_DEFAULT_KMS=1 (ACDL_ALLOW_DEFAULT_KMS=1 fallback) to use "
|
||||
f"alias/aws/ssm (escape hatch for local testing)."
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -32,6 +32,15 @@ from dataclasses import dataclass, field, asdict
|
||||
from pathlib import Path
|
||||
from typing import Callable, Dict, List, Optional, Tuple
|
||||
|
||||
# Repo root on sys.path so `from core import env` resolves to THIS package
|
||||
# when regression_verify.py is run as a script (avoids editable-installed
|
||||
# third-party `core` shadow).
|
||||
_REPO_ROOT = str(Path(__file__).resolve().parent.parent)
|
||||
if _REPO_ROOT not in sys.path:
|
||||
sys.path.insert(0, _REPO_ROOT)
|
||||
|
||||
from core import env as _envhelper
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
CIAgent = ROOT / ".ciagent"
|
||||
|
||||
@@ -306,9 +315,13 @@ def _load_aws_env() -> Dict[str, str]:
|
||||
continue
|
||||
if "=" in line:
|
||||
k, v = line.split("=", 1)
|
||||
if k == "ACDL_AWS_ACCESS_KEY_ID":
|
||||
# G-106 binding: dual-read NOVA_* first, ACDL_* fallback.
|
||||
# The .env.secrets keys are renamed to NOVA_* in P2; the
|
||||
# ACDL_* fallback covers operators who haven't rotated
|
||||
# their local .env.secrets yet. Removed in P5.
|
||||
if k == "NOVA_AWS_ACCESS_KEY_ID" or k == "ACDL_AWS_ACCESS_KEY_ID":
|
||||
env["AWS_ACCESS_KEY_ID"] = v
|
||||
elif k == "ACDL_AWS_SECRET_ACCESS_KEY":
|
||||
elif k == "NOVA_AWS_SECRET_ACCESS_KEY" or k == "ACDL_AWS_SECRET_ACCESS_KEY":
|
||||
env["AWS_SECRET_ACCESS_KEY"] = v
|
||||
elif k == "AWS_DEFAULT_REGION":
|
||||
env["AWS_DEFAULT_REGION"] = v
|
||||
@@ -319,7 +332,8 @@ def _check_live_terraform_plan_microservice() -> Tuple[Status, str]:
|
||||
"""CAP-013: terraform init+validate+plan against live AWS for the
|
||||
microservice stack (D-093 live-AWS tier of the headline E2E).
|
||||
|
||||
Requires AWS credentials (ACDL_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).
|
||||
Runs in a temp dir; does NOT apply (plan only)."""
|
||||
import tempfile, os
|
||||
work = tempfile.mkdtemp(prefix="acdl_regr_live_")
|
||||
@@ -421,7 +435,7 @@ def _check_s3_state_bucket() -> Tuple[Status, str]:
|
||||
s3 = boto3.client("s3", region_name=env.get("AWS_DEFAULT_REGION", "us-east-1"),
|
||||
aws_access_key_id=env.get("AWS_ACCESS_KEY_ID"),
|
||||
aws_secret_access_key=env.get("AWS_SECRET_ACCESS_KEY"))
|
||||
account_id = os.environ.get("ACDL_AWS_ACCOUNT_ID", "581513795199")
|
||||
account_id = _envhelper.get_env("AWS_ACCOUNT_ID", "581513795199")
|
||||
state_bucket = f"acdl-tfstate-{account_id}-us-east-1"
|
||||
s3.head_bucket(Bucket=state_bucket)
|
||||
r = s3.list_objects_v2(Bucket=state_bucket, MaxKeys=5)
|
||||
@@ -643,8 +657,8 @@ def write_report(report: RegressionReport,
|
||||
|
||||
|
||||
def main() -> int:
|
||||
milestone = os.environ.get("ACDL_REGRESSION_MILESTONE", "v1.10")
|
||||
phase = int(os.environ.get("ACDL_REGRESSION_PHASE", "52"))
|
||||
milestone = _envhelper.get_env("REGRESSION_MILESTONE", "v1.10") or "v1.10"
|
||||
phase = int(_envhelper.get_env("REGRESSION_PHASE", "52") or "52")
|
||||
report = run_regression(milestone=milestone, phase=phase)
|
||||
md, js = write_report(report)
|
||||
print(f"regression: {report.summary} -> {md}")
|
||||
|
||||
@@ -5,16 +5,27 @@ Blocks on equality, emits `SEPARATION_OF_DUTIES_VIOLATION`, routes a halt
|
||||
artifact to SRE on-call.
|
||||
|
||||
v1.9 (REQ-107, D-085): route_halt_artifact is a real implementation —
|
||||
publishes to SNS topic `acdl-sod-halt` (ARN from ACDL_SOD_HALT_TOPIC_ARN)
|
||||
publishes to SNS topic `acdl-sod-halt` (ARN from NOVA_SOD_HALT_TOPIC_ARN)
|
||||
when set; falls back to a structured stderr emission + a
|
||||
SEPARATION_OF_DUTIES_VIOLATION event write to the DynamoDB outbox when
|
||||
unset. No silent print-only stub.
|
||||
unset. No silent print-only stub. (Dual-read via core/env.py: NOVA_*
|
||||
preferred, ACDL_* fallback until P5; the SNS topic ARN is the AWS
|
||||
resource `acdl-sod-halt` → renamed `nova-sod-halt` in P4.)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import Optional, Tuple
|
||||
|
||||
# Repo root on sys.path so `from core import env` resolves to THIS package
|
||||
# when imported/run in a context where an editable-installed third-party
|
||||
# `core` package would otherwise shadow it.
|
||||
_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if _REPO_ROOT not in sys.path:
|
||||
sys.path.insert(0, _REPO_ROOT)
|
||||
|
||||
from core import env
|
||||
|
||||
|
||||
def check(outbox_client, contract_id: str,
|
||||
current_prod_approver: Optional[str]) -> Tuple[bool, str]:
|
||||
@@ -40,13 +51,13 @@ def route_halt_artifact(contract_id: str, violation_reason: str,
|
||||
oncall_client=None) -> None:
|
||||
"""Route a halt artifact to SRE on-call (REQ-107, D-085).
|
||||
|
||||
When ACDL_SOD_HALT_TOPIC_ARN is set, publish to the SNS topic via
|
||||
When NOVA_SOD_HALT_TOPIC_ARN is set, publish to the SNS topic via
|
||||
boto3. When unset (dev/CI), fall back to a structured stderr emission
|
||||
+ a SEPARATION_OF_DUTIES_VIOLATION event write to the DynamoDB outbox
|
||||
via outbox_writer.write_event (so the halt is in the audit chain).
|
||||
The oncall_client, when provided, is the SNS client (test injection).
|
||||
"""
|
||||
topic_arn = os.environ.get("ACDL_SOD_HALT_TOPIC_ARN", "")
|
||||
topic_arn = env.get_env("SOD_HALT_TOPIC_ARN", "") or ""
|
||||
halt_payload = {
|
||||
"contractId": contract_id,
|
||||
"reason": violation_reason,
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
# Nova Migration Guide — What Consumers Must Know
|
||||
|
||||
> **Nova** is the new product brand for the platform formerly known as
|
||||
> **ACDL** (Agentic Cloud Delivery Platform). This guide announces the
|
||||
> scheduled breaking changes coming in the rebrand rollout (Phases P2–P4)
|
||||
> and tells you exactly what to do, when, and how long you have.
|
||||
|
||||
The product is being rebranded **A C D L → Nova**. The rebrand is staged
|
||||
across phases so that **no consumer deployment breaks during the
|
||||
transition**. Phases P2–P4 ship the breaking changes behind a **dual-read
|
||||
/ parallel-write grace period**; Phase P5 removes the fallback and the old
|
||||
names stop working. This document is the consumer-facing contract for that
|
||||
rollout.
|
||||
|
||||
## What is NOT changing
|
||||
|
||||
- **The Gitea repository name** (`continuous-intelligence/acdl`) is **not**
|
||||
changing. Only the product brand is changing. The `uses:` reference
|
||||
(`acdl/.github/workflows/deploy.yml@vX.Y`) and the GitHub `acdl/acdl` repo
|
||||
path are unchanged for the duration of the rebrand; the workflow
|
||||
`uses:` reference will be migrated in a later, separately-announced step.
|
||||
- **The platform behavior** is unchanged. Same pipeline stages, same
|
||||
contract schema, same confidence model, same evidence stream, same
|
||||
modules. Only the brand, the on-disk path, the env var names, the SSM
|
||||
path, the AWS tag keys, and the AWS resource names are changing.
|
||||
|
||||
## The 5 breaking changes
|
||||
|
||||
Five things that consumers may reference are being renamed. Each is
|
||||
scheduled into a phase, ships with a grace period, and has a cutoff.
|
||||
|
||||
### 1. Consumer contract path — Phase P2
|
||||
|
||||
- **Old:** `.acdl/contract.yml`
|
||||
- **New:** `.nova/contract.yml`
|
||||
- **Phase:** P2 (env vars + consumer path)
|
||||
- **Grace period:** during P2–P4 the deploy workflow reads **both** paths
|
||||
(`.nova/contract.yml` first, falling back to `.acdl/contract.yml` if the
|
||||
new path is absent). Your existing contracts keep working until P5.
|
||||
- **Cutoff:** P5 removes the `.acdl/` fallback. Move your contract file
|
||||
before P5.
|
||||
- **What you must do:** rename the directory in your consumer repo from
|
||||
`.acdl/` to `.nova/` and update any `contract:` workflow input that
|
||||
points at the old path. Nothing else changes in the contract content.
|
||||
|
||||
### 2. Environment variables — Phase P2
|
||||
|
||||
- **Old:** `ACDL_*` (e.g. `ACDL_LIFECYCLE_MODE`, `ACDL_AWS_ACCOUNT_ID`,
|
||||
`ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID`, …)
|
||||
- **New:** `NOVA_*` (e.g. `NOVA_LIFECYCLE_MODE`, `NOVA_AWS_ACCOUNT_ID`,
|
||||
`NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID`, …)
|
||||
- **Phase:** P2 (env vars + consumer path)
|
||||
- **Grace period — dual-read fallback:** during P2–P4 the platform reads
|
||||
**`NOVA_*` first, then falls back to `ACDL_*`** if the Nova variable is
|
||||
unset. This means your CI secrets, workflow env blocks, and local
|
||||
`.env.secrets` keep working unchanged through P4. You do not need to
|
||||
rename everything in one shot — rename a variable and the dual-read picks
|
||||
it up; leave one old and it still resolves.
|
||||
- **Cutoff:** P5 removes the `ACDL_*` fallback. After P5, only `NOVA_*`
|
||||
is read.
|
||||
- **What you must do:** rename your `ACDL_*` CI secrets, workflow `env:`
|
||||
blocks, and any local `.env.secrets` entries to `NOVA_*`. Because of the
|
||||
dual-read, you can do this incrementally across P2–P4 — but it must be
|
||||
complete before P5.
|
||||
|
||||
### 3. SSM parameter path — Phase P3
|
||||
|
||||
- **Old:** `/acdl/{env}/{contractId}/{output}`
|
||||
- **New:** `/nova/{env}/{contractId}/{output}`
|
||||
- **Phase:** P3 (SSM paths + tag keys)
|
||||
- **Grace period — parallel-write:** during P3–P4 the platform **writes
|
||||
every output to both** the `/acdl/…` and `/nova/…` SSM paths, and reads
|
||||
from `/nova/…` first (falling back to `/acdl/…`). Any hardcoded SSM path
|
||||
reads in your application code keep resolving through P4.
|
||||
- **Cutoff:** P5 stops writing to `/acdl/…` and removes the read fallback.
|
||||
After P5 only `/nova/…` exists.
|
||||
- **What you must do:** if your application code or runbooks read deploy
|
||||
outputs from SSM by hardcoded path, update the path prefix from `/acdl/`
|
||||
to `/nova/`. If you consume outputs only via the PR-comment / GitHub
|
||||
issue surface, you do nothing — the platform republishes under the new
|
||||
path automatically.
|
||||
|
||||
### 4. AWS tag keys — Phase P3
|
||||
|
||||
- **Old:** `acdl:owner`, `acdl:environment`, `acdl:contract`,
|
||||
`acdl:cost-center`, `acdl:ref`
|
||||
- **New:** `nova:owner`, `nova:environment`, `nova:contract`,
|
||||
`nova:cost-center`, `nova:ref`
|
||||
- **Phase:** P3 (SSM paths + tag keys)
|
||||
- **Grace period — parallel-tag period:** during P3–P4 the platform
|
||||
**tags every resource with both** the `acdl:*` and `nova:*` keys (same
|
||||
values). The ABAC session policy matches on **either** key set, so your
|
||||
existing scoped permissions keep working. The default cost-center value
|
||||
moves from `acdl-default` to `nova-default` (both written during the
|
||||
parallel-tag period).
|
||||
- **Cutoff:** P5 stops writing the `acdl:*` keys and the ABAC policy matches
|
||||
only on `nova:*`. After P5, resources created before P5 still carry the
|
||||
old `acdl:*` tags (tags are not retroactively rewritten) but **new**
|
||||
resources are tagged `nova:*` only, and the policy no longer grants
|
||||
access via `acdl:*`.
|
||||
- **What you must do:** if you have IAM policies, Cost Explorer filters,
|
||||
or billing groupings that key off `acdl:*` tag keys, add a parallel
|
||||
`nova:*` condition (or migrate to `nova:*`) before P5. The platform
|
||||
handles the dual-tagging; you only need to update your own tag-key
|
||||
references.
|
||||
|
||||
### 5. AWS resource names — Phase P4
|
||||
|
||||
- **Old:** `acdl-*` (DynamoDB tables `acdl-contracts`,
|
||||
`acdl-change-requests`; Lambda `acdl-contract-ingestor`; SNS
|
||||
`acdl-sod-halt`; security group `acdl-ecs-sg`; KMS alias
|
||||
`alias/acdl-platform`; ECS services, ECR repos, IAM user
|
||||
`acdl-spike-runner`, state bucket `acdl-tfstate-*`, ALB `acdl-alb`,
|
||||
`acdl-deploy-*`)
|
||||
- **New:** `nova-*` (the same resources, prefixed `nova-`)
|
||||
- **Phase:** P4 (resource names) — **maintenance window**
|
||||
- **Grace period:** P4 is a **planned maintenance window**. AWS resources
|
||||
cannot be renamed in place, so P4 provisions the `nova-*` resources,
|
||||
migrates data (DynamoDB tables, S3 state), repoints the platform, and
|
||||
tears down the `acdl-*` resources. The platform team schedules and
|
||||
announces the window; consumers do not provision or rename anything
|
||||
themselves.
|
||||
- **Cutoff:** the `acdl-*` resources are decommissioned at the end of the
|
||||
P4 maintenance window. After P4, only `nova-*` resources exist.
|
||||
- **What you must do:** nothing for the resource names themselves — the
|
||||
platform owns the rename. If your application code or runbooks reference
|
||||
a specific `acdl-*` resource by name (e.g. a hardcoded DynamoDB table
|
||||
name or ECR URI), update it to the `nova-*` name during P4. The platform
|
||||
publishes the exact old → new name mapping with the P4 announcement.
|
||||
|
||||
## Timeline at a glance
|
||||
|
||||
| Phase | What ships | Grace period | Cutoff |
|
||||
|-------|------------|--------------|--------|
|
||||
| **P1** (this phase) | Brand prose, docs, decks, schema `$id`, release titles | n/a (prose only) | n/a |
|
||||
| **P2** | `.nova/` contract path + `NOVA_*` env vars | dual-read: `.nova/`→`.acdl/`, `NOVA_*`→`ACDL_*` | **P5** removes fallback |
|
||||
| **P3** | `/nova/` SSM path + `nova:*` tag keys | parallel-write (SSM) + parallel-tag (ABAC matches either) | **P5** removes old path/tags |
|
||||
| **P4** | `nova-*` AWS resource names | maintenance window (platform-owned migration) | end of P4 window |
|
||||
| **P5** | Fallback removal | — | `ACDL_*` env vars, `.acdl/` path, `/acdl/` SSM, `acdl:*` tags stop working |
|
||||
|
||||
## What consumers must do (checklist)
|
||||
|
||||
1. **Before P5 — contract path:** move `.acdl/contract.yml` →
|
||||
`.nova/contract.yml` in your consumer repo; update the `contract:`
|
||||
workflow input. *(Can be done any time in P2–P4.)*
|
||||
2. **Before P5 — env vars:** rename `ACDL_*` CI secrets / workflow `env:`
|
||||
blocks / local `.env.secrets` to `NOVA_*`. *(Incremental during P2–P4;
|
||||
dual-read keeps you green.)*
|
||||
3. **Before P5 — SSM reads:** if you read deploy outputs from SSM by
|
||||
hardcoded `/acdl/…` path, update to `/nova/…`. *(Skip if you consume
|
||||
outputs via PR comments only.)*
|
||||
4. **Before P5 — tag-key references:** if you have IAM policies, Cost
|
||||
Explorer filters, or billing groupings keyed off `acdl:*`, add or
|
||||
migrate to `nova:*`. *(Platform handles dual-tagging.)*
|
||||
5. **During P4 — resource-name references:** if your code or runbooks
|
||||
reference a specific `acdl-*` AWS resource by name, update to the
|
||||
`nova-*` name per the P4 mapping announcement. *(Platform owns the
|
||||
rename itself.)*
|
||||
|
||||
## Questions
|
||||
|
||||
If anything in this guide is unclear, or you are unsure whether your
|
||||
consumer repo references a renamed value, open an issue on the platform
|
||||
repo. The platform team will confirm what you need to change and when.
|
||||
|
||||
> **Note:** the real Gitea repository name (`continuous-intelligence/acdl`)
|
||||
> is **not** changing — only the product brand. The `uses:` workflow
|
||||
> reference and repo path are migrated in a separately-announced later step;
|
||||
> until then, keep your `uses: acdl/.github/workflows/deploy.yml@vX.Y`
|
||||
> reference as-is.
|
||||
@@ -1,5 +1,5 @@
|
||||
title: ACDL — Agentic Cloud Delivery Platform
|
||||
description: Consumer + platform-engineer documentation for the ACDL platform.
|
||||
title: Nova
|
||||
description: Consumer + platform-engineer documentation for the Nova platform (formerly ACDL — Agentic Cloud Delivery Platform).
|
||||
remote_theme: mmistakes/minimal-mistakes@9.0.4
|
||||
|
||||
exclude:
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
# Consumer Guide — Declare intent, deploy to AWS
|
||||
|
||||
This guide walks a consumer through creating their pipeline and defining a
|
||||
contract that deploys any ACDL module to AWS. It is **generic** across all
|
||||
contract that deploys any Nova module to AWS. It is **generic** across all
|
||||
modules in the registry; `static-assets` is the worked example, but every
|
||||
step applies to `microservice` and any future module.
|
||||
|
||||
## The model
|
||||
|
||||
Consumers have their own repos and consume ACDL by writing a contract
|
||||
Consumers have their own repos and consume Nova by writing a contract
|
||||
that declares infrastructure (one or more modules), an environment, and inputs. The consumer declares a **contract** (which infrastructure, which
|
||||
environment, which inputs); the ACDL platform owns the pipelines, modules,
|
||||
environment, which inputs); the Nova platform owns the pipelines, modules,
|
||||
engine adapter, and evidence stream.
|
||||
|
||||
You do not write infrastructure modules, workflow YAML, or adapter code.
|
||||
@@ -53,7 +53,7 @@ platform-managed. See [Environments](environments/).
|
||||
## Step 1 — Create a consumer repo
|
||||
|
||||
Create a repository for your application. The top level holds your app
|
||||
code; your contract lives at `.acdl/contract.yml`. Example for a static
|
||||
code; your contract lives at `.nova/contract.yml`. Example for a static
|
||||
site:
|
||||
|
||||
```
|
||||
@@ -62,7 +62,7 @@ my-static-site/
|
||||
assets/
|
||||
style.css
|
||||
logo.png
|
||||
.acdl/
|
||||
.nova/
|
||||
contract.yaml
|
||||
.github/
|
||||
workflows/
|
||||
@@ -75,7 +75,7 @@ Example for a microservice:
|
||||
my-microservice/
|
||||
app.py
|
||||
Dockerfile
|
||||
.acdl/
|
||||
.nova/
|
||||
contract.yaml
|
||||
.github/
|
||||
workflows/
|
||||
@@ -83,20 +83,20 @@ my-microservice/
|
||||
```
|
||||
|
||||
Your app code lives at the top level. Your contract lives at
|
||||
`.acdl/contract.yml` regardless of the module you deploy. Your CI
|
||||
`.nova/contract.yml` regardless of the module you deploy. Your CI
|
||||
definition lives at `.github/workflows/deploy.yml`.
|
||||
|
||||
## Step 2 — Reference the central pipeline
|
||||
|
||||
In your CI workflow (`.github/workflows/deploy.yml`), reference the central
|
||||
ACDL deployment workflow with a **versioned tag** (floating MAJOR + MINOR):
|
||||
Nova deployment workflow with a **versioned tag** (floating MAJOR + MINOR):
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
deploy:
|
||||
uses: acdl/.github/workflows/deploy.yml@v1.13
|
||||
with:
|
||||
contract: .acdl/contract.yml
|
||||
contract: .nova/contract.yml
|
||||
environment: dev
|
||||
```
|
||||
|
||||
@@ -106,7 +106,7 @@ field; the version pin lives in the CI workflow reference.
|
||||
|
||||
## Step 3 — Define the contract
|
||||
|
||||
Write `.acdl/contract.yml`. The `static-assets` example:
|
||||
Write `.nova/contract.yml`. The `static-assets` example:
|
||||
|
||||
```yaml
|
||||
environment: dev
|
||||
@@ -167,7 +167,7 @@ and execute for you.
|
||||
|
||||
### The consumer CI definition
|
||||
|
||||
Add a thin workflow file to **your** repo that invokes the reusable ACDL
|
||||
Add a thin workflow file to **your** repo that invokes the reusable Nova
|
||||
deploy workflow with a **versioned tag** (`.github/workflows/deploy.yml`):
|
||||
|
||||
```yaml
|
||||
@@ -179,7 +179,7 @@ jobs:
|
||||
deploy:
|
||||
uses: acdl/.github/workflows/deploy.yml@v1.13
|
||||
with:
|
||||
contract: .acdl/contract.yml
|
||||
contract: .nova/contract.yml
|
||||
```
|
||||
|
||||
That is the entire consumer-side workflow. When you push to `main`:
|
||||
@@ -187,12 +187,12 @@ That is the entire consumer-side workflow. When you push to `main`:
|
||||
1. The platform runner resolves `uses: acdl/.github/workflows/deploy.yml@v1.13`
|
||||
to the reusable workflow **at the pinned tag**.
|
||||
2. A **platform-provided runner** checks out **your** repo.
|
||||
3. The runner checks out the **ACDL platform repo** into the workspace —
|
||||
3. The runner checks out the **Nova platform repo** into the workspace —
|
||||
this is how the pipeline fetches the platform code at run time. You
|
||||
never clone the platform repo yourself.
|
||||
4. The runner installs the runtime dependencies the platform requires.
|
||||
5. The runner invokes `scripts/run_platform.sh` against your
|
||||
`.acdl/contract.yml`.
|
||||
`.nova/contract.yml`.
|
||||
|
||||
You see the streamed output (infrastructure plan, policy-check results,
|
||||
confidence signal) in your run logs. The `--check-only` and `--plan-only`
|
||||
@@ -203,7 +203,7 @@ hold for attestation).
|
||||
|
||||
### Local validation (optional)
|
||||
|
||||
A consumer *may* clone the ACDL platform repo to run `--check-only` against
|
||||
A consumer *may* clone the Nova platform repo to run `--check-only` against
|
||||
their contract before pushing — this is optional and not required for the
|
||||
happy path. If you do this, the runtime dependencies must be installed
|
||||
locally, and any AWS credentials follow the
|
||||
@@ -213,7 +213,7 @@ static key in `.env.secrets` (gitignored) is rotated **out of band by you**
|
||||
locally-held copies.
|
||||
|
||||
```bash
|
||||
bash scripts/run_platform.sh --check-only path/to/your/.acdl/contract.yml
|
||||
bash scripts/run_platform.sh --check-only path/to/your/.nova/contract.yml
|
||||
```
|
||||
|
||||
## Step 5 — What the pipeline does
|
||||
@@ -355,7 +355,7 @@ destruction:
|
||||
```yaml
|
||||
uses: acdl/.github/workflows/deploy.yml@v1.13
|
||||
with:
|
||||
contract: .acdl/contract.yml
|
||||
contract: .nova/contract.yml
|
||||
mode: decommission
|
||||
changeRequestId: "CHG0678912"
|
||||
```
|
||||
@@ -395,7 +395,7 @@ separately (or left running to monitor the decommissioned stack's
|
||||
endpoints going dark).
|
||||
## Per-environment deployment
|
||||
|
||||
ACDL supports a **promotion-without-editing** model: you do not edit the
|
||||
Nova supports a **promotion-without-editing** model: you do not edit the
|
||||
`environment:` field in a contract to promote dev → qa → prod → dr.
|
||||
Instead, there is **one CI job per environment**, each pointing at its
|
||||
respective contract (or the same contract + the `environment` workflow
|
||||
@@ -404,8 +404,8 @@ input). Promotion = running the matching job.
|
||||
### Two shapes (both supported)
|
||||
|
||||
**Shape 1 — per-environment contract files:** a consumer repo has one
|
||||
contract per environment (e.g. `.acdl/static-assets.dev.yml`,
|
||||
`.acdl/static-assets.qa.yml`, …). Each sets `environment:` to its own
|
||||
contract per environment (e.g. `.nova/static-assets.dev.yml`,
|
||||
`.nova/static-assets.qa.yml`, …). Each sets `environment:` to its own
|
||||
name and uses interpolation so env-specific values differ automatically:
|
||||
|
||||
```yaml
|
||||
@@ -439,7 +439,7 @@ jobs:
|
||||
uses: acdl/.github/workflows/deploy.yml@v1.13
|
||||
with:
|
||||
environment: qa
|
||||
contract: .acdl/contract.yml
|
||||
contract: .nova/contract.yml
|
||||
```
|
||||
|
||||
### One job per environment
|
||||
|
||||
@@ -7,7 +7,7 @@ deploys it.
|
||||
|
||||
## The contract file
|
||||
|
||||
A consumer repo keeps its contract at `.acdl/contract.yml`. A minimal
|
||||
A consumer repo keeps its contract at `.nova/contract.yml`. A minimal
|
||||
example (the `static-assets` module):
|
||||
|
||||
```yaml
|
||||
@@ -57,7 +57,7 @@ infrastructure:
|
||||
## Validation
|
||||
|
||||
The contract is validated against
|
||||
[`schemas/contract.schema.json`](https://github.com/acdl/acdl/blob/main/schemas/contract.schema.json).
|
||||
[`schemas/contract.schema.json`](https://github.com/nova/nova/blob/main/schemas/contract.schema.json).
|
||||
An invalid contract (missing field, unknown module, wrong type) fails at the
|
||||
validate-contract stage with a clear error.
|
||||
|
||||
@@ -65,9 +65,9 @@ validate-contract stage with a clear error.
|
||||
|
||||
Two reference examples exist in `contracts/`:
|
||||
|
||||
- [`contracts/static-assets.yml`](https://github.com/acdl/acdl/blob/main/contracts/static-assets.yml)
|
||||
- [`contracts/static-assets.yml`](https://github.com/nova/nova/blob/main/contracts/static-assets.yml)
|
||||
— the `static-assets` module.
|
||||
- [`contracts/microservice.yml`](https://github.com/acdl/acdl/blob/main/contracts/microservice.yml)
|
||||
- [`contracts/microservice.yml`](https://github.com/nova/nova/blob/main/contracts/microservice.yml)
|
||||
— the `microservice` module.
|
||||
|
||||
Additionally, every module has a `modules/<name>/examples/` directory with
|
||||
|
||||
@@ -60,7 +60,7 @@ invoke the **platform Lambda** — `acdl-contract-ingestor` — across
|
||||
accounts. The Lambda is invoked via a Function URL with IAM auth, so the
|
||||
grant is an inline IAM policy applied to the consumer's deploy role. The
|
||||
policy template lives at
|
||||
[`terraform/platform/consumer_invoke_policy.json`](https://github.com/acdl/acdl/blob/main/terraform/platform/consumer_invoke_policy.json)
|
||||
[`terraform/platform/consumer_invoke_policy.json`](https://github.com/nova/nova/blob/main/terraform/platform/consumer_invoke_policy.json)
|
||||
and is scoped via **ABAC**: the condition
|
||||
`aws:PrincipalTag/acdl:owner == ${consumerRepo}` ensures a repo can only
|
||||
invoke the Lambda when its principal tag matches its claimed identity.
|
||||
@@ -83,16 +83,16 @@ is used for two purposes:
|
||||
prepared-status stub until then).
|
||||
|
||||
The Lambda handler and the Terraform that deploys it live in
|
||||
[`core/lambda/contract_ingestor.py`](https://github.com/acdl/acdl/blob/main/core/lambda/contract_ingestor.py)
|
||||
[`core/lambda/contract_ingestor.py`](https://github.com/nova/nova/blob/main/core/lambda/contract_ingestor.py)
|
||||
and
|
||||
[`terraform/platform/main.tf`](https://github.com/acdl/acdl/blob/main/terraform/platform/main.tf)
|
||||
[`terraform/platform/main.tf`](https://github.com/nova/nova/blob/main/terraform/platform/main.tf)
|
||||
respectively.
|
||||
|
||||
## Onboarding scaffold (current state)
|
||||
|
||||
The platform repo ships a minimal onboarding scaffold:
|
||||
|
||||
- [`core/environments/`](https://github.com/acdl/acdl/blob/main/core/environments/)
|
||||
- [`core/environments/`](https://github.com/nova/nova/blob/main/core/environments/)
|
||||
— environment definitions (a sample `dev.json`).
|
||||
- `core/environment_check.py` — checks whether an environment is defined for
|
||||
a given contract's repo + environment name; prints the friendly onboarding
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# ACDL — Agentic Cloud Delivery Platform
|
||||
# Nova
|
||||
|
||||
> **Nova — The New Dawn of DevSecOps.** Security as a seamless enabler of fast deployments — not a bottleneck, not a "no" department.
|
||||
|
||||
Consumers declare intent; the platform delivers safe production deployment
|
||||
through an agentic stack — automatically, safely, and with a complete audit
|
||||
@@ -9,14 +11,14 @@ a configuration file, or an infrastructure module.
|
||||
|
||||
## Two repositories
|
||||
|
||||
There are two kinds of repository in the ACDL model:
|
||||
There are two kinds of repository in the Nova model:
|
||||
|
||||
- **Platform repo (this one).** The source code of the platform. It owns
|
||||
`modules/`, `adapters/`, `core/`, `schemas/`, `pipelines/`, `scripts/`,
|
||||
and the reusable workflow files. Platform engineers work here. A consumer
|
||||
never clones it.
|
||||
- **Consumer repo (yours).** A consumer repo contains only its application
|
||||
code, one or more contracts (`.acdl/contract.yml`), and one or more CI
|
||||
code, one or more contracts (`.nova/contract.yml`), and one or more CI
|
||||
definitions (a thin `.github/workflows/deploy.yml` that `uses:` the central
|
||||
reusable workflow, pointing at the appropriate environment + contract).
|
||||
The consumer does not write infrastructure modules, workflow YAML, or
|
||||
@@ -75,4 +77,11 @@ Planned future features (no dates; tracked in the internal roadmap):
|
||||
|
||||
- [Consumer Guide](consumer-guide) — start here if you are a consumer.
|
||||
- [Architecture](architecture) — start here if you are a platform engineer.
|
||||
- The [README](https://github.com/acdl/acdl) describes the platform repo.
|
||||
- The [README](https://github.com/nova/nova) describes the platform repo.
|
||||
|
||||
> **Note:** The product brand is **Nova** (formerly ACDL — Agentic Cloud
|
||||
> Delivery Platform). The Gitea repository name (`continuous-intelligence/acdl`)
|
||||
> and the GitHub `uses:` reference (`acdl/.github/workflows/deploy.yml@…`)
|
||||
> are unchanged during the rebrand transition; only the product name is
|
||||
> changing. See the [Nova migration guide](NOVA_MIGRATION) for the
|
||||
> scheduled breaking changes.
|
||||
@@ -16,28 +16,28 @@ module's README documents which resources it creates.
|
||||
|
||||
| Module | What it creates | Source |
|
||||
|--------|----------------|--------|
|
||||
| `s3` | `aws_s3_bucket` — a single S3 bucket | [modules/l1/s3/README.md](https://github.com/acdl/acdl/blob/main/modules/l1/s3/README.md) |
|
||||
| `vpc` | `aws_vpc` + `aws_subnet` + `aws_route_table` + `aws_internet_gateway` — VPC with subnets and routing | [modules/l1/vpc/README.md](https://github.com/acdl/acdl/blob/main/modules/l1/vpc/README.md) |
|
||||
| `ecs-cluster` | `aws_ecs_cluster` — ECS Fargate cluster | [modules/l1/ecs-cluster/README.md](https://github.com/acdl/acdl/blob/main/modules/l1/ecs-cluster/README.md) |
|
||||
| `ecs-service` | `aws_ecs_task_definition` + `aws_ecs_service` — Fargate service with task definition | [modules/l1/ecs-service/README.md](https://github.com/acdl/acdl/blob/main/modules/l1/ecs-service/README.md) |
|
||||
| `iam-role` | `aws_iam_role` — IAM role with assume-role policy | [modules/l1/iam-role/README.md](https://github.com/acdl/acdl/blob/main/modules/l1/iam-role/README.md) |
|
||||
| `alb` | `aws_lb` + `aws_lb_target_group` + `aws_lb_listener` — Application Load Balancer | [modules/l1/alb/README.md](https://github.com/acdl/acdl/blob/main/modules/l1/alb/README.md) |
|
||||
| `ecr` | `aws_ecr_repository` — ECR container image repository | [modules/l1/ecr/README.md](https://github.com/acdl/acdl/blob/main/modules/l1/ecr/README.md) |
|
||||
| `cloudfront` | `aws_cloudfront_distribution` + `aws_cloudfront_origin_access_control` — CloudFront distribution with S3 origin via OAC | [modules/l1/cloudfront/README.md](https://github.com/acdl/acdl/blob/main/modules/l1/cloudfront/README.md) |
|
||||
| `waf` | `aws_wafv2_web_acl` — WAFv2 Web ACL (CloudFront-scoped) | [modules/l1/waf/README.md](https://github.com/acdl/acdl/blob/main/modules/l1/waf/README.md) |
|
||||
| `rds` | `aws_db_instance` — RDS database instance (multi-engine: postgres, mysql, etc.) | [modules/l1/rds/README.md](https://github.com/acdl/acdl/blob/main/modules/l1/rds/README.md) |
|
||||
| `s3` | `aws_s3_bucket` — a single S3 bucket | [modules/l1/s3/README.md](https://github.com/nova/nova/blob/main/modules/l1/s3/README.md) |
|
||||
| `vpc` | `aws_vpc` + `aws_subnet` + `aws_route_table` + `aws_internet_gateway` — VPC with subnets and routing | [modules/l1/vpc/README.md](https://github.com/nova/nova/blob/main/modules/l1/vpc/README.md) |
|
||||
| `ecs-cluster` | `aws_ecs_cluster` — ECS Fargate cluster | [modules/l1/ecs-cluster/README.md](https://github.com/nova/nova/blob/main/modules/l1/ecs-cluster/README.md) |
|
||||
| `ecs-service` | `aws_ecs_task_definition` + `aws_ecs_service` — Fargate service with task definition | [modules/l1/ecs-service/README.md](https://github.com/nova/nova/blob/main/modules/l1/ecs-service/README.md) |
|
||||
| `iam-role` | `aws_iam_role` — IAM role with assume-role policy | [modules/l1/iam-role/README.md](https://github.com/nova/nova/blob/main/modules/l1/iam-role/README.md) |
|
||||
| `alb` | `aws_lb` + `aws_lb_target_group` + `aws_lb_listener` — Application Load Balancer | [modules/l1/alb/README.md](https://github.com/nova/nova/blob/main/modules/l1/alb/README.md) |
|
||||
| `ecr` | `aws_ecr_repository` — ECR container image repository | [modules/l1/ecr/README.md](https://github.com/nova/nova/blob/main/modules/l1/ecr/README.md) |
|
||||
| `cloudfront` | `aws_cloudfront_distribution` + `aws_cloudfront_origin_access_control` — CloudFront distribution with S3 origin via OAC | [modules/l1/cloudfront/README.md](https://github.com/nova/nova/blob/main/modules/l1/cloudfront/README.md) |
|
||||
| `waf` | `aws_wafv2_web_acl` — WAFv2 Web ACL (CloudFront-scoped) | [modules/l1/waf/README.md](https://github.com/nova/nova/blob/main/modules/l1/waf/README.md) |
|
||||
| `rds` | `aws_db_instance` — RDS database instance (multi-engine: postgres, mysql, etc.) | [modules/l1/rds/README.md](https://github.com/nova/nova/blob/main/modules/l1/rds/README.md) |
|
||||
|
||||
## Modules
|
||||
|
||||
| Module | What it references | Source |
|
||||
|--------|--------------------|--------|
|
||||
| `static-assets` | 3 primitives (s3, cloudfront, waf) — a production static asset stack | [modules/l2/static-assets/README.md](https://github.com/acdl/acdl/blob/main/modules/l2/static-assets/README.md) |
|
||||
| `microservice` | 6 primitives (vpc, cluster, ecr, iam-role, alb, ecs-service) — an ECS Fargate microservice | [modules/l2/microservice/README.md](https://github.com/acdl/acdl/blob/main/modules/l2/microservice/README.md) |
|
||||
| `static-assets` | 3 primitives (s3, cloudfront, waf) — a production static asset stack | [modules/l2/static-assets/README.md](https://github.com/nova/nova/blob/main/modules/l2/static-assets/README.md) |
|
||||
| `microservice` | 6 primitives (vpc, cluster, ecr, iam-role, alb, ecs-service) — an ECS Fargate microservice | [modules/l2/microservice/README.md](https://github.com/nova/nova/blob/main/modules/l2/microservice/README.md) |
|
||||
|
||||
## Registry
|
||||
|
||||
Module versions are tracked in
|
||||
[`registry.json`](https://github.com/acdl/acdl/blob/main/modules/registry.json).
|
||||
[`registry.json`](https://github.com/nova/nova/blob/main/modules/registry.json).
|
||||
Both primitives and modules are registered.
|
||||
|
||||
## Examples
|
||||
@@ -45,7 +45,7 @@ Both primitives and modules are registered.
|
||||
Each module has a `examples/` directory containing validated consumer
|
||||
contract examples (`simple.yaml` + `complex.yaml` + variation files). The
|
||||
platform-test pipeline validates them against
|
||||
[`schemas/contract.schema.json`](https://github.com/acdl/acdl/blob/main/schemas/contract.schema.json).
|
||||
[`schemas/contract.schema.json`](https://github.com/nova/nova/blob/main/schemas/contract.schema.json).
|
||||
See each module's `## Examples` section for the excerpts.
|
||||
|
||||
## Versioning
|
||||
|
||||
@@ -6,9 +6,9 @@ are the single source of truth for the workflow files.
|
||||
## CI pipeline
|
||||
|
||||
The CI pipeline runs on every push and pull request to `main`. It is defined
|
||||
by [`pipelines/ci.yml`](https://github.com/acdl/acdl/blob/main/pipelines/ci.yml),
|
||||
by [`pipelines/ci.yml`](https://github.com/nova/nova/blob/main/pipelines/ci.yml),
|
||||
validated against
|
||||
[`schemas/pipeline.schema.json`](https://github.com/acdl/acdl/blob/main/schemas/pipeline.schema.json).
|
||||
[`schemas/pipeline.schema.json`](https://github.com/nova/nova/blob/main/schemas/pipeline.schema.json).
|
||||
Both platform-runner workflow files implement the same contract and are
|
||||
byte-identical:
|
||||
|
||||
@@ -31,16 +31,16 @@ bash scripts/run_ci.sh --quiet # suppress per-stage banners
|
||||
## Deployment pipeline
|
||||
|
||||
The deployment pipeline runs when a consumer submits a contract. It is
|
||||
defined by [`pipelines/contract.yml`](https://github.com/acdl/acdl/blob/main/pipelines/contract.yml),
|
||||
defined by [`pipelines/contract.yml`](https://github.com/nova/nova/blob/main/pipelines/contract.yml),
|
||||
validated against
|
||||
[`schemas/deploy-pipeline.schema.json`](https://github.com/acdl/acdl/blob/main/schemas/deploy-pipeline.schema.json).
|
||||
[`schemas/deploy-pipeline.schema.json`](https://github.com/nova/nova/blob/main/schemas/deploy-pipeline.schema.json).
|
||||
It is exposed to consumer repos as a **reusable workflow**:
|
||||
|
||||
- `.github/workflows/deploy.yml` — GitHub Actions (production)
|
||||
|
||||
A consumer repo invokes the reusable workflow via a **versioned tag**
|
||||
(floating MAJOR + MINOR, e.g. `acdl/.github/workflows/deploy.yml@v1.13`).
|
||||
The workflow checks out the consumer repo, then checks out the ACDL platform
|
||||
The workflow checks out the consumer repo, then checks out the Nova platform
|
||||
repo into the runner workspace, and runs `scripts/run_platform.sh` against
|
||||
the consumer's contract. The consumer never clones the platform repo or
|
||||
invokes its scripts locally. See the [Consumer Guide](../consumer-guide/)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Versioning
|
||||
|
||||
ACDL uses two versioning schemes: one for modules, one for the deploy
|
||||
Nova uses two versioning schemes: one for modules, one for the deploy
|
||||
pipeline. Both matter to a consumer.
|
||||
|
||||
## Module versioning
|
||||
@@ -16,7 +16,7 @@ old entry enters a **12-month deprecation window**. A module pins its
|
||||
primitives by `name@semver`; the resolver picks the highest compatible.
|
||||
|
||||
Module versions are tracked in
|
||||
[`registry.json`](https://github.com/acdl/acdl/blob/main/modules/registry.json).
|
||||
[`registry.json`](https://github.com/nova/nova/blob/main/modules/registry.json).
|
||||
|
||||
## Deploy-pipeline versioning (the CI workflow `uses:` tag)
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
deploy:
|
||||
uses: acdl/.github/workflows/deploy.yml@v1.13
|
||||
with:
|
||||
contract: .acdl/contract.yml
|
||||
contract: .nova/contract.yml
|
||||
```
|
||||
|
||||
The version pin lives in the CI workflow reference (not in the contract
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Presentations
|
||||
|
||||
Leadership-facing presentation decks for the ACDL platform.
|
||||
Leadership-facing presentation decks for the Nova platform.
|
||||
|
||||
## The 4-step slide creation process
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ flowchart LR
|
||||
A["Technical dev\n(app code + contract)"]
|
||||
B["Citizen dev\n(intent → AI agent\n→ contract)"]
|
||||
end
|
||||
subgraph ACDL ["ACDL — infrastructure only"]
|
||||
subgraph ACDL ["Nova — infrastructure only"]
|
||||
C["Same contract\nSame pipeline\nSame safety"]
|
||||
D["Provision\nAWS resources"]
|
||||
E["Evidence\nhash-chained"]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
flowchart LR
|
||||
A["1. App code<br/>(top level of the repo)"] --> D["Push to main"]
|
||||
B["2. Contract<br/>(.acdl/contract.yml)"] --> D
|
||||
B["2. Contract<br/>(.nova/contract.yml)"] --> D
|
||||
C["3. CI definition<br/>(.github/workflows/deploy.yml<br/>— one 'uses:' line)"] --> D
|
||||
D --> E["Platform does the rest"]
|
||||
classDef accent fill:#1B1B1B,color:#fff,stroke:#D6002A,stroke-width:2px
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
flowchart LR
|
||||
A["Consumer repo<br/>app + contract + 'uses:'"] -->|triggers on push to main| B["Platform runner"]
|
||||
B -->|checks out the consumer repo| A
|
||||
B -->|checks out the ACDL platform repo<br/>into the workspace| C["Platform code<br/>(modules, adapters, schemas)"]
|
||||
B -->|checks out the Nova platform repo<br/>into the workspace| C["Platform code<br/>(modules, adapters, schemas)"]
|
||||
C --> B
|
||||
B -->|runs the pipeline against<br/>the consumer's contract| D["Consumer's resources in AWS"]
|
||||
classDef accent fill:#1B1B1B,color:#fff,stroke:#D6002A,stroke-width:2px
|
||||
|
||||
@@ -7,7 +7,7 @@ flowchart TD
|
||||
U2["Citizen dev\nintent → AI agent → contract"]
|
||||
end
|
||||
|
||||
subgraph ACDL ["ACDL — infrastructure only"]
|
||||
subgraph ACDL ["Nova — infrastructure only"]
|
||||
direction TB
|
||||
CS["Contract schema\n(validate + fail-fast)"]
|
||||
subgraph PIPE ["Central pipeline — fixed stages, every deployment"]
|
||||
|
||||
@@ -9,7 +9,7 @@ flowchart LR
|
||||
D["Manual promotion"]
|
||||
A --> B --> C --> D
|
||||
end
|
||||
subgraph ACDL ["With ACDL"]
|
||||
subgraph ACDL ["With Nova"]
|
||||
direction TB
|
||||
E["Declare intent\n(one YAML contract)"]
|
||||
F["Platform delivers\nsafely, autonomously"]
|
||||
|
||||
@@ -7,7 +7,7 @@ flowchart LR
|
||||
B["Agentic SDLC\n(agent writes contract)"]
|
||||
C["Citizen dev\n(vibe codes → AI agent\n→ contract)"]
|
||||
end
|
||||
subgraph ACDL ["ACDL — infrastructure only"]
|
||||
subgraph ACDL ["Nova — infrastructure only"]
|
||||
D["Contract\nvalidated"]
|
||||
E["Resolve → Plan\nSecurity + Policy checks\nConfidence signal"]
|
||||
F["Provision\nAWS resources"]
|
||||
|
||||
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
@@ -31,7 +31,7 @@ style: |
|
||||
|
||||
# How The Platform Works
|
||||
|
||||
### Agentic Cloud Delivery Platform
|
||||
### Nova — The New Dawn of DevSecOps
|
||||
|
||||
<style>
|
||||
section.title h1 { font-size: 44px; margin-bottom: 0.1em; }
|
||||
@@ -72,12 +72,12 @@ section.title h3 { color: #F0F0F0; font-weight: 400; font-size: 22px; margin-top
|
||||
|
||||
---
|
||||
|
||||
# ACDL owns infrastructure, not your app
|
||||
# Nova owns infrastructure, not your app
|
||||
|
||||

|
||||
|
||||
- **Upstream is anything** — IDE, agentic SDLC, or vibe coding
|
||||
- **ACDL is infrastructure only** — provisions and governs AWS resources
|
||||
- **Nova is infrastructure only** — provisions and governs AWS resources
|
||||
- **Not a general-purpose AI** — autonomy is narrow, policy-bounded
|
||||
- **Not a permissive highway** — no escape hatches
|
||||
|
||||
@@ -299,7 +299,7 @@ section { font-size: 20px; }
|
||||
table { font-size: 18px; }
|
||||
</style>
|
||||
|
||||
ACDL runs at **zero cloud cost** for day-to-day development. AWS spend was measured via Cost Explorer (`COST.md`, 2026-07-28):
|
||||
Nova runs at **zero cloud cost** for day-to-day development. AWS spend was measured via Cost Explorer (`COST.md`, 2026-07-28):
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
- One-slide map of the whole platform — use it to orient the audience before diving into any single component
|
||||
- The leadership-relevant beats: (1) two surfaces, one pipeline, one evidence stream — the convergence is the design; (2) the pipeline stages are fixed and identical for every consumer; (3) the engine adapter is the only engine-specific code, which makes the catalog and confidence model portable
|
||||
- Don't walk every node — point to the boundaries and say "the rest of this deck zooms into each of these"
|
||||
- The contract schema is the boundary between upstream and ACDL; everything left of it is the consumer's, everything right of it is the platform's
|
||||
- The contract schema is the boundary between upstream and Nova; everything left of it is the consumer's, everything right of it is the platform's
|
||||
|
||||
**Key takeaway:** Two surfaces, one pipeline, one evidence stream. The rest of the deck zooms in.
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
## Slide 4 — Declare intent; the platform delivers safe production
|
||||
|
||||
**Talking points:**
|
||||
- Land the before/after contrast: today's queue vs. ACDL's autonomous flow
|
||||
- Land the before/after contrast: today's queue vs. Nova's autonomous flow
|
||||
- The litmus test: if a platform engineer still has to touch a ticket for a dev→qa promotion, we haven't delivered the vision
|
||||
- The North Star is one sentence: "declare intent → safe production deployment"
|
||||
- A non-technical consumer ships by declaring intent — no workflow, no config file, no module
|
||||
@@ -54,15 +54,15 @@
|
||||
|
||||
---
|
||||
|
||||
## Slide 5 — ACDL owns infrastructure, not your app
|
||||
## Slide 5 — Nova owns infrastructure, not your app
|
||||
|
||||
**Talking points:**
|
||||
- The platform is deliberately scoped — it is not trying to be everything
|
||||
- The sovereign boundary: the platform team owns delivery and infrastructure, not the upstream development process
|
||||
- The anti-goals are as important as the goals — they tell leadership what not to expect
|
||||
- Upstream is anything: IDE, agentic SDLC, or vibe coding — ACDL doesn't care how the contract was produced
|
||||
- Upstream is anything: IDE, agentic SDLC, or vibe coding — Nova doesn't care how the contract was produced
|
||||
|
||||
**Key takeaway:** ACDL is infrastructure only. App build/test/deploy is upstream.
|
||||
**Key takeaway:** Nova is infrastructure only. App build/test/deploy is upstream.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ img { display: block; margin: 0 auto; max-height: 300px; }
|
||||
<header>How The Platform Works</header>
|
||||
|
||||
<h1 id="how-the-platform-works">How The Platform Works</h1>
|
||||
<h3 id="agentic-cloud-delivery-platform">Agentic Cloud Delivery Platform</h3>
|
||||
<h3 id="nova-the-new-dawn-of-devsecops">Nova — The New Dawn of DevSecOps</h3>
|
||||
<footer>Internal</footer>
|
||||
</section>
|
||||
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="2" data-paginate="true" data-header="How The Platform Works" data-footer="Internal" data-theme="default" data-style="section {
|
||||
@@ -232,11 +232,11 @@ img { display: block; margin: 0 auto; max-height: 300px; }
|
||||
.planned { background: #fef3c7; color: #78350f; }
|
||||
;" data-marpit-pagination-total="20">
|
||||
<header>How The Platform Works</header>
|
||||
<h1 id="acdl-owns-infrastructure-not-your-app">ACDL owns infrastructure, not your app</h1>
|
||||
<h1 id="nova-owns-infrastructure-not-your-app">Nova owns infrastructure, not your app</h1>
|
||||
<p><img src="assets/png/platform-works-03-scope-boundary.png" alt="" style="width:1100px;" /></p>
|
||||
<ul>
|
||||
<li><strong>Upstream is anything</strong> — IDE, agentic SDLC, or vibe coding</li>
|
||||
<li><strong>ACDL is infrastructure only</strong> — provisions and governs AWS resources</li>
|
||||
<li><strong>Nova is infrastructure only</strong> — provisions and governs AWS resources</li>
|
||||
<li><strong>Not a general-purpose AI</strong> — autonomy is narrow, policy-bounded</li>
|
||||
<li><strong>Not a permissive highway</strong> — no escape hatches</li>
|
||||
</ul>
|
||||
@@ -1005,7 +1005,7 @@ img { display: block; margin: 0 auto; max-height: 300px; }
|
||||
<header>How The Platform Works</header>
|
||||
<h1 id="a7--operating-model--cost">A7 — Operating Model & Cost</h1>
|
||||
|
||||
<p>ACDL runs at <strong>zero cloud cost</strong> for day-to-day development. AWS spend was measured via Cost Explorer (<code>COST.md</code>, 2026-07-28):</p>
|
||||
<p>Nova runs at <strong>zero cloud cost</strong> for day-to-day development. AWS spend was measured via Cost Explorer (<code>COST.md</code>, 2026-07-28):</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# How The Platform Works
|
||||
|
||||
> **Subtitle:** Agentic Cloud Delivery Platform
|
||||
> **Subtitle:** Nova — The New Dawn of DevSecOps
|
||||
> **Audience:** Senior Leadership, CTO, Head of Cloud, Head of Infrastructure, Head of DevOps
|
||||
> **Length:** ~16 minutes · 11 main + Appendix TOC + 8 appendix = 20 slides
|
||||
> **Purpose:** Sell the platform's value to tech leadership — zero-trust, security, observability, auditability, and the shift from "operators guess" to "the platform computes safety."
|
||||
@@ -13,7 +13,9 @@
|
||||
|
||||
# How The Platform Works
|
||||
|
||||
### Agentic Cloud Delivery Platform
|
||||
### Nova — The New Dawn of DevSecOps
|
||||
|
||||
**Security as a seamless enabler of fast deployments — not a bottleneck, not a "no" department.**
|
||||
|
||||
> **Speaker notes:** Brief introduction — this deck explains *how* the platform works internally, not what the developer experience is (that's the companion deck). Set the frame: the platform is not a CI/CD tool — it's the organizational lever for shipping safely at the pace the business demands.
|
||||
|
||||
@@ -62,7 +64,7 @@ flowchart TD
|
||||
U2["Citizen dev\nintent → AI agent → contract"]
|
||||
end
|
||||
|
||||
subgraph ACDL ["ACDL — infrastructure only"]
|
||||
subgraph ACDL ["Nova — infrastructure only"]
|
||||
direction TB
|
||||
CS["Contract schema\n(validate + fail-fast)"]
|
||||
subgraph PIPE ["Central pipeline — fixed stages, every deployment"]
|
||||
@@ -97,7 +99,7 @@ flowchart TD
|
||||
```
|
||||
|
||||
- **Consumer surfaces** — technical dev or citizen dev; both produce a contract. Upstream is anything.
|
||||
- **Contract schema** — the boundary between upstream and ACDL; validated fail-fast.
|
||||
- **Contract schema** — the boundary between upstream and Nova; validated fail-fast.
|
||||
- **Central pipeline** — fixed stages, identical for every deployment: validate → resolve → security → plan → policy → confidence → evidence → apply.
|
||||
- **Module catalog** — security-reviewed primitives + modules the resolver expands against.
|
||||
- **Engine adapter** — stateless; the only engine-specific code (Terraform today).
|
||||
@@ -123,7 +125,7 @@ flowchart LR
|
||||
D["Manual promotion"]
|
||||
A --> B --> C --> D
|
||||
end
|
||||
subgraph ACDL ["With ACDL"]
|
||||
subgraph ACDL ["With Nova"]
|
||||
direction TB
|
||||
E["Declare intent\n(one YAML contract)"]
|
||||
F["Platform delivers\nsafely, autonomously"]
|
||||
@@ -137,11 +139,11 @@ flowchart LR
|
||||
- A **non-technical consumer** ships by declaring intent — no workflow, no config file, no module.
|
||||
- Every production change is **traceable to a human attestation** and an immutable evidence stream.
|
||||
|
||||
> **Speaker notes:** Land the before/after contrast: today's queue vs. ACDL's autonomous flow. The litmus test: if a platform engineer still has to touch a ticket for a dev→qa promotion, we haven't delivered the vision. The North Star is "declare intent → safe production deployment."
|
||||
> **Speaker notes:** Land the before/after contrast: today's queue vs. Nova's autonomous flow. The litmus test: if a platform engineer still has to touch a ticket for a dev→qa promotion, we haven't delivered the vision. The North Star is "declare intent → safe production deployment."
|
||||
|
||||
---
|
||||
|
||||
## Slide 5 — ACDL owns infrastructure, not your app
|
||||
## Slide 5 — Nova owns infrastructure, not your app
|
||||
|
||||
The platform is deliberately scoped — it is not trying to be everything.
|
||||
|
||||
@@ -153,7 +155,7 @@ flowchart LR
|
||||
B["Agentic SDLC\n(agent writes contract)"]
|
||||
C["Citizen dev\n(vibe codes → AI agent\n→ contract)"]
|
||||
end
|
||||
subgraph ACDL ["ACDL — infrastructure only"]
|
||||
subgraph ACDL ["Nova — infrastructure only"]
|
||||
D["Contract\nvalidated"]
|
||||
E["Resolve → Plan\nSecurity + Policy checks\nConfidence signal"]
|
||||
F["Provision\nAWS resources"]
|
||||
@@ -173,8 +175,8 @@ flowchart LR
|
||||
H --> I
|
||||
```
|
||||
|
||||
- **Upstream is anything** — IDE, agentic SDLC, or vibe coding. ACDL doesn't care how the contract was produced.
|
||||
- **ACDL is infrastructure only** — it provisions and governs AWS resources. App build/test/deploy is upstream.
|
||||
- **Upstream is anything** — IDE, agentic SDLC, or vibe coding. Nova doesn't care how the contract was produced.
|
||||
- **Nova is infrastructure only** — it provisions and governs AWS resources. App build/test/deploy is upstream.
|
||||
- **Not a general-purpose AI** — autonomy is narrow, scoped to delivery, bounded by strict policy.
|
||||
- **Not a permissive highway** — no escape hatches to bypass the confidence framework.
|
||||
|
||||
@@ -184,7 +186,7 @@ flowchart LR
|
||||
|
||||
## Slide 6 — One YAML file. The platform owns everything else.
|
||||
|
||||
The contract is the boundary between upstream and ACDL. It's all a consumer writes.
|
||||
The contract is the boundary between upstream and Nova. It's all a consumer writes.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
@@ -456,7 +458,7 @@ A phased roadmap from the current Testing baseline to the full North Star:
|
||||
|
||||
## A7 — Operating Model & Cost (real AWS spend + pre-mortem)
|
||||
|
||||
ACDL runs at **zero cloud cost** for day-to-day development. The v1.0→v1.10 AWS spend was measured directly via Cost Explorer (`COST.md`, 2026-07-28):
|
||||
Nova runs at **zero cloud cost** for day-to-day development. The v1.0→v1.10 AWS spend was measured directly via Cost Explorer (`COST.md`, 2026-07-28):
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
|
||||
@@ -33,7 +33,7 @@ style: |
|
||||
|
||||
# The Developer Experience
|
||||
|
||||
### Agentic Cloud Delivery Platform
|
||||
### Nova — The New Dawn of DevSecOps
|
||||
|
||||
<style>
|
||||
section.title h1 { font-size: 44px; margin-bottom: 0.1em; }
|
||||
@@ -48,8 +48,8 @@ section.title h3 { color: #F0F0F0; font-weight: 400; font-size: 22px; margin-top
|
||||
|
||||
- **Technical developer** — owns app code + a contract + a thin CI definition
|
||||
- **Citizen developer** — declares intent; an AI agent produces a contract that passes the **same** safety envelope
|
||||
- **Upstream is anything** — IDE, agentic SDLC, or vibe coding. ACDL doesn't care how the contract was produced
|
||||
- **ACDL is infrastructure only** — provisions and governs AWS resources. Application deployment is upstream
|
||||
- **Upstream is anything** — IDE, agentic SDLC, or vibe coding. Nova doesn't care how the contract was produced
|
||||
- **Nova is infrastructure only** — provisions and governs AWS resources. Application deployment is upstream
|
||||
|
||||
---
|
||||
|
||||
@@ -140,7 +140,7 @@ code { font-size: 13px; }
|
||||
```yaml
|
||||
uses: acdl/.github/workflows/deploy.yml@v1.12
|
||||
with:
|
||||
contract: .acdl/contract.yml
|
||||
contract: .nova/contract.yml
|
||||
mode: decommission
|
||||
changeRequestId: "CHG0678912"
|
||||
```
|
||||
@@ -287,7 +287,7 @@ section { font-size: 20px; }
|
||||
table { font-size: 18px; }
|
||||
</style>
|
||||
|
||||
ACDL runs at **zero cloud cost** for day-to-day development. AWS spend was measured via Cost Explorer (`COST.md`, 2026-07-28):
|
||||
Nova runs at **zero cloud cost** for day-to-day development. AWS spend was measured via Cost Explorer (`COST.md`, 2026-07-28):
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
|
||||
@@ -21,13 +21,13 @@
|
||||
## Slide 2 — Two consumer paths, one safety envelope
|
||||
|
||||
**Talking points:**
|
||||
- This is the scope-boundary slide — here's who uses the platform, and here's where ACDL's responsibility starts and stops
|
||||
- This is the scope-boundary slide — here's who uses the platform, and here's where Nova's responsibility starts and stops
|
||||
- Two consumer paths converge on the same contract: **technical** developer writes the contract directly; **citizen** developer declares intent and an AI agent produces a contract that passes the same safety envelope
|
||||
- Upstream is anything — your IDE, an agentic SDLC, or vibe coding on a laptop. ACDL doesn't care how the contract was produced
|
||||
- ACDL is infrastructure only — it provisions and governs AWS resources. Application deployment is upstream of the contract
|
||||
- Upstream is anything — your IDE, an agentic SDLC, or vibe coding on a laptop. Nova doesn't care how the contract was produced
|
||||
- Nova is infrastructure only — it provisions and governs AWS resources. Application deployment is upstream of the contract
|
||||
- The two surfaces are *parallel*, not a progression. A citizen developer doesn't "graduate" to the developer surface. There is no "citizen developer mode" with weaker checks
|
||||
|
||||
**Key takeaway:** Two consumer paths, one safety envelope. ACDL is infra only — anything upstream is fair game.
|
||||
**Key takeaway:** Two consumer paths, one safety envelope. Nova is infra only — anything upstream is fair game.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ img { display: block; margin: 0 auto; max-height: 280px; }
|
||||
<header>The Developer Experience</header>
|
||||
|
||||
<h1 id="the-developer-experience">The Developer Experience</h1>
|
||||
<h3 id="agentic-cloud-delivery-platform">Agentic Cloud Delivery Platform</h3>
|
||||
<h3 id="nova-the-new-dawn-of-devsecops">Nova — The New Dawn of DevSecOps</h3>
|
||||
<footer>Internal</footer>
|
||||
</section>
|
||||
</foreignObject></svg><svg data-marpit-svg="" viewBox="0 0 1280 720"><foreignObject width="1280" height="720"><section id="2" data-paginate="true" data-header="The Developer Experience" data-footer="Internal" data-theme="default" data-style="section {
|
||||
@@ -102,8 +102,8 @@ img { display: block; margin: 0 auto; max-height: 280px; }
|
||||
<ul>
|
||||
<li><strong>Technical developer</strong> — owns app code + a contract + a thin CI definition</li>
|
||||
<li><strong>Citizen developer</strong> — declares intent; an AI agent produces a contract that passes the <strong>same</strong> safety envelope</li>
|
||||
<li><strong>Upstream is anything</strong> — IDE, agentic SDLC, or vibe coding. ACDL doesn't care how the contract was produced</li>
|
||||
<li><strong>ACDL is infrastructure only</strong> — provisions and governs AWS resources. Application deployment is upstream</li>
|
||||
<li><strong>Upstream is anything</strong> — IDE, agentic SDLC, or vibe coding. Nova doesn't care how the contract was produced</li>
|
||||
<li><strong>Nova is infrastructure only</strong> — provisions and governs AWS resources. Application deployment is upstream</li>
|
||||
</ul>
|
||||
<footer>Internal</footer>
|
||||
</section>
|
||||
@@ -456,7 +456,7 @@ img { display: block; margin: 0 auto; max-height: 280px; }
|
||||
<p><img src="assets/png/developer-experience-07-decommission.png" alt="" style="width:1100px;" /></p>
|
||||
<pre is="marp-pre" data-auto-scaling="downscale-only"><code class="language-yaml"><span class="hljs-attr">uses:</span> <span class="hljs-string">acdl/.github/workflows/deploy.yml@v1.12</span>
|
||||
<span class="hljs-attr">with:</span>
|
||||
<span class="hljs-attr">contract:</span> <span class="hljs-string">.acdl/contract.yml</span>
|
||||
<span class="hljs-attr">contract:</span> <span class="hljs-string">.nova/contract.yml</span>
|
||||
<span class="hljs-attr">mode:</span> <span class="hljs-string">decommission</span>
|
||||
<span class="hljs-attr">changeRequestId:</span> <span class="hljs-string">"CHG0678912"</span>
|
||||
</code></pre>
|
||||
@@ -1028,7 +1028,7 @@ img { display: block; margin: 0 auto; max-height: 280px; }
|
||||
<header>The Developer Experience</header>
|
||||
<h1 id="a6--operating-model--cost">A6 — Operating Model & Cost</h1>
|
||||
|
||||
<p>ACDL runs at <strong>zero cloud cost</strong> for day-to-day development. AWS spend was measured via Cost Explorer (<code>COST.md</code>, 2026-07-28):</p>
|
||||
<p>Nova runs at <strong>zero cloud cost</strong> for day-to-day development. AWS spend was measured via Cost Explorer (<code>COST.md</code>, 2026-07-28):</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# The Developer Experience
|
||||
|
||||
> **Subtitle:** Agentic Cloud Delivery Platform
|
||||
> **Subtitle:** Nova — The New Dawn of DevSecOps
|
||||
> **Audience:** Senior Leadership, CTO, Head of Cloud, Head of Infrastructure, Head of DevOps
|
||||
> **Length:** ~16 minutes · 11 main + Appendix TOC + 7 appendix = 19 slides
|
||||
> **Purpose:** Sell the developer experience and the citizen developer experience to tech leadership — velocity without sacrificing safety, and security/observability/compliance as platform defaults rather than per-team effort.
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
## Slide 1 — Title
|
||||
|
||||
**Nova — The New Dawn of DevSecOps.** Security as a seamless enabler of fast deployments — not a bottleneck, not a "no" department.
|
||||
|
||||
The consumer surface is intentionally tiny. The platform's surface is large and opinionated.
|
||||
|
||||
> **Speaker notes:** Brief introduction — this deck covers *who uses the platform and how fast/safe they ship*, not the internal mechanics (that's the companion deck). Set the frame: velocity without sacrificing safety, and security/observability/compliance as platform defaults rather than per-team effort.
|
||||
@@ -28,7 +30,7 @@ flowchart LR
|
||||
A["Technical dev\n(app code + contract)"]
|
||||
B["Citizen dev\n(intent → AI agent\n→ contract)"]
|
||||
end
|
||||
subgraph ACDL ["ACDL — infrastructure only"]
|
||||
subgraph ACDL ["Nova — infrastructure only"]
|
||||
C["Same contract\nSame pipeline\nSame safety"]
|
||||
D["Provision\nAWS resources"]
|
||||
E["Evidence\nhash-chained"]
|
||||
@@ -47,10 +49,10 @@ flowchart LR
|
||||
|
||||
- **Technical developer** — owns app code + a contract + a thin CI definition.
|
||||
- **Citizen developer** — declares intent in plain language; an AI agent produces a contract that passes the **same** safety envelope.
|
||||
- **Upstream is anything** — IDE, agentic SDLC, or vibe coding. ACDL doesn't care how the contract was produced.
|
||||
- **ACDL is infrastructure only** — it provisions and governs AWS resources. Application deployment is upstream.
|
||||
- **Upstream is anything** — IDE, agentic SDLC, or vibe coding. Nova doesn't care how the contract was produced.
|
||||
- **Nova is infrastructure only** — it provisions and governs AWS resources. Application deployment is upstream.
|
||||
|
||||
> **Speaker notes:** This is the thesis of the deck. The two surfaces are *parallel*, not a progression — a citizen developer doesn't "graduate" to the developer surface. Both produce a contract; both get the same treatment. The scope boundary matters: anything upstream of the contract is out of ACDL's concern. The leadership takeaway: we expand who can ship safely without lowering the bar.
|
||||
> **Speaker notes:** This is the thesis of the deck. The two surfaces are *parallel*, not a progression — a citizen developer doesn't "graduate" to the developer surface. Both produce a contract; both get the same treatment. The scope boundary matters: anything upstream of the contract is out of Nova's concern. The leadership takeaway: we expand who can ship safely without lowering the bar.
|
||||
|
||||
---
|
||||
|
||||
@@ -66,7 +68,7 @@ flowchart TD
|
||||
U2["Citizen dev\nintent → AI agent → contract"]
|
||||
end
|
||||
|
||||
subgraph ACDL ["ACDL — infrastructure only"]
|
||||
subgraph ACDL ["Nova — infrastructure only"]
|
||||
direction TB
|
||||
CS["Contract schema\n(validate + fail-fast)"]
|
||||
subgraph PIPE ["Central pipeline — fixed stages, every deployment"]
|
||||
@@ -231,7 +233,7 @@ flowchart LR
|
||||
```yaml
|
||||
uses: acdl/.github/workflows/deploy.yml@v1.12
|
||||
with:
|
||||
contract: .acdl/contract.yml
|
||||
contract: .nova/contract.yml
|
||||
mode: decommission
|
||||
changeRequestId: "CHG0678912"
|
||||
```
|
||||
@@ -364,7 +366,7 @@ Consumers `uses:` a **versioned** central workflow. The platform fetches itself
|
||||
flowchart LR
|
||||
A["Consumer repo<br/>app + contract + 'uses:'"] -->|triggers on push to main| B["Platform runner"]
|
||||
B -->|checks out the consumer repo| A
|
||||
B -->|checks out the ACDL platform repo<br/>into the workspace| C["Platform code<br/>(modules, adapters, schemas)"]
|
||||
B -->|checks out the Nova platform repo<br/>into the workspace| C["Platform code<br/>(modules, adapters, schemas)"]
|
||||
C --> B
|
||||
B -->|runs the pipeline against<br/>the consumer's contract| D["Consumer's resources in AWS"]
|
||||
```
|
||||
@@ -426,7 +428,7 @@ flowchart LR
|
||||
|
||||
## A6 — Operating Model & Cost
|
||||
|
||||
ACDL runs at **zero cloud cost** for day-to-day development. The v1.0→v1.10 AWS spend was measured directly via Cost Explorer (`COST.md`, 2026-07-28):
|
||||
Nova runs at **zero cloud cost** for day-to-day development. The v1.0→v1.10 AWS spend was measured directly via Cost Explorer (`COST.md`, 2026-07-28):
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# Agentic Cloud Delivery Vision
|
||||
# Nova Vision
|
||||
|
||||
> **Nova — The New Dawn of DevSecOps.** Security as a seamless enabler of fast deployments — not a bottleneck, not a "no" department.
|
||||
|
||||
## 1. The Friction
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ACDL Modules
|
||||
# Nova Modules
|
||||
|
||||
Reusable building blocks for cloud infrastructure. Each module is
|
||||
self-documented with a `README.md` following the
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# ACDL Module Engineering Standards
|
||||
# Nova Module Engineering Standards
|
||||
|
||||
Standards for authoring and reviewing ACDL modules. These standards
|
||||
Standards for authoring and reviewing Nova modules. These standards
|
||||
govern the two module tiers — **L1 primitives** (single cloud resource
|
||||
or small group of related resources) and **L2 modules** (compositions
|
||||
that reference L1 primitives to deploy a complete stack) — and the
|
||||
|
||||
@@ -42,7 +42,7 @@ rotation enabled. One key per L2 deployment (no shared keys).
|
||||
"type": "aws:kms:key",
|
||||
"module": "kms-key@1.0.0",
|
||||
"inputs": {
|
||||
"description": "ACDL per-stack CMK",
|
||||
"description": "Nova per-stack CMK",
|
||||
"region": "us-east-1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
# ACDL Pipelines
|
||||
# Nova Pipelines
|
||||
|
||||
## Overview
|
||||
|
||||
ACDL uses declarative pipeline contracts (YAML) as the single source of truth. Both Gitea and GitHub workflows implement the same contract (byte-identical). The shell runner (`scripts/run_ci.sh`) mirrors the CI pipeline locally so that every stage that runs in CI can be reproduced on a developer machine without a forge.
|
||||
Nova uses declarative pipeline contracts (YAML) as the single source of truth. Both Gitea and GitHub workflows implement the same contract (byte-identical). The shell runner (`scripts/run_ci.sh`) mirrors the CI pipeline locally so that every stage that runs in CI can be reproduced on a developer machine without a forge.
|
||||
|
||||
## Existing Pipelines
|
||||
|
||||
| Pipeline | File | Stages | Triggers |
|
||||
| --- | --- | --- | --- |
|
||||
| ACDL CI | `ci.yml` | `lint`, `test`, `check-only` | push/PR to `main` |
|
||||
| ACDL Deploy | `contract.yml` | `validate-contract`, `resolve-stack`, `terraform-plan`, `checkov`, `confidence`, `apply`, `publish-outputs`, `deploy-uptime`, `comment-outputs` | push/PR to `main` (consumer repos via `workflow_call`) |
|
||||
| ACDL Modules Lifecycle | `modules-lifecycle.yml` | `platform-vpc-apply`, `lifecycle-apply`, `lifecycle-modify`, `lifecycle-destroy`, `l2-lifecycle-apply`, `l2-lifecycle-modify`, `l2-lifecycle-destroy`, `platform-vpc-destroy` | PR to `main` + `workflow_dispatch` |
|
||||
| Nova CI | `ci.yml` | `lint`, `test`, `check-only` | push/PR to `main` |
|
||||
| Nova Deploy | `contract.yml` | `validate-contract`, `resolve-stack`, `terraform-plan`, `checkov`, `confidence`, `apply`, `publish-outputs`, `deploy-uptime`, `comment-outputs` | push/PR to `main` (consumer repos via `workflow_call`) |
|
||||
| Nova Modules Lifecycle | `modules-lifecycle.yml` | `platform-vpc-apply`, `lifecycle-apply`, `lifecycle-modify`, `lifecycle-destroy`, `l2-lifecycle-apply`, `l2-lifecycle-modify`, `l2-lifecycle-destroy`, `platform-vpc-destroy` | PR to `main` + `workflow_dispatch` |
|
||||
|
||||
## How to Write a Pipeline
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ACDL Central CI Pipeline Contract (v1.5)
|
||||
# Nova Central CI Pipeline Contract (v1.5)
|
||||
#
|
||||
# This is the single source of truth for the CI/CD pipeline. Both
|
||||
# .gitea/workflows/ci.yml (Gitea Actions, dev) and
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# ACDL Central Deployment Pipeline Contract (v1.8)
|
||||
# Nova Central Deployment Pipeline Contract (v1.8)
|
||||
#
|
||||
# This is the single source of truth for the deployment pipeline. It
|
||||
# declares the stages that run when a consumer submits a contract:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: acdl-modules-lifecycle
|
||||
|
||||
# ACDL Modules Lifecycle Pipeline — apply→modify→destroy against live AWS.
|
||||
# Nova Modules Lifecycle Pipeline — apply→modify→destroy against live AWS.
|
||||
#
|
||||
# Matrix-runs each L1 module's examples/{simple,complex}.yml contracts:
|
||||
# 1. --apply simple.yml (terraform apply — creates resources)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "acdl"
|
||||
name = "nova"
|
||||
version = "1.14.0"
|
||||
description = "Agentic Cloud Delivery Platform — consumers declare intent; the platform delivers safe production deployment."
|
||||
description = "Nova — consumers declare intent; the platform delivers safe production deployment."
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"boto3>=1.34",
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
# ACDL Schemas
|
||||
# Nova Schemas
|
||||
|
||||
## Overview
|
||||
|
||||
ACDL uses JSON Schema draft 2020-12 for all declarative contracts. Schemas are the single source of truth for validation. Every contract, stack instance, pipeline, and policy result in the platform is validated against a schema in this directory before it is consumed by any downstream code path. The resolver, the pipeline runner, the CI workflows, and the test suite all load these schemas directly.
|
||||
Nova uses JSON Schema draft 2020-12 for all declarative contracts. Schemas are the single source of truth for validation. Every contract, stack instance, pipeline, and policy result in the platform is validated against a schema in this directory before it is consumed by any downstream code path. The resolver, the pipeline runner, the CI workflows, and the test suite all load these schemas directly.
|
||||
|
||||
## Existing Schemas
|
||||
|
||||
| Schema | File | Purpose | Where Validated |
|
||||
| --- | --- | --- | --- |
|
||||
| ACDL Consumer Contract | `contract.schema.json` | Consumer contract validation (id, name, environment, infrastructure map with module versions + inputs) | `core/contract_resolver.py`, `scripts/run_platform.sh` Step 1, CI `schema-validation` job |
|
||||
| ACDL Target Stack | `stack.schema.json` | Target Stack instance validation (resources, relationships, composition tree, NFRs) | `core/contract_resolver.py` (post-resolution), `tests/conftest.py` |
|
||||
| ACDL Central Pipeline Contract | `pipeline.schema.json` | Central CI pipeline contract (stages, commands, triggers, runner) | `tests/test_pipeline_contract.py` |
|
||||
| ACDL Central Deployment Pipeline Contract | `deploy-pipeline.schema.json` | Central deploy pipeline contract (validate → resolve → plan → checkov → confidence → apply → publish → uptime → comment) | `tests/test_pipeline_contract.py` |
|
||||
| ACDL PolicyCheckResult | `policy_check_result.schema.json` | Normalized policy check result schema (the contract between policy engines and the confidence signal) | `tests/conftest.py`, all adapter tests |
|
||||
| ACDL Tagging Standard | `tagging-standard.json` | Required tag set for all taggable AWS resources | `adapters/terraform/policy/custom_rules/acdl_tagging.py` |
|
||||
| Nova Consumer Contract | `contract.schema.json` | Consumer contract validation (id, name, environment, infrastructure map with module versions + inputs) | `core/contract_resolver.py`, `scripts/run_platform.sh` Step 1, CI `schema-validation` job |
|
||||
| Nova Target Stack | `stack.schema.json` | Target Stack instance validation (resources, relationships, composition tree, NFRs) | `core/contract_resolver.py` (post-resolution), `tests/conftest.py` |
|
||||
| Nova Central Pipeline Contract | `pipeline.schema.json` | Central CI pipeline contract (stages, commands, triggers, runner) | `tests/test_pipeline_contract.py` |
|
||||
| Nova Central Deployment Pipeline Contract | `deploy-pipeline.schema.json` | Central deploy pipeline contract (validate → resolve → plan → checkov → confidence → apply → publish → uptime → comment) | `tests/test_pipeline_contract.py` |
|
||||
| Nova PolicyCheckResult | `policy_check_result.schema.json` | Normalized policy check result schema (the contract between policy engines and the confidence signal) | `tests/conftest.py`, all adapter tests |
|
||||
| Nova Tagging Standard | `tagging-standard.json` | Required tag set for all taggable AWS resources | `adapters/terraform/policy/custom_rules/nova_tagging.py` |
|
||||
|
||||
## How to Write a Schema
|
||||
|
||||
1. Use JSON Schema draft 2020-12: `"$schema": "https://json-schema.org/draft/2020-12/schema"`.
|
||||
2. Set `$id` to `https://acdl.cloudinit.dev/schemas/<name>.schema.json`.
|
||||
2. Set `$id` to `https://nova.cloudinit.dev/schemas/<name>.schema.json`.
|
||||
3. Include `title` and `description` at the document root.
|
||||
4. Set `type: object` at the document root.
|
||||
5. Declare a `required` array listing the mandatory top-level property names.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://acdl.cloudinit.dev/schemas/contract.schema.json",
|
||||
"title": "ACDL Consumer Contract",
|
||||
"$id": "https://nova.cloudinit.dev/schemas/contract.schema.json",
|
||||
"title": "Nova Consumer Contract",
|
||||
"description": "A consumer contract declares intent: which infrastructure to deploy, in which environment, with which inputs. The contract is keyed by an operational id (3-6 char acronym, becomes the stack name for state keys, tags, and outbox events) and a human-readable name (becomes the stack title for display and evidence). The infrastructure map is keyed by module name; each entry carries an optional version (defaults to the latest published version from the module registry) and per-module inputs. The contract resolver resolves each infrastructure entry against modules/registry.json, then merges them into a single Target Stack instance.",
|
||||
"type": "object",
|
||||
"required": ["id", "name", "environment", "infrastructure"],
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://acdl.cloudinit.dev/schemas/deploy-pipeline.schema.json",
|
||||
"title": "ACDL Central Deployment Pipeline Contract",
|
||||
"description": "Declarative contract for the ACDL deployment pipeline. Declares the stages that run when a consumer submits a contract: validate-contract, resolve-stack, terraform-plan, checkov, confidence, apply. The platform (scripts/run_platform.sh) implements these stages. This is the declarative pipeline spec; the executable workflow is .github/workflows/deploy.yml which invokes scripts/run_platform.sh.",
|
||||
"$id": "https://nova.cloudinit.dev/schemas/deploy-pipeline.schema.json",
|
||||
"title": "Nova Central Deployment Pipeline Contract",
|
||||
"description": "Declarative contract for the Nova deployment pipeline. Declares the stages that run when a consumer submits a contract: validate-contract, resolve-stack, terraform-plan, checkov, confidence, apply. The platform (scripts/run_platform.sh) implements these stages. This is the declarative pipeline spec; the executable workflow is .github/workflows/deploy.yml which invokes scripts/run_platform.sh.",
|
||||
"type": "object",
|
||||
"required": ["name", "triggers", "runner", "stages"],
|
||||
"properties": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://acdl.cloudinit.dev/schemas/environment.schema.json",
|
||||
"title": "ACDL Platform-Managed Environment",
|
||||
"$id": "https://nova.cloudinit.dev/schemas/environment.schema.json",
|
||||
"title": "Nova Platform-Managed Environment",
|
||||
"description": "A named environment the platform owns (an AWS account or scoped partition, a network, a state backend, an IAM role surfaced to the consumer via ABAC). Selected by name in the contract's 'environment' field. The environment onboarding check (core/environment_check.py) loads the matching <name>.json; the contract resolver (core/contract_resolver.py) uses it as the 'env' context for ${env.<field>} interpolation.",
|
||||
"type": "object",
|
||||
"required": ["name", "account_id", "region", "state_backend", "network", "runner_role_arn", "autonomy", "confidence_threshold"],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://acdl.cloudinit.dev/schemas/modules-lifecycle-pipeline.schema.json",
|
||||
"title": "ACDL Modules Lifecycle Pipeline Contract",
|
||||
"$id": "https://nova.cloudinit.dev/schemas/modules-lifecycle-pipeline.schema.json",
|
||||
"title": "Nova Modules Lifecycle Pipeline Contract",
|
||||
"description": "Declarative contract for the modules-lifecycle pipeline. Matrix-runs each L1 module's examples/{simple,complex}.yml contracts through apply→modify→destroy against live AWS. Both Gitea Actions (.gitea/workflows/modules-lifecycle.yml) and GitHub Actions (.github/workflows/modules-lifecycle.yml) implement this contract byte-identically.",
|
||||
"type": "object",
|
||||
"required": ["name", "triggers", "runner", "python_version", "terraform_version", "stages", "matrix"],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://acdl.cloudinit.dev/schemas/pipeline.schema.json",
|
||||
"title": "ACDL Central Pipeline Contract",
|
||||
"$id": "https://nova.cloudinit.dev/schemas/pipeline.schema.json",
|
||||
"title": "Nova Central Pipeline Contract",
|
||||
"description": "Declarative contract for a CI/CD pipeline. Both Gitea Actions (.gitea/workflows/ci.yml, dev) and GitHub Actions (.github/workflows/ci.yml, production) implement the stages, commands, triggers, and runner declared here. The shell script scripts/run_ci.sh mirrors the same stages for local reproducibility. The contract is the single source of truth; the workflow YAMLs and run_ci.sh are generated/validated against it.",
|
||||
"$comment": "The pipeline contract does not replace workflow YAML syntax — it declares the *intent* (stages, commands, triggers, runner) that both Gitea and GitHub workflows implement. A test (tests/test_pipeline_contract.py) validates conformance: the workflow YAMLs must declare the same jobs/stages/commands as the contract, and run_ci.sh must run the same commands in the same order.",
|
||||
"type": "object",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://acdl.cloudinit.dev/schemas/policy_check_result.schema.json",
|
||||
"title": "ACDL PolicyCheckResult",
|
||||
"$id": "https://nova.cloudinit.dev/schemas/policy_check_result.schema.json",
|
||||
"title": "Nova PolicyCheckResult",
|
||||
"description": "Normalized policy check result — the contract between policy engines and the confidence signal. Engine-specific adapters (checkov_adapter.py, future kyverno_adapter) translate native engine output to this shape. The confidence signal consumes a list of these as its policy input; it is engine-agnostic. The severity enum drives the severity->penalty mapping (critical hard-override, high -0.2, medium -0.05, low -0.01, info 0.0).",
|
||||
"$comment": "Canonical PolicyCheckResult (ARCHITECTURE.md §12.6). The confidence signal (platform/confidence_signal.py) consumes a list of these as its policy input; it is engine-agnostic. Adapters translate native output to this shape; the signal never reads engine-specific evidence.",
|
||||
"type": "object",
|
||||
@@ -24,7 +24,7 @@
|
||||
},
|
||||
"ruleId": {
|
||||
"type": "string",
|
||||
"description": "Rule identifier (e.g. CKV_AWS_24, KYVERNO_NO_PRIVILEGED, ACDL_TAG_NAMING)."
|
||||
"description": "Rule identifier (e.g. CKV_AWS_24, KYVERNO_NO_PRIVILEGED, NOVA_TAG_NAMING)."
|
||||
},
|
||||
"severity": {
|
||||
"type": "string",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://acdl.cloudinit.dev/schemas/stack.schema.json",
|
||||
"title": "ACDL Target Stack",
|
||||
"$id": "https://nova.cloudinit.dev/schemas/stack.schema.json",
|
||||
"title": "Nova Target Stack",
|
||||
"description": "Angine-neutral description of a target stack: resources with typed inputs/outputs/NFRs, relationships (single parent per child), composition tree (max depth 5), and policy hooks. The L1 registry, L2 composition tree, contract YML, and PolicyCheckResult schema are all defined against this stack schema. Angine adapters (the Terraform adapter in v1) are the only engine-specific code.",
|
||||
"$comment": "v1 ships one adapter (Terraform). The stack is nearly isomorphic to Terraform in v1 (ARCHITECTURE.md §12.1); the adapter compiles resource.module -> module block, resource.inputs -> variable + arg, resource.outputs -> output, relationship.kind=uses_output -> interpolation, relationship.kind=parent -> composition ordering hint. As more adapters appear (v2+), the stack gains expressiveness; the L1 content + contract YML + composition tree do not change. The schema body is engine-agnostic: no Terraform block keywords (variable/output/resource as blocks) and no aws_ provider prefixes in the schema keywords; type values are stack types (aws:s3:bucket), not Terraform resource types (aws_s3_bucket).",
|
||||
"type": "object",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://acdl.dev/schemas/tagging-standard.json",
|
||||
"title": "ACDL Tagging Standard",
|
||||
"description": "Required tags for all taggable AWS resources created by the platform. Enforced by a Checkov custom Python rule (adapters/terraform/policy/custom_rules/acdl_tagging.py). The checkov adapter maps ACDL_TAG_NAMING as a real rule (D-054, D-043 closure).",
|
||||
"$id": "https://nova.dev/schemas/tagging-standard.json",
|
||||
"title": "Nova Tagging Standard",
|
||||
"description": "Required tags for all taggable AWS resources created by the platform. Enforced by a Checkov custom Python rule (adapters/terraform/policy/custom_rules/nova_tagging.py, D-109 warn mode in P2 — legacy acdl:* tag-key values are left for P3). The checkov adapter maps NOVA_TAG_NAMING as a real rule (D-054, D-043 closure; renamed from ACDL_TAG_NAMING in P2, REQ-158).",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"required_tags": {
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
Steps performed by this script:
|
||||
1. Load AWS creds from /root/acdl/.env.secrets
|
||||
(ACDL_AWS_ACCESS_KEY_ID, ACDL_AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION).
|
||||
(NOVA_AWS_ACCESS_KEY_ID, NOVA_AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION;
|
||||
dual-read ACDL_* fallback until P5).
|
||||
2. Create the ECR repo `acdl-microservice` if it doesn't exist
|
||||
(ecr:DescribeRepositories / ecr:CreateRepository). Region: us-east-1.
|
||||
3. Get the ECR login password (ecr:GetAuthorizationToken) and run
|
||||
@@ -26,17 +27,26 @@ import pathlib
|
||||
|
||||
import boto3
|
||||
|
||||
# Repo root on sys.path so `from core import env` resolves to THIS package
|
||||
# (avoids editable-installed third-party `core` shadow).
|
||||
_REPO_ROOT = str(pathlib.Path(__file__).resolve().parent.parent)
|
||||
if _REPO_ROOT not in sys.path:
|
||||
sys.path.insert(0, _REPO_ROOT)
|
||||
|
||||
from core import env
|
||||
|
||||
|
||||
REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
|
||||
ENV_FILE = REPO_ROOT / ".env.secrets"
|
||||
AWS_ACCOUNT_ID = os.environ.get("ACDL_AWS_ACCOUNT_ID", "581513795199")
|
||||
AWS_ACCOUNT_ID = env.get_env("AWS_ACCOUNT_ID", "581513795199")
|
||||
AWS_REGION = "us-east-1"
|
||||
ECR_REPO_NAME = "acdl-microservice"
|
||||
IMAGE_TAG = "latest"
|
||||
|
||||
|
||||
def _load_env(path):
|
||||
"""Load ACDL_AWS_* + AWS_DEFAULT_REGION from a flat KEY=VALUE file."""
|
||||
"""Load NOVA_AWS_* (preferred) / ACDL_AWS_* (fallback) + AWS_DEFAULT_REGION
|
||||
from a flat KEY=VALUE file (dual-read per G-106, until P5)."""
|
||||
creds = {}
|
||||
with open(path, "r") as fh:
|
||||
for line in fh:
|
||||
@@ -54,11 +64,12 @@ def main():
|
||||
return 2
|
||||
|
||||
creds = _load_env(ENV_FILE)
|
||||
access_key = creds.get("ACDL_AWS_ACCESS_KEY_ID")
|
||||
secret_key = creds.get("ACDL_AWS_SECRET_ACCESS_KEY")
|
||||
# Dual-read: NOVA_* preferred, ACDL_* fallback (G-106, removed in P5).
|
||||
access_key = creds.get("NOVA_AWS_ACCESS_KEY_ID") or creds.get("ACDL_AWS_ACCESS_KEY_ID")
|
||||
secret_key = creds.get("NOVA_AWS_SECRET_ACCESS_KEY") or creds.get("ACDL_AWS_SECRET_ACCESS_KEY")
|
||||
region = creds.get("AWS_DEFAULT_REGION", AWS_REGION)
|
||||
if not access_key or not secret_key:
|
||||
print("FAIL: ACDL_AWS_ACCESS_KEY_ID / ACDL_AWS_SECRET_ACCESS_KEY missing",
|
||||
print("FAIL: NOVA_AWS_ACCESS_KEY_ID / NOVA_AWS_SECRET_ACCESS_KEY missing",
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
# scripts/rotate_spike_key.sh - rotate the acdl-spike-runner IAM access key.
|
||||
#
|
||||
# Uses the bootstrap root key (ACDL_BOOTSTRAP_AWS_*) from the env to:
|
||||
# Uses the bootstrap root key (NOVA_BOOTSTRAP_AWS_*, ACDL_BOOTSTRAP_AWS_*
|
||||
# fallback) from the env to:
|
||||
# 1. List acdl-spike-runner's access keys.
|
||||
# 2. Create a new key.
|
||||
# 3. Deactivate + delete the old key(s).
|
||||
# 4. Write the new key to gitignored .env.secrets (chmod 600).
|
||||
# 5. Optionally upload to Gitea secrets if ACDL_GITEA_TOKEN is set.
|
||||
# 5. Optionally upload to Gitea secrets if NOVA_GITEA_TOKEN is set.
|
||||
#
|
||||
# Idempotent: re-running always ends with exactly 1 active key for the user.
|
||||
# Does NOT rotate the bootstrap root key (D-034 closure = manual user step).
|
||||
#
|
||||
# Spike scope (D-039): the spike user key is per-run-rotated; real OIDC is
|
||||
# v1.2 (blocked on go-gitea/gitea#36988).
|
||||
# Nova rebrand (P2): writes NOVA_* keys; ACDL_* bootstrap fallback kept
|
||||
# until P5 (the AWS user/role rename acdl-spike-runner → nova-spike-runner
|
||||
# is P4 territory — left unchanged here).
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
@@ -20,8 +24,9 @@ ENV_FILE="$ROOT/.env.secrets"
|
||||
|
||||
fail() { echo "FAIL: $*" >&2; exit 1; }
|
||||
|
||||
: "${ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID:?set ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID to the root key}"
|
||||
: "${ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY:?set ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY to the root key}"
|
||||
# Dual-read bootstrap creds: NOVA_* preferred, ACDL_* fallback (removed in P5).
|
||||
: "${NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID:-${ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID:?set NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID (or ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID) to the root key}}"
|
||||
: "${NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY:-${ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY:?set NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY (or ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY) to the root key}}"
|
||||
REGION="${AWS_DEFAULT_REGION:-us-east-1}"
|
||||
USER_NAME="acdl-spike-runner"
|
||||
|
||||
@@ -38,9 +43,13 @@ region = os.environ.get("AWS_DEFAULT_REGION", "us-east-1")
|
||||
user = "acdl-spike-runner"
|
||||
env_file = os.path.join(os.getcwd(), ".env.secrets")
|
||||
|
||||
# Dual-read bootstrap creds: NOVA_* preferred, ACDL_* fallback (G-106, removed in P5).
|
||||
bootstrap_key = os.environ.get("NOVA_BOOTSTRAP_AWS_ACCESS_KEY_ID") or os.environ["ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID"]
|
||||
bootstrap_secret = os.environ.get("NOVA_BOOTSTRAP_AWS_SECRET_ACCESS_KEY") or os.environ["ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY"]
|
||||
|
||||
session = boto3.Session(
|
||||
aws_access_key_id=os.environ["ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID"],
|
||||
aws_secret_access_key=os.environ["ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY"],
|
||||
aws_access_key_id=bootstrap_key,
|
||||
aws_secret_access_key=bootstrap_secret,
|
||||
region_name=region,
|
||||
)
|
||||
iam = session.client("iam")
|
||||
@@ -65,20 +74,23 @@ for k in active:
|
||||
print(f"iam: deactivated+deleted old key {old_id}", file=sys.stderr)
|
||||
|
||||
# Write the new key to gitignored .env.secrets (chmod 600).
|
||||
# Nova rebrand (P2): keys are NOVA_*; the ACDL_* legacy keys are the
|
||||
# dual-read fallback source until P5 (kept as comments in .env.secrets).
|
||||
with open(env_file, "w") as fh:
|
||||
fh.write(f"ACDL_AWS_ACCESS_KEY_ID={new_id}\n")
|
||||
fh.write(f"ACDL_AWS_SECRET_ACCESS_KEY={new_secret}\n")
|
||||
fh.write(f"NOVA_AWS_ACCESS_KEY_ID={new_id}\n")
|
||||
fh.write(f"NOVA_AWS_SECRET_ACCESS_KEY={new_secret}\n")
|
||||
fh.write(f"AWS_DEFAULT_REGION={region}\n")
|
||||
os.chmod(env_file, 0o600)
|
||||
print(f"rotated key written to {env_file} (chmod 600)", file=sys.stderr)
|
||||
|
||||
# Optionally upload to Gitea secrets.
|
||||
gitea_token = os.environ.get("ACDL_GITEA_TOKEN")
|
||||
# Dual-read token: NOVA_GITEA_TOKEN preferred, ACDL_GITEA_TOKEN fallback (G-106).
|
||||
gitea_token = os.environ.get("NOVA_GITEA_TOKEN") or os.environ.get("ACDL_GITEA_TOKEN")
|
||||
if gitea_token:
|
||||
import urllib.request
|
||||
base = "https://git.cloudinit.dev/api/v1/repos/continuous-intelligence/acdl/actions/secrets"
|
||||
for name, value in [("ACDL_AWS_ACCESS_KEY_ID", new_id),
|
||||
("ACDL_AWS_SECRET_ACCESS_KEY", new_secret)]:
|
||||
for name, value in [("NOVA_AWS_ACCESS_KEY_ID", new_id),
|
||||
("NOVA_AWS_SECRET_ACCESS_KEY", new_secret)]:
|
||||
req = urllib.request.Request(
|
||||
f"{base}/{name}",
|
||||
data=json.dumps({"value": value}).encode(),
|
||||
@@ -92,7 +104,7 @@ if gitea_token:
|
||||
except Exception as e:
|
||||
print(f"gitea: secret {name} upload FAILED: {e}", file=sys.stderr)
|
||||
else:
|
||||
print("gitea: ACDL_GITEA_TOKEN not set; Gitea secret upload skipped (v1.2 hardening)", file=sys.stderr)
|
||||
print("gitea: NOVA_GITEA_TOKEN not set; Gitea secret upload skipped (v1.2 hardening)", file=sys.stderr)
|
||||
|
||||
print(f"OK: {user} now has exactly 1 active key: {new_id}")
|
||||
PY
|
||||
@@ -45,11 +45,12 @@ python3 -m py_compile \
|
||||
core/confidence_signal.py \
|
||||
core/outbox_writer.py \
|
||||
core/output_publisher.py \
|
||||
core/env.py \
|
||||
core/contract_resolver.py \
|
||||
core/lambda/contract_ingestor.py \
|
||||
adapters/terraform/adapter.py \
|
||||
adapters/terraform/policy/checkov_adapter.py \
|
||||
adapters/terraform/policy/custom_rules/acdl_tagging.py \
|
||||
adapters/terraform/policy/custom_rules/nova_tagging.py \
|
||||
adapters/wiz/wiz_adapter.py \
|
||||
adapters/kyverno/kyverno_adapter.py \
|
||||
scripts/push_consumer_image.py \
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
# Usage: run_l2_lifecycle_destroy.sh <module>
|
||||
#
|
||||
# Wraps run_platform.sh for L2 composition modules in the modules-lifecycle
|
||||
# pipeline. Sets ACDL_REMOTE_STATE_KEY to point to the CI VPC state.
|
||||
# pipeline. Sets NOVA_REMOTE_STATE_KEY (ACDL_REMOTE_STATE_KEY fallback until
|
||||
# P5) to point to the CI VPC state.
|
||||
#
|
||||
# NOTE: unlike the L1 scripts (run_lifecycle_destroy.sh), the L2 path does
|
||||
# NOT take a ci-vpc-outputs.json argument. L2 compositions reference the
|
||||
@@ -13,7 +14,8 @@
|
||||
# parity with the L1 matrix, but $2 is accepted-but-ignored here (documented,
|
||||
# not a bug).
|
||||
#
|
||||
# Lifecycle mode (REQ-134): ACDL_LIFECYCLE_MODE default "plan" = no-op
|
||||
# Lifecycle mode (REQ-134): NOVA_LIFECYCLE_MODE (dual-read NOVA_* preferred,
|
||||
# ACDL_* fallback until P5) default "plan" = no-op
|
||||
# (plan mode never applies resources, so there is nothing to destroy).
|
||||
# Set to "full" for the real `--destroy` against live AWS.
|
||||
set -euo pipefail
|
||||
@@ -23,7 +25,8 @@ cd "$ROOT"
|
||||
MODULE="$1"
|
||||
|
||||
# Lifecycle mode: "plan" (default) skips destroy; "full" runs the real destroy.
|
||||
LIFECYCLE_MODE="${ACDL_LIFECYCLE_MODE:-plan}"
|
||||
# Dual-read: NOVA_* preferred, ACDL_* fallback (removed in P5).
|
||||
LIFECYCLE_MODE="${NOVA_LIFECYCLE_MODE:-${ACDL_LIFECYCLE_MODE:-plan}}"
|
||||
|
||||
if [ "$LIFECYCLE_MODE" != "full" ]; then
|
||||
echo "lifecycle mode=$LIFECYCLE_MODE — nothing to destroy (plan-only run), exiting 0"
|
||||
@@ -32,8 +35,10 @@ fi
|
||||
|
||||
CONTRACT="modules/l2/${MODULE}/examples/complex.yml"
|
||||
|
||||
# Point terraform_remote_state to the CI VPC state (not the platform VPC)
|
||||
export ACDL_REMOTE_STATE_KEY="spike/ci-vpc/terraform.tfstate"
|
||||
# Point terraform_remote_state to the CI VPC state (not the platform VPC).
|
||||
# Set both NOVA_* (preferred) and ACDL_* (legacy fallback) until P5.
|
||||
export NOVA_REMOTE_STATE_KEY="spike/ci-vpc/terraform.tfstate"
|
||||
export ACDL_REMOTE_STATE_KEY="spike/ci-vpc/terraform.tfstate" # legacy fallback, removed in P5
|
||||
|
||||
# Run the platform lifecycle destroy command
|
||||
bash scripts/run_platform.sh --destroy "$CONTRACT"
|
||||
@@ -4,19 +4,21 @@
|
||||
# Usage: run_l2_lifecycle_test.sh <module> <example>
|
||||
#
|
||||
# Wraps run_platform.sh for L2 composition modules in the modules-lifecycle
|
||||
# pipeline. Sets ACDL_REMOTE_STATE_KEY to point to the CI VPC state so the
|
||||
# microservice composition's terraform_remote_state data source reads from
|
||||
# the short-lived CI VPC (not the long-lived platform VPC).
|
||||
# pipeline. Sets NOVA_REMOTE_STATE_KEY (ACDL_REMOTE_STATE_KEY fallback until
|
||||
# P5) to point to the CI VPC state so the microservice composition's
|
||||
# terraform_remote_state data source reads from the short-lived CI VPC
|
||||
# (not the long-lived platform VPC).
|
||||
#
|
||||
# NOTE: unlike the L1 scripts (run_lifecycle_test.sh), the L2 path does NOT
|
||||
# take a ci-vpc-outputs.json argument. L2 compositions reference the platform
|
||||
# VPC via terraform_remote_state (a data source), not by injecting VPC
|
||||
# outputs into the contract. The ACDL_REMOTE_STATE_KEY env var points the
|
||||
# outputs into the contract. The NOVA_REMOTE_STATE_KEY env var points the
|
||||
# data source at the correct CI VPC state key. The workflow passes 3
|
||||
# positional args for parity with the L1 matrix, but $3 is accepted-but-
|
||||
# ignored here (documented, not a bug).
|
||||
#
|
||||
# Lifecycle mode (REQ-134): ACDL_LIFECYCLE_MODE default "plan" runs
|
||||
# Lifecycle mode (REQ-134): NOVA_LIFECYCLE_MODE (dual-read NOVA_* preferred,
|
||||
# ACDL_* fallback until P5) default "plan" runs
|
||||
# `run_platform.sh --plan-only` (fast, no AWS mutation). Set to "full" for
|
||||
# the real `--apply` against live AWS.
|
||||
set -euo pipefail
|
||||
@@ -27,12 +29,16 @@ MODULE="$1"
|
||||
EXAMPLE="$2" # simple or complex
|
||||
|
||||
# Lifecycle mode: "plan" (default, fast) or "full" (real apply against AWS).
|
||||
LIFECYCLE_MODE="${ACDL_LIFECYCLE_MODE:-plan}"
|
||||
# Dual-read: NOVA_* preferred, ACDL_* fallback (removed in P5).
|
||||
LIFECYCLE_MODE="${NOVA_LIFECYCLE_MODE:-${ACDL_LIFECYCLE_MODE:-plan}}"
|
||||
|
||||
CONTRACT="modules/l2/${MODULE}/examples/${EXAMPLE}.yml"
|
||||
|
||||
# Point terraform_remote_state to the CI VPC state (not the platform VPC)
|
||||
export ACDL_REMOTE_STATE_KEY="spike/ci-vpc/terraform.tfstate"
|
||||
# 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 ACDL_REMOTE_STATE_KEY="spike/ci-vpc/terraform.tfstate" # legacy fallback, removed in P5
|
||||
|
||||
# Run the platform lifecycle command (plan-only by default; full = apply).
|
||||
if [ "$LIFECYCLE_MODE" = "full" ]; then
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
# For VPC-dependent modules, injects CI VPC outputs into the complex contract
|
||||
# before destroy (so terraform can find the resources in the right VPC).
|
||||
#
|
||||
# Lifecycle mode (REQ-134): ACDL_LIFECYCLE_MODE default "plan" = no-op
|
||||
# Lifecycle mode (REQ-134): NOVA_LIFECYCLE_MODE (dual-read NOVA_* preferred,
|
||||
# ACDL_* fallback until P5) default "plan" = no-op
|
||||
# (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"
|
||||
# for the real `--destroy` against live AWS.
|
||||
@@ -19,7 +20,8 @@ CI_VPC_OUTPUTS="${2:-}"
|
||||
|
||||
# Lifecycle mode: "plan" (default) skips destroy (nothing was applied);
|
||||
# "full" runs the real terraform destroy.
|
||||
LIFECYCLE_MODE="${ACDL_LIFECYCLE_MODE:-plan}"
|
||||
# Dual-read: NOVA_* preferred, ACDL_* fallback (removed in P5).
|
||||
LIFECYCLE_MODE="${NOVA_LIFECYCLE_MODE:-${ACDL_LIFECYCLE_MODE:-plan}}"
|
||||
|
||||
if [ "$LIFECYCLE_MODE" != "full" ]; then
|
||||
echo "lifecycle mode=$LIFECYCLE_MODE — nothing to destroy (plan-only run), exiting 0"
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
# The CI VPC is short-lived (created/destroyed by the pipeline), separate
|
||||
# from the long-lived platform VPC.
|
||||
#
|
||||
# Lifecycle mode (REQ-134): the ACDL_LIFECYCLE_MODE env var selects the
|
||||
# tier. Default "plan" runs `run_platform.sh --plan-only` (fast, no AWS
|
||||
# Lifecycle mode (REQ-134): the NOVA_LIFECYCLE_MODE env var selects the
|
||||
# tier (dual-read NOVA_* preferred, ACDL_* fallback until P5). Default
|
||||
# "plan" runs `run_platform.sh --plan-only` (fast, no AWS
|
||||
# mutation, validates the contract->resolver->adapter->plan chain for
|
||||
# every module). Set to "full" to run the real `--apply` (terraform apply
|
||||
# against live AWS). The CI variable is passed via the workflow input
|
||||
@@ -25,7 +26,8 @@ EXAMPLE="$2" # simple or complex
|
||||
CI_VPC_OUTPUTS="${3:-}"
|
||||
|
||||
# Lifecycle mode: "plan" (default, fast) or "full" (real apply against AWS).
|
||||
LIFECYCLE_MODE="${ACDL_LIFECYCLE_MODE:-plan}"
|
||||
# Dual-read: NOVA_* preferred, ACDL_* fallback (removed in P5).
|
||||
LIFECYCLE_MODE="${NOVA_LIFECYCLE_MODE:-${ACDL_LIFECYCLE_MODE:-plan}}"
|
||||
|
||||
CONTRACT="modules/l1/${MODULE}/examples/${EXAMPLE}.yml"
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
# Capture the caller's CWD before we cd to ROOT. The reusable deploy workflow
|
||||
# invokes this script from the CONSUMER repo's workspace root with a relative
|
||||
# contract path (e.g. .acdl/contract.yml); the contract must resolve against
|
||||
# contract path (e.g. .nova/contract.yml); the contract must resolve against
|
||||
# the consumer repo, not the platform repo (platform/). Without this, the
|
||||
# `[ -f "$CONTRACT" ]` check below looks for the contract inside the platform
|
||||
# repo and fails (P0 fix — see docs/CONSUMER_GUIDE.md Step 4).
|
||||
@@ -118,10 +118,12 @@ fail() { echo "FAIL: $*" >&2; exit 1; }
|
||||
# state, and the contract-ingestor Lambda in-process. Exits 0 on success.
|
||||
if [ "$LOCAL_TIER" = "1" ]; then
|
||||
[ -n "$CONTRACT" ] || CONTRACT="contracts/microservice.yml"
|
||||
echo "=== ACDL Local Emulating Tier (D-092) ==="
|
||||
echo "=== Nova Local Emulating Tier (D-092) ==="
|
||||
echo "contract: $CONTRACT (no AWS credentials required)"
|
||||
echo ""
|
||||
ACDL_LOCAL_TIER=1 python3 core/local_emulators.py "$CONTRACT" \
|
||||
# Dual-read: set NOVA_LOCAL_TIER (preferred); ACDL_LOCAL_TIER fallback
|
||||
# kept for any unmigrated reader until P5 (removed in P5).
|
||||
NOVA_LOCAL_TIER=1 ACDL_LOCAL_TIER=1 python3 core/local_emulators.py "$CONTRACT" \
|
||||
|| fail "local E2E failed"
|
||||
echo ""
|
||||
echo "=== LOCAL E2E OK ==="
|
||||
@@ -147,7 +149,8 @@ rm -rf "$WORK"; mkdir -p "$TF_DIR"
|
||||
|
||||
echo "=== Step 0: environment onboarding check ==="
|
||||
if [ -n "$ENVIRONMENT_OVERRIDE" ]; then
|
||||
export ACDL_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" || {
|
||||
echo "FAIL: environment not bound — see the onboarding prompt above" >&2
|
||||
exit 1
|
||||
@@ -285,8 +288,11 @@ if [ -z "${AWS_ACCESS_KEY_ID:-}" ] || [ -z "${AWS_SECRET_ACCESS_KEY:-}" ]; then
|
||||
set -a
|
||||
. "$ENV_FILE"
|
||||
set +a
|
||||
export AWS_ACCESS_KEY_ID="$ACDL_AWS_ACCESS_KEY_ID"
|
||||
export AWS_SECRET_ACCESS_KEY="$ACDL_AWS_SECRET_ACCESS_KEY"
|
||||
# G-106 binding: dual-read NOVA_* first, ACDL_* fallback. The .env.secrets
|
||||
# keys are renamed to NOVA_* in P2; the ACDL_* fallback covers operators
|
||||
# who haven't rotated their local .env.secrets yet. Removed in P5.
|
||||
export AWS_ACCESS_KEY_ID="${NOVA_AWS_ACCESS_KEY_ID:-$ACDL_AWS_ACCESS_KEY_ID}"
|
||||
export AWS_SECRET_ACCESS_KEY="${NOVA_AWS_SECRET_ACCESS_KEY:-$ACDL_AWS_SECRET_ACCESS_KEY}"
|
||||
export AWS_DEFAULT_REGION="$AWS_DEFAULT_REGION"
|
||||
fi
|
||||
|
||||
@@ -333,16 +339,17 @@ if [ "$APPLY_ONLY" = "1" ]; then
|
||||
import os, sys
|
||||
sys.path.insert(0, '.')
|
||||
from core.hitl_gates import attest
|
||||
contract_id = os.environ['ACDL_HITL_CONTRACT_ID']
|
||||
env = os.environ['ACDL_HITL_ENV']
|
||||
approver = os.environ.get('ACDL_HITL_APPROVER', '') or 'local-test'
|
||||
from core import env as _envhelper
|
||||
contract_id = _envhelper.get_env('HITL_CONTRACT_ID') or os.environ['NOVA_HITL_CONTRACT_ID']
|
||||
env = _envhelper.get_env('HITL_ENV') or os.environ['NOVA_HITL_ENV']
|
||||
approver = _envhelper.get_env('HITL_APPROVER', '') or 'local-test'
|
||||
ok, reason = attest(contract_id, env, approver)
|
||||
if ok:
|
||||
print(f'HITL PASS: {reason}')
|
||||
else:
|
||||
print(f'HITL BLOCK: {reason}', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
" ACDL_HITL_CONTRACT_ID="$CONTRACT_ID" ACDL_HITL_ENV="$RESOLVED_ENV" ACDL_HITL_APPROVER="$APPROVER" || { echo "FAIL: HITL attestation gate blocked the apply" >&2; exit 1; }
|
||||
" NOVA_HITL_CONTRACT_ID="$CONTRACT_ID" NOVA_HITL_ENV="$RESOLVED_ENV" NOVA_HITL_APPROVER="$APPROVER" ACDL_HITL_CONTRACT_ID="$CONTRACT_ID" ACDL_HITL_ENV="$RESOLVED_ENV" ACDL_HITL_APPROVER="$APPROVER" || { echo "FAIL: HITL attestation gate blocked the apply" >&2; exit 1; }
|
||||
else
|
||||
echo "Environment is dev — autonomous (no HITL gate)."
|
||||
fi
|
||||
@@ -448,16 +455,17 @@ if [ "$RESOLVED_ENV" != "dev" ]; then
|
||||
import os, sys
|
||||
sys.path.insert(0, '.')
|
||||
from core.hitl_gates import attest
|
||||
contract_id = os.environ['ACDL_HITL_CONTRACT_ID']
|
||||
env = os.environ['ACDL_HITL_ENV']
|
||||
approver = os.environ.get('ACDL_HITL_APPROVER', '') or 'local-test'
|
||||
from core import env as _envhelper
|
||||
contract_id = _envhelper.get_env('HITL_CONTRACT_ID') or os.environ['NOVA_HITL_CONTRACT_ID']
|
||||
env = _envhelper.get_env('HITL_ENV') or os.environ['NOVA_HITL_ENV']
|
||||
approver = _envhelper.get_env('HITL_APPROVER', '') or 'local-test'
|
||||
ok, reason = attest(contract_id, env, approver)
|
||||
if ok:
|
||||
print(f'HITL PASS: {reason}')
|
||||
else:
|
||||
print(f'HITL BLOCK: {reason}', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
" ACDL_HITL_CONTRACT_ID="$CONTRACT_ID" ACDL_HITL_ENV="$RESOLVED_ENV" ACDL_HITL_APPROVER="$APPROVER" || { echo "FAIL: HITL attestation gate blocked the promotion" >&2; exit 1; }
|
||||
" NOVA_HITL_CONTRACT_ID="$CONTRACT_ID" NOVA_HITL_ENV="$RESOLVED_ENV" NOVA_HITL_APPROVER="$APPROVER" ACDL_HITL_CONTRACT_ID="$CONTRACT_ID" ACDL_HITL_ENV="$RESOLVED_ENV" ACDL_HITL_APPROVER="$APPROVER" || { echo "FAIL: HITL attestation gate blocked the promotion" >&2; exit 1; }
|
||||
else
|
||||
echo "Environment is dev — autonomous (no HITL gate)."
|
||||
fi
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
#
|
||||
# Usage:
|
||||
# bash scripts/run_regression.sh # run all checks
|
||||
# ACDL_REGRESSION_MILESTONE=v1.10 ACDL_REGRESSION_PHASE=52 \
|
||||
# NOVA_REGRESSION_MILESTONE=v1.10 NOVA_REGRESSION_PHASE=52 \
|
||||
# bash scripts/run_regression.sh # override metadata
|
||||
# (ACDL_REGRESSION_* legacy fallback kept until P5)
|
||||
#
|
||||
# Output:
|
||||
# .ciagent/REGRESSION_REPORT.md human-readable report
|
||||
@@ -17,8 +18,9 @@ set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
echo "=== ACDL Regression VERIFY (D-091) ==="
|
||||
echo "milestone: ${ACDL_REGRESSION_MILESTONE:-v1.10} phase: ${ACDL_REGRESSION_PHASE:-52}"
|
||||
echo "=== Nova Regression VERIFY (D-091) ==="
|
||||
# Dual-read: NOVA_* preferred, ACDL_* fallback (removed in P5).
|
||||
echo "milestone: ${NOVA_REGRESSION_MILESTONE:-${ACDL_REGRESSION_MILESTONE:-v1.10}} phase: ${NOVA_REGRESSION_PHASE:-${ACDL_REGRESSION_PHASE:-52}}"
|
||||
echo ""
|
||||
|
||||
python3 core/regression_verify.py
|
||||
|
||||
@@ -408,14 +408,16 @@ class TestAdapterDedupMergesSameModule:
|
||||
|
||||
|
||||
class TestAdapterRemoteStateKeyOverride:
|
||||
"""P2-2 (v1.14, REQ-139): ACDL_REMOTE_STATE_KEY env var overrides the
|
||||
default 'platform/terraform.tfstate' key in the emitted
|
||||
"""P2-2 (v1.14, REQ-139): NOVA_REMOTE_STATE_KEY env var (P2 renamed from
|
||||
ACDL_REMOTE_STATE_KEY; dual-read NOVA_* preferred, ACDL_* fallback until
|
||||
P5) overrides the default 'platform/terraform.tfstate' key in the emitted
|
||||
data terraform_remote_state block. This is the load-bearing correctness
|
||||
mechanism for the microservice L2 lifecycle (remote state points at the
|
||||
CI VPC, not the platform VPC)."""
|
||||
|
||||
def test_default_remote_state_key(self, tmp_path, monkeypatch):
|
||||
"""When ACDL_REMOTE_STATE_KEY is unset, the default key is used."""
|
||||
"""When NOVA_REMOTE_STATE_KEY is unset, the default key is used."""
|
||||
monkeypatch.delenv("NOVA_REMOTE_STATE_KEY", raising=False)
|
||||
monkeypatch.delenv("ACDL_REMOTE_STATE_KEY", raising=False)
|
||||
stack = {
|
||||
"resources": [
|
||||
@@ -431,9 +433,9 @@ class TestAdapterRemoteStateKeyOverride:
|
||||
assert "platform/terraform.tfstate" in main_tf
|
||||
|
||||
def test_env_override_remote_state_key(self, tmp_path, monkeypatch):
|
||||
"""When ACDL_REMOTE_STATE_KEY is set, the emitted data block uses
|
||||
"""When NOVA_REMOTE_STATE_KEY is set, the emitted data block uses
|
||||
the overridden key (e.g. 'spike/ci-vpc/terraform.tfstate')."""
|
||||
monkeypatch.setenv("ACDL_REMOTE_STATE_KEY", "spike/ci-vpc/terraform.tfstate")
|
||||
monkeypatch.setenv("NOVA_REMOTE_STATE_KEY", "spike/ci-vpc/terraform.tfstate")
|
||||
stack = {
|
||||
"resources": [
|
||||
{"id": "s3", "type": "aws:s3:bucket", "module": "s3@1.0.0", "inputs": {"bucket_name": "test", "region": "us-east-1"}}
|
||||
|
||||
@@ -102,6 +102,8 @@ def test_dr_blocks_on_missing_dr_drill():
|
||||
|
||||
def test_signature_skip_when_key_unset(monkeypatch, capsys):
|
||||
"""D-089: signature verification is skipped when the signing key is unset."""
|
||||
# P2: dual-read — both NOVA_* and ACDL_* must be unset for the skip.
|
||||
monkeypatch.delenv("NOVA_ATTESTATION_SIGNING_KEY_ID", raising=False)
|
||||
monkeypatch.delenv("ACDL_ATTESTATION_SIGNING_KEY_ID", raising=False)
|
||||
artifact = {"timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
||||
"type": "x", "payload": {}, "signature": "sig"}
|
||||
@@ -112,7 +114,7 @@ def test_signature_skip_when_key_unset(monkeypatch, capsys):
|
||||
|
||||
def test_signature_required_when_key_set(monkeypatch):
|
||||
"""When the signing key is set, a missing signature fails."""
|
||||
monkeypatch.setenv("ACDL_ATTESTATION_SIGNING_KEY_ID", "kms-key-id")
|
||||
monkeypatch.setenv("NOVA_ATTESTATION_SIGNING_KEY_ID", "kms-key-id")
|
||||
artifact = {"timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
||||
"type": "x", "payload": {}} # no signature
|
||||
assert _verify_signature(artifact) is False
|
||||
|
||||
@@ -67,10 +67,10 @@ class TestToPcr:
|
||||
|
||||
|
||||
class TestRuleMapTagging:
|
||||
def test_acdl_tag_naming_is_real_rule(self):
|
||||
# D-054 / D-043 closure: ACDL_TAG_NAMING is now a real custom Checkov
|
||||
# rule, not a synthetic SKIPPED record.
|
||||
assert RULE_MAP["ACDL_TAG_NAMING"] == ("tagging-standard", "medium")
|
||||
def test_nova_tag_naming_is_real_rule(self):
|
||||
# D-054 / D-043 closure, P2 rename (REQ-158): NOVA_TAG_NAMING is now
|
||||
# a real custom Checkov rule, not a synthetic SKIPPED record.
|
||||
assert RULE_MAP["NOVA_TAG_NAMING"] == ("tagging-standard", "medium")
|
||||
|
||||
|
||||
class TestAdapt:
|
||||
@@ -100,13 +100,13 @@ class TestAdapt:
|
||||
|
||||
def test_adapt_does_not_emit_synthetic_tag_naming(self, tmp_path):
|
||||
# D-043 closure: adapt() no longer appends a synthetic SKIPPED
|
||||
# ACDL_TAG_NAMING record. The custom Checkov rule (loaded via
|
||||
# NOVA_TAG_NAMING record. The custom Checkov rule (loaded via
|
||||
# --external-checks-dir) produces real PASS/FAIL records instead.
|
||||
data = self._sample_checkov_json()
|
||||
f = tmp_path / "checkov.json"
|
||||
f.write_text(json.dumps(data))
|
||||
results = adapt(str(f), "c-1")
|
||||
tag = [r for r in results if r["ruleId"] == "ACDL_TAG_NAMING"]
|
||||
tag = [r for r in results if r["ruleId"] == "NOVA_TAG_NAMING"]
|
||||
assert tag == [] # no synthetic record
|
||||
|
||||
def test_adapt_has_passed_and_failed(self, tmp_path):
|
||||
|
||||
@@ -78,4 +78,6 @@ def test_run_platform_sh_has_environment_flag():
|
||||
text = (ROOT / "scripts" / "run_platform.sh").read_text()
|
||||
assert "--environment" in text
|
||||
assert "ENVIRONMENT_OVERRIDE" in text
|
||||
assert "ACDL_ENVIRONMENT_OVERRIDE" in text
|
||||
# P2 (REQ-159): NOVA_* preferred; ACDL_* kept as dual-read fallback until P5.
|
||||
assert "NOVA_ENVIRONMENT_OVERRIDE" in text
|
||||
assert "ACDL_ENVIRONMENT_OVERRIDE" in text # legacy fallback, removed in P5
|
||||
@@ -0,0 +1,56 @@
|
||||
"""Unit tests for the dual-read env helper (core/env.py, D-108, REQ-159).
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from core import env
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _isolate_env(monkeypatch):
|
||||
"""Ensure no ACDL_*/NOVA_* leakage between tests."""
|
||||
for key in list(__import__("os").environ):
|
||||
if key.startswith(("ACDL_", "NOVA_")):
|
||||
monkeypatch.delenv(key, raising=False)
|
||||
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.
|
||||
monkeypatch.setenv("NOVA_AWS_ACCOUNT_ID", "")
|
||||
monkeypatch.setenv("ACDL_AWS_ACCOUNT_ID", "acdl-value")
|
||||
assert env.get_env("AWS_ACCOUNT_ID") == "acdl-value"
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
The lifecycle scripts (run_lifecycle_test.sh, run_lifecycle_destroy.sh,
|
||||
run_l2_lifecycle_test.sh, run_l2_lifecycle_destroy.sh) wrap run_platform.sh.
|
||||
REQ-134 (v1.12) adds the ACDL_LIFECYCLE_MODE env var: default "plan" runs
|
||||
`run_platform.sh --plan-only` (fast, no AWS mutation); "full" runs the real
|
||||
`--apply`/`--destroy` against live AWS.
|
||||
REQ-134 (v1.12) adds the NOVA_LIFECYCLE_MODE env var (P2 renamed from
|
||||
ACDL_LIFECYCLE_MODE, dual-read NOVA_* preferred / ACDL_* fallback until
|
||||
P5): default "plan" runs `run_platform.sh --plan-only` (fast, no AWS
|
||||
mutation); "full" runs the real `--apply`/`--destroy` against live AWS.
|
||||
|
||||
These tests verify the dispatch logic offline by inspecting script content
|
||||
(running the scripts end-to-end requires AWS credentials in full mode).
|
||||
@@ -32,12 +33,14 @@ class TestLifecycleModeFlag:
|
||||
|
||||
@pytest.mark.parametrize("script", SCRIPTS)
|
||||
def test_script_reads_acdl_lifecycle_mode(self, script):
|
||||
"""Every lifecycle script reads ACDL_LIFECYCLE_MODE with a 'plan' default."""
|
||||
"""Every lifecycle script reads NOVA_LIFECYCLE_MODE (dual-read with
|
||||
ACDL_LIFECYCLE_MODE fallback) with a 'plan' default."""
|
||||
src = _read(script)
|
||||
# The default must be 'plan' (the speed-up default). The scripts use
|
||||
# an intermediate LIFECYCLE_MODE var sourced from ACDL_LIFECYCLE_MODE.
|
||||
assert "${ACDL_LIFECYCLE_MODE:-plan}" in src, \
|
||||
f"{script} must read ACDL_LIFECYCLE_MODE defaulting to 'plan'"
|
||||
# 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'"
|
||||
assert "LIFECYCLE_MODE=" in src, \
|
||||
f"{script} must assign LIFECYCLE_MODE from the env var"
|
||||
|
||||
|
||||
@@ -179,11 +179,11 @@ def test_local_lambda_stub_rejects_missing_field(tmp_path):
|
||||
def test_run_local_e2e_microservice():
|
||||
"""Headline E2E: contract -> resolver -> adapter -> local S3 backend
|
||||
-> local ECS (HTTP 200) -> flat-file outbox -> local Lambda. No AWS."""
|
||||
os.environ["ACDL_LOCAL_TIER"] = "1"
|
||||
os.environ["NOVA_LOCAL_TIER"] = "1"
|
||||
try:
|
||||
result = le.run_local_e2e("contracts/microservice.yml")
|
||||
finally:
|
||||
os.environ.pop("ACDL_LOCAL_TIER", None)
|
||||
os.environ.pop("NOVA_LOCAL_TIER", None); os.environ.pop("ACDL_LOCAL_TIER", None)
|
||||
assert result["tier"] == "local-emulator"
|
||||
assert result["backend"] == "local"
|
||||
assert result["ecs"] is not None
|
||||
@@ -196,11 +196,11 @@ def test_run_local_e2e_microservice():
|
||||
def test_run_local_e2e_static_assets():
|
||||
"""Static-assets stack has no ECS service; the local E2E must still
|
||||
complete (ecs=None) and the outbox chain + Lambda stub must pass."""
|
||||
os.environ["ACDL_LOCAL_TIER"] = "1"
|
||||
os.environ["NOVA_LOCAL_TIER"] = "1"
|
||||
try:
|
||||
result = le.run_local_e2e("contracts/static-assets.yml")
|
||||
finally:
|
||||
os.environ.pop("ACDL_LOCAL_TIER", None)
|
||||
os.environ.pop("NOVA_LOCAL_TIER", None); os.environ.pop("ACDL_LOCAL_TIER", None)
|
||||
assert result["tier"] == "local-emulator"
|
||||
assert result["ecs"] is None # no ECS service in this stack
|
||||
assert result["outbox_chain_verified"] is True
|
||||
@@ -209,9 +209,9 @@ def test_run_local_e2e_static_assets():
|
||||
|
||||
def test_is_local_tier_flag():
|
||||
assert le.is_local_tier() is False
|
||||
os.environ["ACDL_LOCAL_TIER"] = "1"
|
||||
os.environ["NOVA_LOCAL_TIER"] = "1"
|
||||
try:
|
||||
assert le.is_local_tier() is True
|
||||
finally:
|
||||
os.environ.pop("ACDL_LOCAL_TIER", None)
|
||||
os.environ.pop("NOVA_LOCAL_TIER", None); os.environ.pop("ACDL_LOCAL_TIER", None)
|
||||
assert le.is_local_tier() is False
|
||||
@@ -376,7 +376,9 @@ class TestCli:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestKmsFailLoud:
|
||||
"""P1-3: SSM publisher must fail loud when ACDL_KMS_KEY_ID is unset."""
|
||||
"""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,
|
||||
ACDL_* fallback until P5)."""
|
||||
|
||||
def test_kms_unset_raises(self, monkeypatch):
|
||||
from moto import mock_aws
|
||||
@@ -385,11 +387,14 @@ class TestKmsFailLoud:
|
||||
monkeypatch.setenv("AWS_DEFAULT_REGION", "us-east-1")
|
||||
monkeypatch.setenv("AWS_ACCESS_KEY_ID", "testing")
|
||||
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_ALLOW_DEFAULT_KMS", raising=False)
|
||||
monkeypatch.delenv("ACDL_ALLOW_DEFAULT_KMS", raising=False)
|
||||
|
||||
with mock_aws():
|
||||
with pytest.raises(RuntimeError, match="ACDL_KMS_KEY_ID is not set"):
|
||||
with pytest.raises(RuntimeError, match="NOVA_KMS_KEY_ID is not set"):
|
||||
publish_to_ssm({"vpc_id": "vpc-1"}, "dev", "c-1")
|
||||
|
||||
def test_kms_unset_allow_default_kms_escape_hatch(self, monkeypatch):
|
||||
@@ -399,8 +404,9 @@ class TestKmsFailLoud:
|
||||
monkeypatch.setenv("AWS_DEFAULT_REGION", "us-east-1")
|
||||
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.setenv("ACDL_ALLOW_DEFAULT_KMS", "1")
|
||||
monkeypatch.setenv("NOVA_ALLOW_DEFAULT_KMS", "1")
|
||||
|
||||
with mock_aws():
|
||||
ssm = boto3.client("ssm", region_name="us-east-1")
|
||||
@@ -415,8 +421,8 @@ class TestKmsFailLoud:
|
||||
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", "arn:aws:kms:us-east-1:123:key/abc")
|
||||
monkeypatch.setenv("ACDL_ALLOW_DEFAULT_KMS", "1")
|
||||
monkeypatch.setenv("NOVA_KMS_KEY_ID", "arn:aws:kms:us-east-1:123:key/abc")
|
||||
monkeypatch.setenv("NOVA_ALLOW_DEFAULT_KMS", "1")
|
||||
|
||||
from core.output_publisher import _kms_key_id
|
||||
assert _kms_key_id() == "arn:aws:kms:us-east-1:123:key/abc"
|
||||
|
||||
@@ -310,7 +310,7 @@ class TestDeployWorkflowConformance:
|
||||
wf = _load_workflow(".gitea/workflows/deploy.yml")
|
||||
inputs = wf["on"]["workflow_call"]["inputs"]
|
||||
assert "contract" in inputs
|
||||
assert inputs["contract"]["default"] == ".acdl/contract.yml"
|
||||
assert inputs["contract"]["default"] == ".nova/contract.yml"
|
||||
|
||||
def test_deploy_workflow_has_mode_input(self):
|
||||
wf = _load_workflow(".gitea/workflows/deploy.yml")
|
||||
@@ -646,20 +646,20 @@ class TestModulesLifecyclePipeline:
|
||||
assert set(inputs["lifecycle_mode"].get("options", [])) == {"plan", "full"}
|
||||
|
||||
def test_lifecycle_job_passes_mode_env_to_steps(self):
|
||||
"""The lifecycle job sets ACDL_LIFECYCLE_MODE env so scripts dispatch
|
||||
"""The lifecycle job sets NOVA_LIFECYCLE_MODE env so scripts dispatch
|
||||
to plan-only by default, full on override."""
|
||||
wf = _load_workflow(".gitea/workflows/modules-lifecycle.yml")
|
||||
env = wf["jobs"]["lifecycle"].get("env", {})
|
||||
assert "ACDL_LIFECYCLE_MODE" in env
|
||||
assert "NOVA_LIFECYCLE_MODE" in env
|
||||
# The expression must resolve to 'plan' when no input/var is set.
|
||||
assert "plan" in env["ACDL_LIFECYCLE_MODE"]
|
||||
assert "plan" in env["NOVA_LIFECYCLE_MODE"]
|
||||
|
||||
def test_l2_lifecycle_job_passes_mode_env_to_steps(self):
|
||||
"""The L2 lifecycle job also sets ACDL_LIFECYCLE_MODE env."""
|
||||
"""The L2 lifecycle job also sets NOVA_LIFECYCLE_MODE env."""
|
||||
wf = _load_workflow(".gitea/workflows/modules-lifecycle.yml")
|
||||
env = wf["jobs"]["l2-lifecycle"].get("env", {})
|
||||
assert "ACDL_LIFECYCLE_MODE" in env
|
||||
assert "plan" in env["ACDL_LIFECYCLE_MODE"]
|
||||
assert "NOVA_LIFECYCLE_MODE" in env
|
||||
assert "plan" in env["NOVA_LIFECYCLE_MODE"]
|
||||
|
||||
def test_ci_vpc_apply_skipped_in_plan_mode(self):
|
||||
"""The CI VPC apply job is skipped in plan mode (nothing is applied)."""
|
||||
|
||||
@@ -29,6 +29,7 @@ def test_route_halt_publishes_to_sns_when_arn_set(monkeypatch):
|
||||
|
||||
def test_route_halt_falls_back_to_stderr_when_arn_unset(monkeypatch, capsys):
|
||||
"""Without ACDL_SOD_HALT_TOPIC_ARN, a stderr emission occurs."""
|
||||
monkeypatch.delenv("NOVA_SOD_HALT_TOPIC_ARN", raising=False)
|
||||
monkeypatch.delenv("ACDL_SOD_HALT_TOPIC_ARN", raising=False)
|
||||
# Mock outbox_writer.write_event to avoid AWS calls.
|
||||
with mock.patch("core.outbox_writer.write_event", return_value=None):
|
||||
@@ -40,6 +41,7 @@ def test_route_halt_falls_back_to_stderr_when_arn_unset(monkeypatch, capsys):
|
||||
|
||||
def test_route_halt_outbox_fallback_writes_event(monkeypatch):
|
||||
"""Without the SNS ARN, the outbox fallback writes a SEPARATION_OF_DUTIES_VIOLATION event."""
|
||||
monkeypatch.delenv("NOVA_SOD_HALT_TOPIC_ARN", raising=False)
|
||||
monkeypatch.delenv("ACDL_SOD_HALT_TOPIC_ARN", raising=False)
|
||||
with mock.patch("core.outbox_writer.write_event") as mock_write:
|
||||
route_halt_artifact("contract-789", "sod violation", oncall_client=None)
|
||||
|
||||
@@ -106,9 +106,11 @@ class TestCreateStateBackend:
|
||||
"""terraform/bootstrap/create_state_backend.py — mock boto3."""
|
||||
|
||||
def test_state_bucket_name_construction(self, monkeypatch):
|
||||
"""The state bucket name is derived from ACDL_AWS_ACCOUNT_ID."""
|
||||
monkeypatch.setenv("ACDL_AWS_ACCOUNT_ID", "123456789012")
|
||||
account_id = os.environ.get("ACDL_AWS_ACCOUNT_ID", "581513795199")
|
||||
"""The state bucket name is derived from NOVA_AWS_ACCOUNT_ID
|
||||
(P2 renamed from ACDL_AWS_ACCOUNT_ID; the bucket name acdl-tfstate-*
|
||||
stays until P4, REQ-163)."""
|
||||
monkeypatch.setenv("NOVA_AWS_ACCOUNT_ID", "123456789012")
|
||||
account_id = os.environ.get("NOVA_AWS_ACCOUNT_ID", "581513795199")
|
||||
state_bucket = f"acdl-tfstate-{account_id}-us-east-1"
|
||||
assert state_bucket == "acdl-tfstate-123456789012-us-east-1"
|
||||
|
||||
|
||||