# 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 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 referencing `uses:` the central pipeline definitions. The consumer declares a **contract** (which module, which environment, which inputs); the ACDL platform owns the pipelines, modules, substrate 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. ```mermaid flowchart LR A["your repo
(app code + contracts + CI definitions)"] -->|uses: acdl/.github/workflows/deploy.yml@v1.6| B B["platform runners
(modules + pipelines + adapters + schemas)"] -->|contract -> resolver -> stack -> adapter
-> security checks -> infrastructure plan -> policy checks
-> confidence -> apply -> 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. `acdl/pipelines/deploy.yaml@v1.6`). Version constraints cannot be expressed inside the contract, so the tag in `uses:` is the only immutability lever a consumer has. See [Versioning](pipeline/versioning) for the full rationale. **Unversioned references are discouraged.** Do not use `@main` or a bare `acdl/pipelines/deploy.yaml`. ## 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](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](environments/). - **Authorization to reference the central pipeline.** Onboarding grants your repo the right to `uses: acdl/.github/workflows/deploy.yml@v1.6`. 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 `.acdl/contract.yaml`. Example for a static site: ``` my-static-site/ index.html assets/ style.css logo.png .acdl/ contract.yaml .github/ workflows/ deploy.yml ``` Example for a microservice: ``` my-microservice/ app.py Dockerfile .acdl/ contract.yaml .github/ workflows/ deploy.yml ``` Your app code lives at the top level. Your contract lives at `.acdl/contract.yaml` regardless of the module you deploy. Your CI definition lives at `.github/workflows/deploy.yml`. ## Step 2 — Reference the central pipeline In your contract YAML, declare `uses:` pointing at the central ACDL deployment pipeline with a **versioned tag** (floating MAJOR + MINOR): ```yaml uses: acdl/pipelines/deploy.yaml@v1.6 ``` This tells the platform to run the standard deployment pipeline: validate-contract → resolve-stack → security checks → infrastructure plan → policy checks → confidence → evidence event → apply. ## Step 3 — Define the contract Write `.acdl/contract.yaml`. The `static-assets` example: ```yaml uses: acdl/pipelines/deploy.yaml@v1.6 module: static-assets environment: dev inputs: bucket_name: my-static-site-assets region: us-east-1 ``` A `microservice` example: ```yaml uses: acdl/pipelines/deploy.yaml@v1.6 module: microservice environment: dev inputs: image: my-registry/my-microservice:latest port: 8080 env: LOG_LEVEL: info ``` ### Contract fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `uses` | string | yes | Reference to the central deployment pipeline, **versioned** with a floating MAJOR+MINOR tag (e.g. `acdl/pipelines/deploy.yaml@v1.6`). Bare or `@main` references are discouraged. See [Versioning](pipeline/versioning). | | `module` | string | yes | Module name from the registry — any primitive or module (e.g. `static-assets`, `microservice`, `s3`). See the [module catalog](modules/). | | `environment` | string | yes | The platform-managed environment to deploy to (e.g. `dev`). See [Environments](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](modules/) for the full list, or read the module's own README under `modules/l1//` or `modules/l2//`. 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 ACDL deploy workflow with a **versioned tag** (`.github/workflows/deploy.yml`): ```yaml name: deploy on: push: branches: [main] jobs: deploy: uses: acdl/.github/workflows/deploy.yml@v1.6 with: contract: .acdl/contract.yaml ``` That is the entire consumer-side workflow. When you push to `main`: 1. The platform runner resolves `uses: acdl/.github/workflows/deploy.yml@v1.6` 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 — 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.yaml`. 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 ACDL 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](../README.md#credentials--zero-trust) 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 bash scripts/run_platform.sh --check-only path/to/your/.acdl/contract.yaml ``` ## Step 5 — What the pipeline does Each stage of the central deployment pipeline: ```mermaid flowchart TD S1["validate-contract
schema check"] --> S2 S2["resolve-stack
contract -> Target Stack"] --> S3 S3["security checks
(adapter)"] --> S4 S4["infrastructure plan
(adapter compiles the stack)"] --> S5 S5["policy checks
(adapter -> PolicyCheckResult)"] --> S6 S6["confidence
score + band (dev >= 0.50)"] --> S7 S7["evidence event
to the audit outbox"] --> S8 S8["infrastructure apply
(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 substrate 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//README.md` or `modules/l2//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: ```bash 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 (keeping the same versioned `uses:`): ```yaml uses: acdl/pipelines/deploy.yaml@v1.6 environment: qa # QA attestation + confidence >= 0.75 ``` Higher environments require human attestation (a platform-runner deployment approval) and higher confidence thresholds. See [Environments](environments/) for the full table. ## Step 9 — Compliance extensions Each module lists compliance extension points for the future compliance milestone (GDPR, SOX, SOC2, HIPAA, DORA). See each module's README under `modules/l1//README.md` or `modules/l2//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/deploy.yaml` | 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/](modules/) | All primitives and modules. | | Sample contract | `contracts/static-assets.yaml` | The reference example contract (uses `@v1.6`). | | Sample contract | `contracts/microservice.yaml` | The microservice example contract (uses `@v1.6`). | | Module examples | `modules//examples/` | Validated per-module example contracts (`simple.yaml` + `complex.yaml`). | | Contract resolver | `core/contract_resolver.py` | Resolves contracts to stack instances. | | Substrate 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/](environments/) | Platform-managed environments + onboarding. | | Versioning | [pipeline/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 `acdl-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: ```yaml uses: acdl/.github/workflows/deploy.yml@v1.8 with: contract: .acdl/contract.yaml mode: decommission changeRequestId: "CR-2026-001" ``` 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 ACDL 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. `.acdl/static-assets.dev.yaml`, `.acdl/static-assets.qa.yaml`, …). Each sets `environment:` to its own name and uses interpolation so env-specific values differ automatically: ```yaml # .acdl/static-assets.qa.yaml uses: acdl/pipelines/deploy.yaml@v1.9 module: static-assets environment: qa inputs: bucket_name: acdl-${env.environment}-${contract.module}-${env.account_id}-${env.region} region: ${env.region} ``` **Shape 2 — single contract + `environment` workflow input:** the reusable deploy workflow (`acdl/.github/workflows/deploy.yml@v1.9`) 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: ```yaml # .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: acdl/.github/workflows/deploy.yml@v1.9 with: environment: qa contract: .acdl/contract.yaml ``` ### 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 | `acdl-qa-state` | | `${env.network.vpc_cidr}` | the environment's VPC CIDR | `10.1.0.0/16` | | `${contract.module}` | the contract's module name | `static-assets` | | `${contract.environment}` | the contract's environment field | `qa` | | `${contract.inputs.}` | a contract input value | (as declared) | Unknown tokens raise `ValueError` (fail loud). Expansion is recursive (nested map/list values expand too).