Files
acdl/docs/consumer-guide.md
T
Jon Chery 0d2cbdb423 feat(P1): remove gitea/gitlab from synced files + simplify docs (REQ-230,231,232)
Genericize forge-detection code: gitea→forge/generic_forge, GITEA_ACTOR→FORGE_ACTOR.
Drop .gitea byte-identity test assertions (keep GitHub-side + contract conformance).
Add test_no_forge_mentions.py guard test (REQ-230).
Delete completed migration docs (NOVA_MIGRATION.md, NOVA_AWS_MIGRATION.md).
Move NO_HUMANS_THESIS.md to .ciagent/ (internal artifact).
Strip ciagent-internal provenance from synced docs (REQ-/D-/P-/CAP- IDs,
milestone headers, .ciagent/PROJECT.md citations).
Trim README.md (reusable deploy section, local key rotation paragraph).
Fix version-tag drift (@v1.13→@v1.19, acdl/→nova/).

---ci---
project: acdl
phase: 1
milestone: v1.20
status: execute
requirements: [REQ-230, REQ-231, REQ-232]
---/ci---
2026-08-07 18:20:29 +00:00

19 KiB

Consumer Guide — Declare intent, deploy to AWS

This guide walks a consumer through creating their pipeline and defining a 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 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 Nova platform owns the pipelines, modules, engine adapter, and evidence stream.

You do not write infrastructure modules, workflow YAML, or adapter code. You write a contract YAML file and the platform does the rest. Your repository contains only your application code, your contracts, and your CI definitions.

flowchart LR
    A["your repo<br/>(app code + contracts + CI definitions)"] -->|uses: nova/.github/workflows/deploy.yml@v1.19| B
    B["platform runners<br/>(modules + pipelines + adapters + schemas)"] -->|contract -&gt; resolver -&gt; stack -&gt; adapter<br/>-&gt; security checks -&gt; infrastructure plan -&gt; policy checks<br/>-&gt; confidence -&gt; apply -&gt; evidence event| C
    C["your resources in AWS"]

Versioning the uses: reference

The central deployment pipeline is always versioned with floating MAJOR and MINOR tags (e.g. nova/pipelines/contract.yml@v1.19). Version constraints cannot be expressed inside the contract, so the tag in uses: is the only immutability lever a consumer has. See Versioning for the full rationale.

Unversioned references are discouraged. Do not use @main or a bare nova/pipelines/contract.yml.

Prerequisites

These are the only prerequisites for a consumer repo. You do not need an AWS account, infrastructure tooling, or a runner key — those are platform-managed. See Environments.

  • A consumer GitHub repository for your application code + contracts.
  • A platform-managed environment bound to your repo. The platform team provisions the AWS account, network, state backend, and IAM role. If no environment is bound, your first pipeline run emits a friendly onboarding prompt. See Environments.
  • Authorization to reference the central pipeline. Onboarding grants your repo the right to uses: nova/.github/workflows/deploy.yml@v1.19. Contact the platform team if you have not been onboarded.

Step 1 — Create a consumer repo

Create a repository for your application. The top level holds your app code; your contract lives at .nova/contract.yml. Example for a static site:

my-static-site/
  index.html
  assets/
    style.css
    logo.png
  .nova/
    contract.yaml
  .github/
    workflows/
      deploy.yml

Example for a microservice:

my-microservice/
  app.py
  Dockerfile
  .nova/
    contract.yaml
  .github/
    workflows/
      deploy.yml

Your app code lives at the top level. Your contract lives at .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 Nova deployment workflow with a versioned tag (floating MAJOR + MINOR):

jobs:
  deploy:
    uses: nova/.github/workflows/deploy.yml@v1.19
    with:
      contract: .nova/contract.yml
      environment: dev

The versioned tag is the only immutability lever — the consumer's CI workflow pins the platform version. The contract itself no longer carries a uses: field; the version pin lives in the CI workflow reference.

Step 3 — Define the contract

Write .nova/contract.yml. The static-assets example:

environment: dev
id: assets
infrastructure:
  static-assets:
    inputs:
      bucket_name: my-static-site-assets
      region: us-east-1
    version: 1.0.0
name: static-assets

A microservice example:

environment: dev
id: msvc
infrastructure:
  microservice:
    inputs:
      env:
        LOG_LEVEL: info
      image: my-registry/my-microservice:latest
      port: 8080
    version: 1.0.0
name: microservice

Contract fields

Field Type Required Description
uses string yes Reference to the central deployment pipeline, versioned with a floating MAJOR+MINOR tag (e.g. nova/pipelines/contract.yml@v1.19). Bare or @main references are discouraged. See Versioning.
module string yes Module name from the registry — any primitive or module (e.g. static-assets, microservice, s3). See the module catalog.
environment string yes The platform-managed environment to deploy to (e.g. dev). See Environments.
inputs object yes Module-specific inputs (see the module's README).

Module inputs

Each module declares its inputs in its interface.json (primitives) or composition.json (modules). Consult the module catalog for the full list, or read the module's own README under modules/l1/<name>/ or modules/l2/<name>/. Each module also has an examples/ directory with validated consumer contract examples (simple.yaml + complex.yaml

  • variation files) that demonstrate real usage — see the module's ## Examples section.

The contract is validated against the contract schema. An invalid contract (missing field, unknown module, wrong type) fails at the validate-contract stage with a clear error.

Step 4 — Run the pipeline

You do not run platform scripts locally for the happy path. The central deploy workflow is a reusable workflow that the platform runners fetch and execute for you.

The consumer CI definition

Add a thin workflow file to your repo that invokes the reusable Nova deploy workflow with a versioned tag (.github/workflows/deploy.yml):

name: deploy
on:
  push:
    branches: [main]
jobs:
  deploy:
    uses: nova/.github/workflows/deploy.yml@v1.19
    with:
      contract: .nova/contract.yml

That is the entire consumer-side workflow. When you push to main:

  1. The platform runner resolves uses: nova/.github/workflows/deploy.yml@v1.19 to the reusable workflow at the pinned tag.
  2. A platform-provided runner checks out your repo.
  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 .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 flags are platform-side modes visible in the pipeline logs; you do not pass them yourself — the reusable workflow selects the mode based on the environment in your contract (dev = full apply; higher environments hold for attestation).

Local validation (optional)

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 Credentials override model: a static key in .env.secrets (gitignored) is rotated out of band by you — the platform guarantees daily rotation for platform-runner runs, not for locally-held copies.

bash scripts/run_platform.sh --check-only path/to/your/.nova/contract.yml

Step 5 — What the pipeline does

Each stage of the central deployment pipeline:

flowchart TD
    S1["validate-contract<br/>schema check"] --> S2
    S2["resolve-stack<br/>contract -&gt; Target Stack"] --> S3
    S3["security checks<br/>(adapter)"] --> S4
    S4["infrastructure plan<br/>(adapter compiles the stack)"] --> S5
    S5["policy checks<br/>(adapter -&gt; PolicyCheckResult)"] --> S6
    S6["confidence<br/>score + band (dev &gt;= 0.50)"] --> S7
    S7["evidence event<br/>to the audit outbox"] --> S8
    S8["infrastructure apply<br/>(dev only)"]
  1. validate-contract — validates your contract YAML against the contract schema. Fails fast on missing fields, unknown modules, or wrong types.
  2. resolve-stack — the contract resolver resolves your contract to a Target Stack instance. It loads the module's pattern, expands its children, wires your contract inputs to the children's inputs, and emits a stack JSON instance.
  3. security checks (adapter) — security checks run on the resolved stack before any infrastructure is planned.
  4. infrastructure plan (adapter) — the engine adapter compiles the stack to an infrastructure plan. You see the plan in your run logs.
  5. policy checks (adapter) — policy checks run on the plan. The results are normalized to PolicyCheckResult records. Each result has a severity, rule ID, and pass/fail status.
  6. confidence — the confidence signal computes a score from 6 inputs (policy, validation, freshness, source, history, NFRs). For dev, the threshold is ≥ 0.50. If the band is pass, the pipeline proceeds.
  7. evidence event — a hash-chained evidence event is written to the audit outbox.
  8. infrastructure apply (dev only) — the infrastructure plan is applied, creating the resources in your AWS account. An evidence event for the apply is recorded.

Step 6 — What gets created

After a successful dev run, the resources declared by your module's pattern exist in your AWS account, and an evidence event is recorded.

For the static-assets example:

  • An S3 bucket named my-static-site-assets in us-east-1 with versioning enabled.
  • A CloudFront distribution with the S3 bucket as the origin (via Origin Access Control) and HTTPS redirection.
  • A WAFv2 Web ACL (CloudFront-scoped) associated with the distribution.
  • An evidence event in the audit outbox with the contract ID, stack name (static-assets), confidence score, and band.
  • A confidence band of pass (score ≥ 0.50 for dev).

For other modules, consult the module's README (modules/l1/<name>/README.md or modules/l2/<name>/README.md) for the exact resources created.

Step 7 — Upload your content (static-assets example)

The platform provisions the infrastructure; you upload your content. For the static-assets module:

aws s3 sync ./assets s3://my-static-site-assets/ --acl public-read

For a microservice, the platform provisions the ECS service and ALB; you push your container image to the ECR repo the platform created.

Step 8 — Promote to qa / prod

Change environment in your contract (the infrastructure stays the same):

id: assets
name: static-assets
environment: qa   # QA attestation + confidence >= 0.75
infrastructure:
  static-assets:
    version: "1.0.0"
    inputs: { ... }

Higher environments require human attestation (a platform-runner deployment approval) and higher confidence thresholds. See Environments for the full table.

Step 9 — Compliance extensions

Each module lists compliance extension points for the future compliance milestone (GDPR, SOX, SOC2, DORA). See each module's README under modules/l1/<name>/README.md or modules/l2/<name>/README.md for the per-module extension points. Common examples:

  • KMS key — shared encryption key for SSE.
  • S3 access logs — access logging to a separate audit bucket.
  • Object Lock — 7-year immutable retention for evidence.
  • Public access block — prevent data exfiltration.

Reference

Resource Path Description
Central deployment pipeline contract pipelines/contract.yml The pipeline stages your contract references.
Reusable deploy workflow .github/workflows/deploy.yml The workflow your repo invokes via uses:.
Contract schema schemas/contract.schema.json JSON Schema for consumer contracts.
Stack schema schemas/stack.schema.json JSON Schema for the resolved stack instance.
Module catalog modules/ All primitives and modules.
Sample contract contracts/static-assets.yaml The reference example contract (uses @v1.19).
Sample contract contracts/microservice.yaml The microservice example contract (uses @v1.19).
Module examples modules/<name>/examples/ Validated per-module example contracts (simple.yaml + complex.yaml).
Contract resolver core/contract_resolver.py Resolves contracts to stack instances.
Angine adapter adapters/terraform/adapter.py Compiles stack instances to infrastructure.
Platform pipeline runner scripts/run_platform.sh The pipeline runner (platform-side; consumers do not invoke it directly).
Environments environments/ Platform-managed environments + onboarding.
Versioning pipeline/versioning The uses: tag + module versioning.
Platform README README.md How the platform works + how to run the platform repo locally.
Credentials & zero-trust README.md#credentials--zero-trust The OIDC/ABAC default + static-key override model.

Decommissioning a stack

When a consumer needs to tear down a deployed stack, the platform provides a decommission mode on the same deploy pipeline. The decommission process is a 2-step pipeline with HITL SRE gates to prevent accidental destruction:

  1. Request a change request (CR): Contact the platform team to create a change request in the platform CMDB (DynamoDB nova-change-requests table). The CR must be approved before decommission can proceed. The CR includes the consumer repo, contract ID, and the reason for decommission.

  2. Trigger decommission: Update the consumer's deploy workflow call to use mode: decommission with the changeRequestId input:

    uses: nova/.github/workflows/deploy.yml@v1.19
    with:
      contract: .nova/contract.yml
      mode: decommission
      changeRequestId: "CHG0678912"
    
  3. Step 1 — Disable deletion protection (HITL SRE gate): The pipeline validates the CR ID against the CMDB (status must be approved). Then it resolves the contract with deletion_protection: false injected into all resources and runs terraform plan + terraform apply. This removes the prevent_destroy lifecycle meta-argument from all resources. An SRE must approve this step via the GitHub environment decommission-gate-sre.

  4. Step 2 — Zero counts + destroy (HITL SRE gate): The pipeline applies decommission_transform which sets all scalable counts to 0 (desired_count=0, min_capacity=0, max_capacity=0) and deletion_protection=false on all resources. Then it runs terraform plan + terraform apply which destroys all resources (now that deletion protection is off and counts are zeroed). A second SRE must approve this step via the GitHub environment decommission-destroy-sre.

  5. Confirmation: The pipeline confirms the stack is destroyed (terraform state is empty for the stack).

What happens to the per-stack CMK?

The per-stack CMK is not immediately destroyed — it enters a deletion window (default 30 days, configurable via the deletion_window_days input). This ensures any encrypted data can still be decrypted during the deletion window if needed. The CMK is permanently deleted after the window expires.

What happens to the uptime monitoring?

The uptime monitoring stack (deployed with separate state) is not automatically destroyed by the decommission. It must be destroyed separately (or left running to monitor the decommissioned stack's endpoints going dark).

Per-environment deployment

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 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. .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:

environment: qa
id: assets
infrastructure:
  static-assets:
    inputs:
      bucket_name: acdl-${env.environment}-${contract.id}-${env.account_id}-${env.region}
      region: ${env.region}
    version: 1.0.0
name: static-assets

Shape 2 — single contract + environment workflow input: the reusable deploy workflow (nova/.github/workflows/deploy.yml@v1.19) declares an environment input. When non-empty, it overrides the contract's environment field at load time (before interpolation), so the same contract can be promoted by passing a different environment:

# .github/workflows/deploy-qa.yml  (caller workflow)
on: workflow_dispatch:
  inputs:
    approve_qa:
      description: "Set to true to approve the QA promotion"
      type: boolean
      required: true
jobs:
  deploy-qa:
    uses: nova/.github/workflows/deploy.yml@v1.19
    with:
      environment: qa
      contract: .nova/contract.yml

One job per environment

A consumer repo's .github/workflows/ directory has one caller workflow per environment:

File Environment Gate
deploy-dev.yml dev autonomous (no gate, confidence ≥ 0.50)
deploy-qa.yml qa QA HITL (approve_qa workflow_dispatch input; github.actor is the approver of record)
deploy-prod.yml prod SRE HITL (approve_prod; separation-of-duties enforced)
deploy-dr.yml dr SRE HITL (approve_dr)

Promotion = running the matching job. No environment: field editing. The approver identity is recorded to the DynamoDB outbox (approver_qa / approver_prod / approver_dr) and the separation-of- duties check blocks a prod promotion when approver_qa == approver_prod (see core/hitl_matrix_design.md).

Interpolation reference

Token Resolves to Example
${env.environment} the environment name (dev/qa/prod/dr) qa
${env.region} the environment's AWS region us-east-1
${env.account_id} the environment's AWS account id 123456789012
${env.state_backend.bucket} the environment's state bucket nova-qa-state
${env.network.vpc_cidr} the environment's VPC CIDR 10.1.0.0/16
${contract.id} the contract's operational acronym assets
${contract.environment} the contract's environment field qa
${contract.inputs.<name>} a contract input value (as declared)

Unknown tokens raise ValueError (fail loud). Expansion is recursive (nested map/list values expand too).