Files
acdl/docs/consumer-guide.md
T
Jon Chery 7585c828f0
acdl-ci / Lint (push) Successful in 7s
acdl-ci / Test (push) Successful in 23s
acdl-ci / Platform check-only (offline) (push) Successful in 8s
docs(P48): vision gaps + badge system + substrate→engine + CR format + agentic tags
9 requirements implemented across presentation decks and project docs:

1. DX closing slide: added 'Infrastructure as a utility, not a craft' bullet
   to convey the full vision (infrastructure consumed, not maintained;
   platform compounds value over time).
2. PW Problem slide: 'moving a merged change' → 'promoting a change'.
3. PW Problem slide: added 'Red tape' and 'Scalability without increasing
   headcount' bullets (4 frictions, not 2).
4. PW Roadmap slide: redesigned with side-by-side HTML table layout
   (Testing | Planned), 16px font, no overflow.
5. PW deck: added new slide 'What This Platform Is — and Isn't' after North
   Star (sovereign boundary, infrastructure as utility, 4 anti-goals).
   PW deck now 16 slides (was 15).
6. Maturity nomenclature: 'Available today'/'shipped' → 'Testing' across
   both decks + source markdown. New .testing badge (blue/teal #DBEAFE).
   Roadmap title: 'Testing vs. Planned'. The platform has 0 consumer
   adoption — 'shipped' was inaccurate.
7. Global: 'substrate' → 'engine' across entire project (88 matches, 30+
   files including .ciagent/, docs/, modules/, adapters/, schemas/, code).
8. Presentation files only: 'forge' → 'VCS' / 'version control system'
   (6 occurrences in 4 files). 'forge' retained in all technical docs and
   code as the industry-standard term.
9. New .agentic badge (purple/violet #EDE9FE) appended to agentic features
   in both decks: confidence signal, autonomous dev, pattern recognition,
   dynamic module creation, citizen developer surface, auto-promotion.

Also: Change Request ID format changed from 'CR-2026-001' to 'CHG0678912'
across presentation files, consumer guide, and test fixtures.

HTML re-rendered. PPTX rendered for release upload.

---ci---
phase: 48
milestone: v1.9
status: complete
requirements:
  covered: []
  partial: []
---/ci---
2026-07-23 14:58: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 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, 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: acdl/.github/workflows/deploy.yml@v1.9| 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. acdl/pipelines/deploy.yaml@v1.9). 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 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.

  • 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: acdl/.github/workflows/deploy.yml@v1.9. 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):

uses: acdl/pipelines/deploy.yaml@v1.9

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:

uses: acdl/pipelines/deploy.yaml@v1.9
module: static-assets
environment: dev
inputs:
  bucket_name: my-static-site-assets
  region: us-east-1

A microservice example:

uses: acdl/pipelines/deploy.yaml@v1.9
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.9). 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 ACDL deploy workflow with a versioned tag (.github/workflows/deploy.yml):

name: deploy
on:
  push:
    branches: [main]
jobs:
  deploy:
    uses: acdl/.github/workflows/deploy.yml@v1.9
    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.9 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 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/.acdl/contract.yaml

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 (keeping the same versioned uses:):

uses: acdl/pipelines/deploy.yaml@v1.9
environment: qa   # QA attestation + confidence >= 0.75

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/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/ All primitives and modules.
Sample contract contracts/static-assets.yaml The reference example contract (uses @v1.9).
Sample contract contracts/microservice.yaml The microservice example contract (uses @v1.9).
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 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:

    uses: acdl/.github/workflows/deploy.yml@v1.8
    with:
      contract: .acdl/contract.yaml
      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

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:

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

# .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.<name>} a contract input value (as declared)

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