# 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
L2 modules in the registry; `static-asset` is the worked example, but every
step applies to `microservice` and any future L2 composition.
## 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, Terraform adapter, and evidence stream.
You do not write Terraform, workflow YAML, or adapter code. You write a
contract YAML file and the platform does the rest. Your repository contains
only your application code and that one contract.
```mermaid
flowchart LR
A["your repo
(app code + contract.yaml)"] -->|uses: acdl/.gitea/workflows/deploy.yml@v1.4| B
B["ACDL platform runners
(modules/ + pipelines/ + adapters/ + schemas/)"] -->|contract -> resolver -> stack -> adapter
-> terraform plan -> Checkov -> confidence
-> apply -> evidence event to outbox| 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.4`). Version
constraints cannot be expressed inside the contract, so the tag in
`uses:` is the only immutability lever a consumer has.
**Unversioned references are discouraged.** Do not use `@main` or a bare
`acdl/pipelines/deploy.yaml` — `main` is constantly updated and can cause
unexpected failures in your deployment. Pinning to a MAJOR+MINOR tag means:
- **Immutability** — the pipeline behavior you tested is the behavior you
get. Patch fixes flow within the tag; breaking changes land under the
next MINOR tag (`@v1.5`), which you opt into explicitly.
- **Resilience** — your deployment does not break because an unrelated
change landed on `main`.
- **DX** — your setup is stable and reproducible. You upgrade on your
schedule by bumping the tag.
All examples in this guide use `@v1.4`. When a new MINOR tag is released
(e.g. `@v1.5`), review its changelog and bump your `uses:` reference when
ready.
## Prerequisites
These are the **only** prerequisites for a consumer repo. You do **not**
need an AWS account, Terraform, Checkov, boto3, or a rotated runner key —
those are platform-repo concerns, provided by the platform runners.
- **A consumer GitHub or Gitea repository** for your application code +
`contract.yaml`.
- **An ACDL platform runner available to your org.** The platform team
provides runners with Terraform, Checkov, Python, and the AWS auth
already configured. You do not install any of these.
- **Authorization to reference the central pipeline.** Onboarding grants
your repo the right to `uses: acdl/.gitea/workflows/deploy.yml@v1.4`.
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
```
Example for a microservice:
```
my-microservice/
app.py
Dockerfile
.acdl/
contract.yaml
```
Your app code lives at the top level. Your contract lives at
`.acdl/contract.yaml` regardless of the module you deploy.
## 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.4
```
This tells the platform to run the standard deployment pipeline:
validate-contract -> resolve-stack -> terraform-plan -> checkov ->
confidence -> apply.
## Step 3 — Define the contract
Write `.acdl/contract.yaml`. The `static-asset` example:
```yaml
uses: acdl/pipelines/deploy.yaml@v1.4
module: static-asset
environment: dev
inputs:
bucket_name: my-static-site-assets
region: us-east-1
```
A `microservice` example:
```yaml
uses: acdl/pipelines/deploy.yaml@v1.4
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.4`). Bare or `@main` references are discouraged. |
| `module` | string | yes | Module name from the registry — any L1 primitive or L2 composition (e.g. `static-asset`, `microservice`, `s3`). See the [module catalog](../modules/README.md). |
| `environment` | enum | yes | `dev` (autonomous), `qa` (QA HITL), `prod` (SRE HITL), `dr` (SRE HITL). |
| `inputs` | object | yes | Module-specific inputs (see below). |
### Module inputs
Each module declares its inputs in its `interface.json` (L1) or
`composition.json` (L2). Consult the [module catalog](../modules/README.md)
for the full list, or read the module's own README under `modules/l1//`
or `modules/l2//`.
**`static-asset` inputs** (the worked example):
| Input | Type | Required | Description |
|-------|------|----------|-------------|
| `bucket_name` | string | yes | Globally-unique S3 bucket name. |
| `region` | string | yes | AWS region the bucket is created in. |
The contract is validated against `schemas/contract.schema.json`. 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 workflow
Add a thin workflow file to **your** repo that invokes the reusable ACDL
deploy workflow with a **versioned tag**. For Gitea Actions
(`.gitea/workflows/deploy.yml`):
```yaml
name: deploy
on:
push:
branches: [main]
jobs:
deploy:
uses: acdl/.gitea/workflows/deploy.yml@v1.4
with:
contract: .acdl/contract.yaml
```
For GitHub Actions (`.github/workflows/deploy.yml`), the `uses:` line is
identical — only the directory differs:
```yaml
name: deploy
on:
push:
branches: [main]
jobs:
deploy:
uses: acdl/.github/workflows/deploy.yml@v1.4
with:
contract: .acdl/contract.yaml
```
That is the entire consumer-side workflow. When you push to `main`:
1. The forge resolves `uses: acdl/.gitea/workflows/deploy.yml@v1.4` (or
the GitHub equivalent) to the reusable workflow **at the pinned tag**.
2. A **platform-provided runner** checks out **your** repo (the consumer
repo).
3. The runner checks out the **ACDL platform repo** into the workspace
(`acdl-platform/`) — 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 (Python, Terraform,
Checkov) that the platform requires.
5. The runner invokes `scripts/run_platform.sh` against your
`.acdl/contract.yaml`.
You see the streamed output (terraform plan, Checkov results, confidence
signal) in your forge 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 HITL).
### 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 (Python,
`jsonschema`, `pyyaml`, `boto3`) 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 forge
runs, not for locally-held copies.
```bash
# Optional pre-push validation (clone the platform repo first):
bash scripts/run_platform.sh --check-only path/to/your/.acdl/contract.yaml
# Expected: "=== PLATFORM CHECK OK ==="
```
## Step 5 — What the pipeline does
Each stage of the central deployment pipeline (`pipelines/deploy.yaml`):
```mermaid
flowchart TD
S1["validate-contract
schema check vs contract.schema.json"] --> S2
S2["resolve-stack
contract_resolver.py -> Target Stack JSON"] --> S3
S3["terraform-plan
adapter.py compiles stack -> terraform plan (real AWS)"] --> S4
S4["checkov
policy checks -> PolicyCheckResult records"] --> S5
S5["confidence
confidence_signal.py -> score + band (dev >= 0.50)"] --> S6
S6["apply
dev only: terraform apply + evidence event to outbox"]
```
1. **validate-contract** — validates your contract YAML against
`schemas/contract.schema.json`. Fails fast on missing fields, unknown
modules, or wrong types.
2. **resolve-stack** — the contract resolver
(`acdl_platform/contract_resolver.py`) resolves your contract to a
Target Stack instance. It loads the module's composition, expands its
children, wires your contract inputs to the children's inputs, and
emits a stack JSON instance.
3. **terraform-plan** — the Terraform adapter
(`adapters/terraform/adapter.py`) compiles the stack to Terraform
(`main.tf`, `terraform.tf`, `providers.tf`) and runs `terraform plan`
against real AWS. You see the plan in your run logs.
4. **checkov** — Checkov runs policy checks on the emitted Terraform. The
results are normalized to `PolicyCheckResult` records by the Checkov
adapter. Each result has a severity, rule ID, and pass/fail status.
5. **confidence** — the confidence signal
(`acdl_platform/confidence_signal.py`) 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.
6. **apply** — (dev only, autonomous per the environment model) Terraform
applies the plan, creating the resources in your AWS account. An
evidence event (hash-chained) is written to the DynamoDB outbox.
## Step 6 — What gets created
After a successful `dev` run, the resources declared by your module's
composition exist in your AWS account, and an evidence event is recorded.
For the `static-asset` example:
- **An S3 bucket** named `my-static-site-assets` in `us-east-1` with
versioning enabled.
- **An evidence event** in the DynamoDB outbox (`acdl-outbox` table) with
the contract ID, stack name (`static-asset`), 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-asset example)
The platform provisions the infrastructure; you upload your content. For
the `static-asset` module:
```bash
aws s3 sync ./assets s3://my-static-site-assets/ --acl public-read
```
(For a proper static site, configure the bucket for website hosting or
put a CloudFront distribution in front — both are future compliance
extension points for the `static-asset` module.)
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.4
environment: qa # QA HITL gate + confidence >= 0.75
environment: prod # SRE HITL gate + confidence >= 0.90
```
Higher environments require human attestation (forge deployment approval)
and higher confidence thresholds. The platform enforces separation of
duties (qaApprover != prodApprover) via the DynamoDB outbox.
| Environment | Autonomy | Gate |
|-------------|----------|------|
| dev | Full autonomy | Confidence >= 0.50 |
| qa | QA HITL | Confidence >= 0.75 |
| prod | SRE HITL | Confidence >= 0.90 |
| dr | SRE HITL | Confidence >= 0.95 + dr-drill |
## 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 (Gitea) | `.gitea/workflows/deploy.yml` | The workflow your repo invokes via `uses:`. |
| Reusable deploy workflow (GitHub) | `.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/README.md` | All L1 primitives and L2 compositions. |
| Sample contract | `contracts/static-asset.yaml` | The reference example contract (uses `@v1.4`). |
| Contract resolver | `acdl_platform/contract_resolver.py` | Resolves contracts to stack instances. |
| Terraform adapter | `adapters/terraform/adapter.py` | Compiles stack instances to Terraform. |
| Platform pipeline runner | `scripts/run_platform.sh` | The pipeline runner (platform-side; consumers do not invoke it directly). |
| 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. |