feat(P20): consumer happy path + reusable deploy workflow (v1.5.0)
---ci--- project: acdl phase: 20 milestone: v1.5 status: verify ---/ci--- REQ-46: README rewritten — platform-source vs consumer-repo distinction up front; platform flow converted to mermaid flowchart TD; L3A/L3B + spike nomenclature scrubbed from prose (code paths kept verbatim); prereqs pointer to consumer guide added. REQ-47: docs/CONSUMER_GUIDE.md (generic, all L2 modules) replaces docs/consumer-guide-static-asset.md — mermaid diagrams (model LR + pipeline TD), versioned uses: (@v1.4 floating MAJOR+MINOR, bare/@main discouraged), consumer-scoped prerequisites (no Terraform/Checkov/boto3/runner-key), run- time platform fetch via reusable workflow (consumers never invoke scripts/run_platform.sh locally for the happy path), optional local validation note. REQ-48: Credentials section rewritten — zero-trust OIDC + ABAC default (repo-identity + resource-tag scoping, blast-radius containment); static-key override in GitHub Secrets or .env.secrets with platform-managed daily rotation; consumer rotates out of band when using .env.secrets locally. REQ-49: byte-identical .gitea/workflows/deploy.yml + .github/workflows/ deploy.yml — reusable (on: workflow_call), checks out consumer repo + ACDL platform repo, installs deps, runs run_platform.sh, uploads artifacts; OIDC default (permissions: id-token: write) + static-key override via secrets. REQ-50: contracts/static-asset.yaml uses: @v1.4 (MAJOR+MINOR). REQ-51: tests/test_pipeline_contract.py extended — TestDeployPipelineSchema, TestDeployPipelineContract, TestDeployWorkflowConformance (byte-identical, reusable, contract/mode inputs, run_platform invocation, platform-repo checkout, OIDC permissions), TestSampleContractVersioning. 154 tests pass (19 new); run_ci.sh green. Fixes: modules/l2/static-asset/README.md dangling link retargeted to docs/CONSUMER_GUIDE.md.
This commit is contained in:
@@ -0,0 +1,359 @@
|
||||
# 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<br/>(app code + contract.yaml)"] -->|uses: acdl/.gitea/workflows/deploy.yml@v1.4| B
|
||||
B["ACDL platform runners<br/>(modules/ + pipelines/ + adapters/ + schemas/)"] -->|contract -> resolver -> stack -> adapter<br/>-> terraform plan -> Checkov -> confidence<br/>-> 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/<name>/`
|
||||
or `modules/l2/<name>/`.
|
||||
|
||||
**`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<br/>schema check vs contract.schema.json"] --> S2
|
||||
S2["resolve-stack<br/>contract_resolver.py -> Target Stack JSON"] --> S3
|
||||
S3["terraform-plan<br/>adapter.py compiles stack -> terraform plan (real AWS)"] --> S4
|
||||
S4["checkov<br/>policy checks -> PolicyCheckResult records"] --> S5
|
||||
S5["confidence<br/>confidence_signal.py -> score + band (dev >= 0.50)"] --> S6
|
||||
S6["apply<br/>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/<name>/README.md` or `modules/l2/<name>/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/<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 (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. |
|
||||
Reference in New Issue
Block a user