refactor(P57): contract surface redesign + rename + .yml repo-wide

Contract surface redesign:
- New top-level fields: id (3-6 char acronym → stack.name), name (full → stack.title),
  infrastructure (map keyed by module name, replaces module:)
- Drop uses: field (dead reference; version pin lives in CI workflow uses: line)
- Drop top-level module/inputs (now nested under infrastructure map)
- Per-module optional version (defaults to latest published from registry)
- Multi-module contracts: one file deploys N modules in one pipeline run,
  resource IDs namespaced with module name to avoid collisions
- stack.schema.json: add optional title field for display name

Rename:
- pipelines/deploy.yaml → pipelines/contract.yml (declarative spec, not a pipeline)
- pipelines/ci.yaml → pipelines/ci.yml
- All 44 .yaml files → .yml repo-wide (contracts, module examples, kyverno policies)
- .acdl/contract.yaml → .acdl/contract.yml

Resolver (core/contract_resolver.py):
- Rewrite resolve() to loop infrastructure map, default version to latest,
  merge module fragments into one stack with namespaced resource IDs
- _latest_version() picks highest non-deprecated from registry
- _namespace_resources() prefixes IDs + rewrites ref: expressions for multi-module
- Single-module path: unprefixed IDs (backward compatible)

Verification:
- 494 tests pass (0 contract-shape failures)
- Local E2E passes (contract → resolver → adapter → local ECS HTTP 200 → outbox)

---ci---
project: acdl
phase: 57
milestone: v1.10.2
status: execute
---/ci---
This commit is contained in:
Jon Chery
2026-07-27 21:37:40 +00:00
parent 7f36df5610
commit 031887ec56
127 changed files with 1597 additions and 1100 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
# ACDL CI Pipeline — Gitea Actions (dev environment)
#
# This workflow implements the central pipeline contract:
# pipelines/ci.yaml (validated against schemas/pipeline.schema.json)
# pipelines/ci.yml (validated against schemas/pipeline.schema.json)
#
# The same contract is implemented by .github/workflows/ci.yml (GitHub
# Actions, production). Both files must be byte-identical — the only
+3 -3
View File
@@ -1,7 +1,7 @@
# ACDL Reusable Deploy Workflow — Gitea Actions (dev environment)
#
# This reusable workflow implements the central deployment pipeline contract:
# pipelines/deploy.yaml (validated against schemas/deploy-pipeline.schema.json)
# pipelines/contract.yml (validated against schemas/deploy-pipeline.schema.json)
#
# The same contract is implemented by .github/workflows/deploy.yml (GitHub
# Actions, production). Both files must be byte-identical — the only
@@ -26,7 +26,7 @@
# platform log) for auditability.
#
# Inputs:
# contract — path to the consumer's contract YAML (default .acdl/contract.yaml)
# contract — path to the consumer's contract YAML (default .acdl/contract.yml)
# mode — full | plan-only | check-only (default full; dev = full apply,
# higher environments hold for HITL — the calling repo or the
# forge environment gate enforces that)
@@ -51,7 +51,7 @@ on:
contract:
description: Path to the consumer contract YAML (in the consumer repo)
type: string
default: .acdl/contract.yaml
default: .acdl/contract.yml
mode:
description: Pipeline mode — full (apply), plan-only, check-only, or decommission
type: string
+1 -1
View File
@@ -1,7 +1,7 @@
# ACDL CI Pipeline — Gitea Actions (dev environment)
#
# This workflow implements the central pipeline contract:
# pipelines/ci.yaml (validated against schemas/pipeline.schema.json)
# pipelines/ci.yml (validated against schemas/pipeline.schema.json)
#
# The same contract is implemented by .github/workflows/ci.yml (GitHub
# Actions, production). Both files must be byte-identical — the only
+3 -3
View File
@@ -1,7 +1,7 @@
# ACDL Reusable Deploy Workflow — Gitea Actions (dev environment)
#
# This reusable workflow implements the central deployment pipeline contract:
# pipelines/deploy.yaml (validated against schemas/deploy-pipeline.schema.json)
# pipelines/contract.yml (validated against schemas/deploy-pipeline.schema.json)
#
# The same contract is implemented by .github/workflows/deploy.yml (GitHub
# Actions, production). Both files must be byte-identical — the only
@@ -26,7 +26,7 @@
# platform log) for auditability.
#
# Inputs:
# contract — path to the consumer's contract YAML (default .acdl/contract.yaml)
# contract — path to the consumer's contract YAML (default .acdl/contract.yml)
# mode — full | plan-only | check-only (default full; dev = full apply,
# higher environments hold for HITL — the calling repo or the
# forge environment gate enforces that)
@@ -51,7 +51,7 @@ on:
contract:
description: Path to the consumer contract YAML (in the consumer repo)
type: string
default: .acdl/contract.yaml
default: .acdl/contract.yml
mode:
description: Pipeline mode — full (apply), plan-only, check-only, or decommission
type: string
+3 -3
View File
@@ -5,7 +5,7 @@
#
# Shell reproducibility: scripts/run_ci.sh runs lint + test + check-only locally.
# The integration-test stage runs run_platform.sh --check-only for every
# contracts/*.yaml file. The schema-validation stage validates schemas, module
# contracts/*.yml file. The schema-validation stage validates schemas, module
# interfaces, compositions, and example contracts.
name: acdl-platform-test
@@ -62,7 +62,7 @@ jobs:
run: pip install jsonschema pyyaml boto3
- name: Run platform check-only for every sample contract
run: |
for contract in contracts/*.yaml; do
for contract in contracts/*.yml; do
echo "--- Testing $contract ---"
bash scripts/run_platform.sh --check-only "$contract"
done
@@ -139,7 +139,7 @@ jobs:
except Exception as e:
print(f'{example}: SKIP (not a contract or invalid: {e})')
# Also validate all sample contracts in contracts/
for contract_file in glob.glob('contracts/*.yaml'):
for contract_file in glob.glob('contracts/*.yml'):
contract = yaml.safe_load(open(contract_file))
jsonschema.validate(contract, schema)
print(f'{contract_file}: valid contract')
+12 -12
View File
@@ -26,9 +26,9 @@ There are two kinds of repository in the ACDL model:
A **consumer never clones it.**
- **Consumer repo (yours).** A consumer repo contains only:
1. **Its application code** — the service or site being deployed.
2. **One or more contracts** — small YAML files at `.acdl/contract.yaml`
that reference the central pipeline, name a module, select an
environment, and supply module-specific inputs.
2. **One or more contracts** — small YAML files at `.acdl/contract.yml`
that declare infrastructure (one or more modules by name + version),
select an environment, and supply module-specific inputs.
3. **One or more CI definitions** — thin `.github/workflows/*.yml` files
that `uses:` the central reusable deploy workflow, pointing at the
appropriate environment + contract.
@@ -93,9 +93,9 @@ intent via a contract; the platform delivers the deployment through the
same contract schema, the same policy envelope, and the same evidence
stream.
Consumers have their own repos and consume ACDL by referencing `uses:` the
central pipeline definitions. A consumer declares a contract (module +
environment + inputs); the platform resolves it to a stack instance,
Consumers have their own repos and consume ACDL by writing a contract that
declares infrastructure. A consumer declares a contract (id + name +
environment + infrastructure); the platform resolves it to a stack instance,
compiles it, runs security + policy checks, computes a confidence signal,
writes an evidence event to the audit outbox, and applies the
infrastructure.
@@ -104,7 +104,7 @@ infrastructure.
```mermaid
flowchart TD
A["consumer contract<br/>(uses + module + environment + inputs)"] --> B
A["consumer contract<br/>(id + name + environment + infrastructure)"] --> B
B["schema validation<br/>(contract schema)"] --> C
C["resolve to Target Stack<br/>(contract resolver)"] --> D
D["security checks<br/>(adapter)"] --> E
@@ -155,7 +155,7 @@ ACDL_BOOTSTRAP_AWS_ACCESS_KEY_ID=... ACDL_BOOTSTRAP_AWS_SECRET_ACCESS_KEY=... \
# 3. Run the full platform pipeline (contract -> environment check -> stack ->
# adapter -> security checks -> infrastructure plan -> policy checks ->
# confidence -> evidence event -> apply). Output is streamed to stdout.
bash scripts/run_platform.sh contracts/static-assets.yaml
bash scripts/run_platform.sh contracts/static-assets.yml
# Expected: "=== PLATFORM E2E OK ==="
# Or plan-only (contract -> stack -> adapter -> infrastructure plan; no
@@ -189,7 +189,7 @@ bash scripts/run_ci.sh
### CI/CD pipelines
The CI/CD pipeline is defined by a **central pipeline contract** — a
declarative YAML instance (`pipelines/ci.yaml`) validated against a JSON
declarative YAML instance (`pipelines/ci.yml`) validated against a JSON
Schema (`schemas/pipeline.schema.json`). Both platform-runner workflows
implement the same contract:
@@ -212,13 +212,13 @@ bash scripts/run_ci.sh --quiet # suppress per-stage banners
### Reusable deploy workflow
The deployment pipeline is defined by a **central deployment pipeline
contract** (`pipelines/deploy.yaml`, validated against
contract** (`pipelines/contract.yml`, validated against
`schemas/deploy-pipeline.schema.json`) and exposed to consumer repos as a
**reusable workflow**:
- `.github/workflows/deploy.yml` — GitHub Actions (production)
The workflow implements the same stages as `pipelines/deploy.yaml`
The workflow implements the same stages as `pipelines/contract.yml`
(validate-contract → resolve-stack → security checks → infrastructure plan
→ policy checks → confidence → evidence event → apply). A consumer repo
invokes the reusable workflow via a **versioned tag** (floating MAJOR +
@@ -257,7 +257,7 @@ across all modules; `static-assets` is the worked example.
|------|---------|--------|
| `core/` | Platform code: contract resolver, confidence signal, outbox writer, environment check, environments, separation of duties, HITL/ledger designs | active |
| `schemas/` | JSON Schemas: stack, contract, PolicyCheckResult, pipeline contract, deploy pipeline contract (draft 2020-12) | active |
| `pipelines/` | Central pipeline contracts: `ci.yaml` (CI), `deploy.yaml` (deployment) | active |
| `pipelines/` | Central pipeline contracts: `ci.yml` (CI), `contract.yml` (deployment) | active |
| `adapters/` | Angine adapters — the engine adapter (the only engine-specific code per §12) + the policy adapter | active |
| `terraform/` | State backend (S3 + DynamoDB) + platform TF (`terraform/spike/`) + bootstrap scripts (`terraform/bootstrap/`) | active |
| `modules/` | Primitives + modules + `registry.json`. Primitives: s3, vpc, ecs-cluster, ecs-service, iam-role, alb, ecr, cloudfront, waf, rds. Modules: microservice, static-assets. Each module has a `examples/` directory with validated contract examples | active |
+3 -3
View File
@@ -53,12 +53,12 @@ invoke it. The `engine: "kyverno"` enum value is present in
The `policies/` directory holds three valid Kyverno `ClusterPolicy`
manifests (documentation-only today — the platform does not run them):
- `disallow-privileged-containers.yaml` — fail pods with
- `disallow-privileged-containers.yml` — fail pods with
`securityContext.privileged: true`.
- `require-resource-labels.yaml` — require `acdl:owner` and
- `require-resource-labels.yml` — require `acdl:owner` and
`acdl:environment` labels on all pods (mirrors the ACDL tagging standard
in [`schemas/tagging-standard.json`](../../schemas/tagging-standard.json)).
- `require-image-digests.yaml` — require container images to reference a
- `require-image-digests.yml` — require container images to reference a
digest (`image@sha256:...`), not a mutable tag.
## Schema path
-11
View File
@@ -1,11 +0,0 @@
# ACDL sample consumer contract — microservice module (dev)
# Per-environment contract (REQ-105). Promotion = running the dev job;
# no environment field editing. Interpolation resolves against dev.json.
uses: acdl/pipelines/deploy.yaml@v1.9
module: microservice
environment: dev
inputs:
bucket_name: acdl-${env.environment}-${contract.module}-${env.account_id}-${env.region}
region: ${env.region}
image: public.ecr.aws/docker/library/nginx:latest
port: 80
+14
View File
@@ -0,0 +1,14 @@
# ACDL sample consumer contract — microservice module (dev)
# Per-environment contract (REQ-105). Promotion = running the dev job;
# no environment field editing. Interpolation resolves against dev.json.
id: msvc
name: microservice
environment: dev
infrastructure:
microservice:
version: "1.0.0"
inputs:
bucket_name: acdl-${env.environment}-${contract.id}-${env.account_id}-${env.region}
region: ${env.region}
image: public.ecr.aws/docker/library/nginx:latest
port: 80
-11
View File
@@ -1,11 +0,0 @@
# ACDL sample consumer contract — microservice module (dr)
# Per-environment contract (REQ-105). Promotion = running the dr job;
# no environment field editing. Interpolation resolves against dr.json.
uses: acdl/pipelines/deploy.yaml@v1.9
module: microservice
environment: dr
inputs:
bucket_name: acdl-${env.environment}-${contract.module}-${env.account_id}-${env.region}
region: ${env.region}
image: public.ecr.aws/docker/library/nginx:latest
port: 80
+14
View File
@@ -0,0 +1,14 @@
# ACDL sample consumer contract — microservice module (dr)
# Per-environment contract (REQ-105). Promotion = running the dr job;
# no environment field editing. Interpolation resolves against dr.json.
id: msvc
name: microservice
environment: dr
infrastructure:
microservice:
version: "1.0.0"
inputs:
bucket_name: acdl-${env.environment}-${contract.id}-${env.account_id}-${env.region}
region: ${env.region}
image: public.ecr.aws/docker/library/nginx:latest
port: 80
-11
View File
@@ -1,11 +0,0 @@
# ACDL sample consumer contract — microservice module (prod)
# Per-environment contract (REQ-105). Promotion = running the prod job;
# no environment field editing. Interpolation resolves against prod.json.
uses: acdl/pipelines/deploy.yaml@v1.9
module: microservice
environment: prod
inputs:
bucket_name: acdl-${env.environment}-${contract.module}-${env.account_id}-${env.region}
region: ${env.region}
image: public.ecr.aws/docker/library/nginx:latest
port: 80
+14
View File
@@ -0,0 +1,14 @@
# ACDL sample consumer contract — microservice module (prod)
# Per-environment contract (REQ-105). Promotion = running the prod job;
# no environment field editing. Interpolation resolves against prod.json.
id: msvc
name: microservice
environment: prod
infrastructure:
microservice:
version: "1.0.0"
inputs:
bucket_name: acdl-${env.environment}-${contract.id}-${env.account_id}-${env.region}
region: ${env.region}
image: public.ecr.aws/docker/library/nginx:latest
port: 80
-11
View File
@@ -1,11 +0,0 @@
# ACDL sample consumer contract — microservice module (qa)
# Per-environment contract (REQ-105). Promotion = running the qa job;
# no environment field editing. Interpolation resolves against qa.json.
uses: acdl/pipelines/deploy.yaml@v1.9
module: microservice
environment: qa
inputs:
bucket_name: acdl-${env.environment}-${contract.module}-${env.account_id}-${env.region}
region: ${env.region}
image: public.ecr.aws/docker/library/nginx:latest
port: 80
+14
View File
@@ -0,0 +1,14 @@
# ACDL sample consumer contract — microservice module (qa)
# Per-environment contract (REQ-105). Promotion = running the qa job;
# no environment field editing. Interpolation resolves against qa.json.
id: msvc
name: microservice
environment: qa
infrastructure:
microservice:
version: "1.0.0"
inputs:
bucket_name: acdl-${env.environment}-${contract.id}-${env.account_id}-${env.region}
region: ${env.region}
image: public.ecr.aws/docker/library/nginx:latest
port: 80
-14
View File
@@ -1,14 +0,0 @@
# ACDL sample consumer contract — microservice module (dev)
#
# Reference example for an ECS Fargate microservice deployment.
# Interpolation (D-081): bucket_name uses the naming pattern that includes
# region, aws account id, and environment:
# acdl-${env.environment}-${contract.module}-${env.account_id}-${env.region}
uses: acdl/pipelines/deploy.yaml@v1.9
module: microservice
environment: dev
inputs:
bucket_name: acdl-${env.environment}-${contract.module}-${env.account_id}-${env.region}
region: ${env.region}
image: public.ecr.aws/docker/library/nginx:latest
port: 80
+17
View File
@@ -0,0 +1,17 @@
# ACDL sample consumer contract — microservice module (dev)
#
# Reference example for an ECS Fargate microservice deployment.
# Interpolation (D-081): bucket_name uses the naming pattern that includes
# region, aws account id, and environment:
# acdl-${env.environment}-${contract.id}-${env.account_id}-${env.region}
id: msvc
name: microservice
environment: dev
infrastructure:
microservice:
version: "1.0.0"
inputs:
bucket_name: acdl-${env.environment}-${contract.id}-${env.account_id}-${env.region}
region: ${env.region}
image: public.ecr.aws/docker/library/nginx:latest
port: 80
-10
View File
@@ -1,10 +0,0 @@
# ACDL sample consumer contract — static-assets module (dev)
# Per-environment contract (REQ-105). The dev default
# (contracts/static-assets.yaml) remains for backwards compat; this file
# is the explicit per-env dev contract. Interpolation resolves against dev.json.
uses: acdl/pipelines/deploy.yaml@v1.9
module: static-assets
environment: dev
inputs:
bucket_name: acdl-${env.environment}-${contract.module}-${env.account_id}-${env.region}
region: ${env.region}
+13
View File
@@ -0,0 +1,13 @@
# ACDL sample consumer contract — static-assets module (dev)
# Per-environment contract (REQ-105). The dev default
# (contracts/static-assets.yml) remains for backwards compat; this file
# is the explicit per-env dev contract. Interpolation resolves against dev.json.
id: assets
name: static-assets
environment: dev
infrastructure:
static-assets:
version: "1.0.0"
inputs:
bucket_name: acdl-${env.environment}-${contract.id}-${env.account_id}-${env.region}
region: ${env.region}
@@ -1,9 +1,12 @@
# ACDL sample consumer contract — static-assets module (dr)
# Per-environment contract (REQ-105). Promotion = running the dr job;
# no environment field editing. Interpolation resolves against dr.json.
uses: acdl/pipelines/deploy.yaml@v1.9
module: static-assets
id: assets
name: static-assets
environment: dr
inputs:
bucket_name: acdl-${env.environment}-${contract.module}-${env.account_id}-${env.region}
region: ${env.region}
infrastructure:
static-assets:
version: "1.0.0"
inputs:
bucket_name: acdl-${env.environment}-${contract.id}-${env.account_id}-${env.region}
region: ${env.region}
@@ -1,9 +1,12 @@
# ACDL sample consumer contract — static-assets module (prod)
# Per-environment contract (REQ-105). Promotion = running the prod job;
# no environment field editing. Interpolation resolves against prod.json.
uses: acdl/pipelines/deploy.yaml@v1.9
module: static-assets
id: assets
name: static-assets
environment: prod
inputs:
bucket_name: acdl-${env.environment}-${contract.module}-${env.account_id}-${env.region}
region: ${env.region}
infrastructure:
static-assets:
version: "1.0.0"
inputs:
bucket_name: acdl-${env.environment}-${contract.id}-${env.account_id}-${env.region}
region: ${env.region}
@@ -1,9 +1,12 @@
# ACDL sample consumer contract — static-assets module (qa)
# Per-environment contract (REQ-105). Promotion = running the qa job;
# no environment field editing. Interpolation resolves against qa.json.
uses: acdl/pipelines/deploy.yaml@v1.9
module: static-assets
id: assets
name: static-assets
environment: qa
inputs:
bucket_name: acdl-${env.environment}-${contract.module}-${env.account_id}-${env.region}
region: ${env.region}
infrastructure:
static-assets:
version: "1.0.0"
inputs:
bucket_name: acdl-${env.environment}-${contract.id}-${env.account_id}-${env.region}
region: ${env.region}
-23
View File
@@ -1,23 +0,0 @@
# ACDL sample consumer contract — static-assets module (dev)
#
# This is the reference example for a consumer contract. It declares:
# uses: the central ACDL deployment pipeline to reference
# module: which module to deploy (must match a registry key)
# environment: which environment to deploy to (dev = autonomous)
# inputs: module-specific inputs
#
# Validated against schemas/contract.schema.json.
# Resolved by core/contract_resolver.py to a Target Stack instance.
#
# Interpolation (D-081): ${env.<field>} + ${contract.<field>} tokens are
# expanded by the resolver from the environment onboarding JSON. The
# bucket_name below demonstrates the naming pattern that includes region,
# aws account id, and environment:
# acdl-${env.environment}-${contract.module}-${env.account_id}-${env.region}
uses: acdl/pipelines/deploy.yaml@v1.9
module: static-assets
environment: dev
inputs:
bucket_name: acdl-${env.environment}-${contract.module}-${env.account_id}-${env.region}
region: ${env.region}
+29
View File
@@ -0,0 +1,29 @@
# ACDL sample consumer contract — static-assets module (dev)
#
# This is the reference example for a consumer contract. It declares:
# id: short operational acronym (becomes stack.name for state, tags, evidence)
# name: full human-readable stack name (becomes stack.title for display)
# environment: which environment to deploy to (dev = autonomous)
# infrastructure: map of modules to deploy (keyed by module registry name)
# <module>:
# version: module version pin (defaults to latest published)
# inputs: module-specific inputs
#
# Validated against schemas/contract.schema.json.
# Resolved by core/contract_resolver.py to a Target Stack instance.
#
# Interpolation (D-081): ${env.<field>} + ${contract.<field>} tokens are
# expanded by the resolver from the environment onboarding JSON. The
# bucket_name below demonstrates the naming pattern that includes region,
# aws account id, and environment:
# acdl-${env.environment}-${contract.id}-${env.account_id}-${env.region}
id: assets
name: static-assets
environment: dev
infrastructure:
static-assets:
version: "1.0.0"
inputs:
bucket_name: acdl-${env.environment}-${contract.id}-${env.account_id}-${env.region}
region: ${env.region}
+209 -76
View File
@@ -5,17 +5,27 @@ The contract resolver is the bridge between the consumer's declared intent
Stack JSON instance). It:
1. Loads and validates the contract against schemas/contract.schema.json.
2. Looks up the module name in modules/registry.json.
3. If the module is an L1 primitive: builds a stack instance directly from
the interface.json + contract inputs.
4. If the module is an L2 composition: loads the composition.json, expands
children to stack resources, resolves wires to ref: expressions, and
emits the full stack instance.
2. For each module in the contract's `infrastructure` map:
a. Looks up the module name + version in modules/registry.json
(version defaults to the latest non-deprecated entry when omitted).
b. If the module is an L1 primitive: builds a stack fragment from
the interface.json + module inputs.
c. If the module is an L2 composition: loads the composition.json,
expands children to stack resources, resolves wires to ref:
expressions, and emits the fragment.
3. Merges all module fragments into a single Target Stack instance:
- stack.name = contract.id (the short operational acronym)
- stack.title = contract.name (the full human-readable name)
- When the contract has one module: resource IDs are unprefixed
(backward-compatible with existing stack consumers).
- When the contract has multiple modules: resource IDs are prefixed
with the module name (e.g. `microservice-vpc`) to avoid collisions,
and all ref:/parent references are rewritten to match.
The output is a JSON instance valid against schemas/stack.schema.json,
ready for the Terraform adapter to compile.
CLI: contract_resolver.py <contract.yaml> <out.json>
CLI: contract_resolver.py <contract.yml> <out.json>
"""
import json
@@ -150,68 +160,77 @@ def _resolve_wire_value(wire, contract_inputs, child_outputs):
return None
def resolve_l1(contract, registry, repo_root):
"""Resolve a contract referencing an L1 primitive to a stack instance."""
module_name = contract["module"]
module_ref = f"{module_name}@1.0.0"
inputs = contract.get("inputs", {})
environment = contract.get("environment", "dev")
def _latest_version(registry, module_name):
"""Return the latest non-deprecated version string for a module.
Falls back to the highest version even if all are deprecated.
"""
versions = registry[module_name]
non_deprecated = [(v, e) for v, e in versions.items()
if not e.get("deprecated", False)]
if not non_deprecated:
non_deprecated = list(versions.items())
non_deprecated.sort(key=lambda x: [int(p) for p in x[0].split(".")],
reverse=True)
return non_deprecated[0][0]
def _resolve_l1(module_name, version, inputs, registry, repo_root):
"""Resolve a single L1 primitive module to a stack-fragment (resources list)."""
module_ref = f"{module_name}@{version}"
# Load the interface
entry = registry[module_name]["1.0.0"]
entry = registry[module_name][version]
iface_path = os.path.join(repo_root, entry["interface"])
iface = _load_json(iface_path)
# Build the stack instance
stack_instance = {
"version": "1.0.0",
"stack": {
"name": module_name,
"kind": "l1",
"depth": 1,
# Build the resource
resource = {
"id": iface.get("type", module_name).split(":")[-1]
if ":" in iface.get("type", "") else module_name,
"type": iface["type"],
"module": module_ref,
"inputs": dict(inputs),
"outputs": {
out_name: {"type": out_spec.get("type", "string")}
for out_name, out_spec in iface.get("outputs", {}).items()
},
"resources": [
{
"id": iface.get("type", module_name).split(":")[-1]
if ":" in iface.get("type", "") else module_name,
"type": iface["type"],
"module": module_ref,
"inputs": dict(inputs),
"outputs": {
out_name: {"type": out_spec.get("type", "string")}
for out_name, out_spec in iface.get("outputs", {}).items()
},
}
],
}
# Add NFRs if present in the interface
nfrs = iface.get("nfrs", {})
if nfrs:
stack_instance["resources"][0]["nfrs"] = nfrs
resource["nfrs"] = nfrs
return stack_instance
return {
"kind": "l1",
"depth": 1,
"resources": [resource],
"features": {},
"outputs": {},
}
def resolve_l2(contract, registry, repo_root):
"""Resolve a contract referencing an L2 composition to a stack instance."""
module_name = contract["module"]
inputs = contract.get("inputs", {})
def _resolve_l2(module_name, version, inputs, registry, repo_root):
"""Resolve a single L2 composition module to a stack-fragment.
Returns a dict with: kind, depth, resources, features, outputs.
The caller is responsible for merging fragments and setting stack.name/title.
"""
# Load the composition
entry = registry[module_name]["1.0.0"]
entry = registry[module_name][version]
comp_path = os.path.join(repo_root, entry["interface"])
composition = _load_json(comp_path)
# Track child outputs for wire resolution
# child_outputs[childId] = {outputName: resourceId}
# child_outputs[childId] = {outputName -> resourceId}
# For single-resource L1s, resourceId == childId
# For multi-resource L1s, resourceId is the expanded sub-resource id
child_outputs = {}
# child_input_map[childId] = {inputName: sub_resource_id} for multi-resource L1s
# child_input_map[childId] = {inputName -> sub_resource_id} for multi-resource L1s
# so a wire targeting <childId>.inputs.<name> routes to the sub-resource
# that actually declares that input (P1-1 — desired_count aws:ecs:service,
# family aws:ecs:task_definition).
# that actually declares that input (P1-1 — desired_count -> aws:ecs:service,
# family -> aws:ecs:task_definition).
child_input_map = {}
resources = []
@@ -220,9 +239,10 @@ def resolve_l2(contract, registry, repo_root):
child_id = child["id"]
child_module = child["module"]
child_name = child_module.split("@")[0]
child_version = child_module.split("@")[1] if "@" in child_module else "1.0.0"
# Load the child's interface to get type and outputs
child_entry = registry[child_name]["1.0.0"]
child_entry = registry[child_name][child_version]
child_iface_path = os.path.join(repo_root, child_entry["interface"])
child_iface = _load_json(child_iface_path)
@@ -309,20 +329,10 @@ def resolve_l2(contract, registry, repo_root):
res["inputs"][input_name] = value
break
# Build the stack instance
stack_instance = {
"version": "1.0.0",
"stack": {
"name": module_name,
"kind": "l2",
"depth": composition.get("depth", 1),
},
"resources": resources,
}
# REQ-87: Propagate deletion_protection feature flag from contract inputs
# to all children's NFRs. When inputs.deletion_protection is false,
# all resources get deletion_protection=false (used by decommission).
features = {}
deletion_protection_input = inputs.get("deletion_protection", True)
if deletion_protection_input is not True:
for res in resources:
@@ -331,9 +341,7 @@ def resolve_l2(contract, registry, repo_root):
res["nfrs"]["deletion_protection"] = deletion_protection_input
# Also record the feature flag on the stack object for introspection.
if "deletion_protection" in inputs:
stack_instance["stack"]["features"] = {
"deletion_protection": deletion_protection_input
}
features["deletion_protection"] = deletion_protection_input
# P1-7: Process the composition's outputs[] array to build stack.outputs.
# Each output wire: {"from": "<childId>.outputs.<name>", "to": "stack.outputs.<outName>"}
@@ -363,10 +371,55 @@ def resolve_l2(contract, registry, repo_root):
"from": src_resource_id,
"output": src_output,
}
if stack_outputs:
stack_instance["outputs"] = stack_outputs
return stack_instance
return {
"kind": "l2",
"depth": composition.get("depth", 1),
"resources": resources,
"features": features,
"outputs": stack_outputs,
}
def _namespace_resources(resources, module_name):
"""Prefix all resource IDs with the module name for multi-module contracts.
Rewrites resource 'id', 'parent', and ref: expressions in inputs/outputs
so cross-references stay consistent within the module fragment.
"""
prefix = f"{module_name}-"
# Build the old->new id mapping
id_map = {res["id"]: f"{prefix}{res['id']}" for res in resources}
def _rewrite_ref(val):
"""Recursively rewrite ref:<id>.<out> and parent:<id> strings."""
if isinstance(val, str):
if val.startswith("ref:"):
# ref:<resourceId>.<outputName>
rest = val[4:]
if "." in rest:
rid, outname = rest.split(".", 1)
if rid in id_map:
return f"ref:{id_map[rid]}.{outname}"
return val
return val
if isinstance(val, dict):
return {k: _rewrite_ref(v) for k, v in val.items()}
if isinstance(val, list):
return [_rewrite_ref(v) for v in val]
return val
for res in resources:
res["id"] = id_map[res["id"]]
# Rewrite parent
if "parent" in res and res["parent"] in id_map:
res["parent"] = id_map[res["parent"]]
# Rewrite all ref: expressions in inputs and outputs
res["inputs"] = _rewrite_ref(res.get("inputs", {}))
if "outputs" in res:
res["outputs"] = _rewrite_ref(res["outputs"])
return resources, id_map
def decommission_transform(stack_instance):
@@ -432,24 +485,105 @@ def resolve(contract_path, repo_root=None, environment_override=None):
# reference the environment by ${env.environment}).
env["environment"] = env.get("name", env_name)
context = {"env": env, "contract": contract}
contract["inputs"] = _expand_vars(contract.get("inputs", {}), context)
# Expand interpolation tokens in each module's inputs
infrastructure = contract.get("infrastructure", {})
for module_name, module_entry in infrastructure.items():
module_entry["inputs"] = _expand_vars(
module_entry.get("inputs", {}), context)
# Load registry
registry = _load_json(os.path.join(repo_root, "modules", "registry.json"))
module_name = contract["module"]
if module_name not in registry:
raise ValueError(f"module '{module_name}' not found in registry")
# Validate every module exists in the registry, then resolve each
module_names = list(infrastructure.keys())
fragments = []
for module_name in module_names:
if module_name not in registry:
raise ValueError(f"module '{module_name}' not found in registry")
module_entry = infrastructure[module_name]
# Default version to latest non-deprecated
version = module_entry.get("version")
if version is None:
version = _latest_version(registry, module_name)
elif version not in registry[module_name]:
raise ValueError(
f"module '{module_name}' version '{version}' not found in registry")
module_inputs = module_entry.get("inputs", {})
# Determine if L1 or L2
entry = registry[module_name]["1.0.0"]
interface_path = entry["interface"]
is_l2 = "l2" in interface_path or "composition" in interface_path
# Determine if L1 or L2
entry = registry[module_name][version]
interface_path = entry["interface"]
is_l2 = "l2" in interface_path or "composition" in interface_path
if is_l2:
stack_instance = resolve_l2(contract, registry, repo_root)
if is_l2:
fragment = _resolve_l2(module_name, version, module_inputs,
registry, repo_root)
else:
fragment = _resolve_l1(module_name, version, module_inputs,
registry, repo_root)
fragments.append((module_name, fragment))
# Merge fragments into a single stack instance
all_resources = []
max_depth = 1
any_l2 = False
merged_features = {}
merged_outputs = {}
multi_module = len(fragments) > 1
for module_name, fragment in fragments:
if fragment["kind"] == "l2":
any_l2 = True
max_depth = max(max_depth, fragment["depth"])
merged_features.update(fragment.get("features", {}))
if multi_module:
# Namespace resource IDs to avoid cross-module collisions
namespaced, id_map = _namespace_resources(
fragment["resources"], module_name)
# Namespace the fragment's stack outputs (from refs)
for out_name, out_spec in fragment.get("outputs", {}).items():
src_id = out_spec.get("from", "")
if src_id in id_map:
out_spec["from"] = id_map[src_id]
merged_outputs[f"{module_name}-{out_name}"] = out_spec
all_resources.extend(namespaced)
else:
# Single module: keep IDs as-is (backward compatible)
merged_outputs.update(fragment.get("outputs", {}))
all_resources.extend(fragment["resources"])
# Determine stack kind: L2 if any module is L2 or if multi-module
if multi_module:
kind = "l2"
elif any_l2:
kind = "l2"
else:
stack_instance = resolve_l1(contract, registry, repo_root)
kind = "l1"
stack_instance = {
"version": "1.0.0",
"stack": {
"name": contract["id"],
"kind": kind,
"depth": max_depth,
},
"resources": all_resources,
}
# Add the human-readable title
if contract.get("name"):
stack_instance["stack"]["title"] = contract["name"]
# Add features if any were set
if merged_features:
stack_instance["stack"]["features"] = merged_features
# Add stack-level outputs
if merged_outputs:
stack_instance["outputs"] = merged_outputs
# Validate against stack schema
stack_schema = _load_json(os.path.join(repo_root, "schemas", "stack.schema.json"))
@@ -460,7 +594,7 @@ def resolve(contract_path, repo_root=None, environment_override=None):
if __name__ == "__main__":
if len(sys.argv) < 3:
print("usage: contract_resolver.py <contract.yaml> <out.json> [--environment <name>]", file=sys.stderr)
print("usage: contract_resolver.py <contract.yml> <out.json> [--environment <name>]", file=sys.stderr)
sys.exit(2)
contract_path = sys.argv[1]
out_path = sys.argv[2]
@@ -475,4 +609,3 @@ if __name__ == "__main__":
result = resolve(contract_path, environment_override=env_override)
with open(out_path, "w") as fh:
json.dump(result, fh, indent=2)
print(f"resolver: resolved {contract_path} -> {out_path}", file=sys.stderr)
+1 -1
View File
@@ -488,7 +488,7 @@ def run_local_e2e(contract_path: str, repo_root: Optional[Path] = None) -> Dict[
if __name__ == "__main__":
contract = sys.argv[1] if len(sys.argv) > 1 else "contracts/microservice.yaml"
contract = sys.argv[1] if len(sys.argv) > 1 else "contracts/microservice.yml"
os.environ["ACDL_LOCAL_TIER"] = "1"
result = run_local_e2e(contract)
print(json.dumps(result, indent=2))
+8 -8
View File
@@ -120,7 +120,7 @@ def _check_contract_schema_validation() -> Tuple[Status, str]:
"import json, yaml, jsonschema; "
"s=json.load(open('schemas/contract.schema.json')); "
"[jsonschema.validate(yaml.safe_load(open(f)), s) "
" for f in ['contracts/static-assets.yaml','contracts/microservice.yaml']]; "
" for f in ['contracts/static-assets.yml','contracts/microservice.yml']]; "
"print('2 sample contracts validate')",
])
@@ -144,7 +144,7 @@ def _check_resolver_static_assets() -> Tuple[Status, str]:
try:
return _check_subprocess([
"python3", "core/contract_resolver.py",
"contracts/static-assets.yaml", out,
"contracts/static-assets.yml", out,
])
finally:
try:
@@ -160,7 +160,7 @@ def _check_resolver_microservice() -> Tuple[Status, str]:
try:
return _check_subprocess([
"python3", "core/contract_resolver.py",
"contracts/microservice.yaml", out,
"contracts/microservice.yml", out,
])
finally:
try:
@@ -177,7 +177,7 @@ def _check_adapter_emits_terraform() -> Tuple[Status, str]:
os.makedirs(tf_dir, exist_ok=True)
rc, out, err = _run_subprocess([
"python3", "core/contract_resolver.py",
"contracts/static-assets.yaml", stack_path,
"contracts/static-assets.yml", stack_path,
])
if rc != 0:
return "Broken", f"resolver failed: {err.strip()[-200:]}"
@@ -276,7 +276,7 @@ def _check_local_e2e_microservice() -> Tuple[Status, str]:
This is the local-tier half of the headline E2E; the live-AWS half
lands in Phase 54 (D-093)."""
return _check_subprocess(
["python3", "core/local_emulators.py", "contracts/microservice.yaml"],
["python3", "core/local_emulators.py", "contracts/microservice.yml"],
timeout=60,
)
@@ -284,7 +284,7 @@ def _check_local_e2e_microservice() -> Tuple[Status, str]:
def _check_local_e2e_static_assets() -> Tuple[Status, str]:
"""CAP-012: local E2E on the static-assets stack (no ECS service)."""
return _check_subprocess(
["python3", "core/local_emulators.py", "contracts/static-assets.yaml"],
["python3", "core/local_emulators.py", "contracts/static-assets.yml"],
timeout=60,
)
@@ -324,7 +324,7 @@ def _check_live_terraform_plan_microservice() -> Tuple[Status, str]:
os.makedirs(tf_dir, exist_ok=True)
rc, out, err = _run_subprocess([
"python3", "core/contract_resolver.py",
"contracts/microservice.yaml", stack_path,
"contracts/microservice.yml", stack_path,
])
if rc != 0:
return "Broken", f"resolver failed: {err.strip()[-200:]}"
@@ -364,7 +364,7 @@ def _check_live_terraform_plan_static_assets() -> Tuple[Status, str]:
os.makedirs(tf_dir, exist_ok=True)
rc, out, err = _run_subprocess([
"python3", "core/contract_resolver.py",
"contracts/static-assets.yaml", stack_path,
"contracts/static-assets.yml", stack_path,
])
if rc != 0:
return "Broken", f"resolver failed: {err.strip()[-200:]}"
+60 -42
View File
@@ -7,10 +7,10 @@ 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.
Consumers have their own repos and consume ACDL 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 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
@@ -27,13 +27,13 @@ flowchart LR
## 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
and MINOR tags** (e.g. `acdl/pipelines/contract.yml@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](pipeline/versioning) for the full rationale.
**Unversioned references are discouraged.** Do not use `@main` or a bare
`acdl/pipelines/deploy.yaml`.
`acdl/pipelines/contract.yml`.
## Prerequisites
@@ -53,7 +53,7 @@ platform-managed. See [Environments](environments/).
## 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
code; your contract lives at `.acdl/contract.yml`. Example for a static
site:
```
@@ -83,53 +83,64 @@ my-microservice/
```
Your app code lives at the top level. Your contract lives at
`.acdl/contract.yaml` regardless of the module you deploy. Your CI
`.acdl/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 contract YAML, declare `uses:` pointing at the central ACDL
deployment pipeline with a **versioned tag** (floating MAJOR + MINOR):
In your CI workflow (`.github/workflows/deploy.yml`), reference the central
ACDL deployment workflow with a **versioned tag** (floating MAJOR + MINOR):
```yaml
uses: acdl/pipelines/deploy.yaml@v1.9
jobs:
deploy:
uses: acdl/.github/workflows/deploy.yml@v1.9
with:
contract: .acdl/contract.yml
environment: dev
```
This tells the platform to run the standard deployment pipeline:
validate-contract → resolve-stack → security checks → infrastructure plan →
policy checks → confidence → evidence event → apply.
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 `.acdl/contract.yaml`. The `static-assets` example:
Write `.acdl/contract.yml`. The `static-assets` example:
```yaml
uses: acdl/pipelines/deploy.yaml@v1.9
module: static-assets
environment: dev
inputs:
bucket_name: my-static-site-assets
region: us-east-1
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:
```yaml
uses: acdl/pipelines/deploy.yaml@v1.9
module: microservice
environment: dev
inputs:
image: my-registry/my-microservice:latest
port: 8080
env:
LOG_LEVEL: info
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. `acdl/pipelines/deploy.yaml@v1.9`). Bare or `@main` references are discouraged. See [Versioning](pipeline/versioning). |
| `uses` | string | yes | Reference to the central deployment pipeline, **versioned** with a floating MAJOR+MINOR tag (e.g. `acdl/pipelines/contract.yml@v1.9`). 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). |
@@ -168,7 +179,7 @@ jobs:
deploy:
uses: acdl/.github/workflows/deploy.yml@v1.9
with:
contract: .acdl/contract.yaml
contract: .acdl/contract.yml
```
That is the entire consumer-side workflow. When you push to `main`:
@@ -181,7 +192,7 @@ That is the entire consumer-side workflow. When you push to `main`:
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`.
`.acdl/contract.yml`.
You see the streamed output (infrastructure plan, policy-check results,
confidence signal) in your run logs. The `--check-only` and `--plan-only`
@@ -202,7 +213,7 @@ static key in `.env.secrets` (gitignored) is rotated **out of band by you**
locally-held copies.
```bash
bash scripts/run_platform.sh --check-only path/to/your/.acdl/contract.yaml
bash scripts/run_platform.sh --check-only path/to/your/.acdl/contract.yml
```
## Step 5 — What the pipeline does
@@ -278,11 +289,16 @@ 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:`):
Change `environment` in your contract (the infrastructure stays the same):
```yaml
uses: acdl/pipelines/deploy.yaml@v1.9
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
@@ -305,7 +321,7 @@ per-module extension points. Common examples:
| Resource | Path | Description |
|----------|------|-------------|
| Central deployment pipeline contract | `pipelines/deploy.yaml` | The pipeline stages your contract references. |
| 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. |
@@ -339,7 +355,7 @@ destruction:
```yaml
uses: acdl/.github/workflows/deploy.yml@v1.8
with:
contract: .acdl/contract.yaml
contract: .acdl/contract.yml
mode: decommission
changeRequestId: "CHG0678912"
```
@@ -393,13 +409,15 @@ contract per environment (e.g. `.acdl/static-assets.dev.yaml`,
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}
id: assets
infrastructure:
static-assets:
inputs:
bucket_name: acdl-${env.environment}-${contract.module}-${env.account_id}-${env.region}
region: ${env.region}
version: 1.0.0
name: static-assets
```
**Shape 2 — single contract + `environment` workflow input:** the
@@ -421,7 +439,7 @@ jobs:
uses: acdl/.github/workflows/deploy.yml@v1.9
with:
environment: qa
contract: .acdl/contract.yaml
contract: .acdl/contract.yml
```
### One job per environment
+61 -28
View File
@@ -1,44 +1,57 @@
# Contracts
A consumer declares intent in a **contract** — a small YAML file that
references the central deploy pipeline, names a module, selects an
environment, and supplies module-specific inputs. The platform validates,
resolves, and deploys it.
names infrastructure (one or more modules), selects an environment, and
supplies module-specific inputs. The platform validates, resolves, and
deploys it.
## The contract file
A consumer repo keeps its contract at `.acdl/contract.yaml`. A minimal
A consumer repo keeps its contract at `.acdl/contract.yml`. A minimal
example (the `static-assets` module):
```yaml
uses: acdl/pipelines/deploy.yaml@v1.6
module: static-assets
id: assets
name: static-assets
environment: dev
inputs:
bucket_name: my-static-site-assets
region: us-east-1
infrastructure:
static-assets:
version: "1.0.0"
inputs:
bucket_name: my-static-site-assets
region: us-east-1
```
A `microservice` example:
```yaml
uses: acdl/pipelines/deploy.yaml@v1.6
module: microservice
id: msvc
name: microservice
environment: dev
inputs:
image: my-registry/my-microservice:latest
port: 8080
env:
LOG_LEVEL: info
infrastructure:
microservice:
version: "1.0.0"
inputs:
image: my-registry/my-microservice:latest
port: 8080
env:
LOG_LEVEL: info
```
## Fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `uses` | string | yes | Reference to the central deploy 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/). |
| `id` | string | yes | Short operational acronym (3-6 chars, `^[a-z][a-z0-9-]{2,5}$`). Becomes the stack name used for the Terraform state key, ECS service name, outbox event identity, and resource naming prefix. |
| `name` | string | yes | Full human-readable stack name (min 3 chars). Becomes the stack title used for display in PR comments, evidence records, and leadership dashboards. |
| `environment` | string | yes | The platform-managed environment to deploy to (`dev`/`qa`/`prod`/`dr`). See [Environments](../environments/). |
| `infrastructure` | object | yes | Map of modules to deploy, keyed by module registry name. Each entry has an optional `version` (defaults to latest published) and required `inputs`. One entry = single-module deploy; N entries = multi-module manifest deployed in one pipeline run. |
### Infrastructure entry fields
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `version` | string | no | Module version pin (semver `X.Y.Z`). Omitted = latest non-deprecated version from the registry. |
| `inputs` | object | yes | Module-specific inputs (see the module's README). |
## Validation
@@ -52,19 +65,39 @@ validate-contract stage with a clear error.
Two reference examples exist in `contracts/`:
- [`contracts/static-assets.yaml`](https://github.com/acdl/acdl/blob/main/contracts/static-assets.yaml)
— the `static-assets` module (uses `@v1.6`).
- [`contracts/microservice.yaml`](https://github.com/acdl/acdl/blob/main/contracts/microservice.yaml)
— the `microservice` module (uses `@v1.6`).
- [`contracts/static-assets.yml`](https://github.com/acdl/acdl/blob/main/contracts/static-assets.yml)
— the `static-assets` module.
- [`contracts/microservice.yml`](https://github.com/acdl/acdl/blob/main/contracts/microservice.yml)
— the `microservice` module.
Additionally, every module has a `modules/<name>/examples/` directory with
validated example contracts (`simple.yaml` + `complex.yaml` + variation
validated example contracts (`simple.yml` + `complex.yml` + variation
files). See the [module catalog](../modules/) for the full list.
## Multiple modules per contract
A contract may declare multiple modules under the `infrastructure` map.
All modules deploy to the same `environment` in one pipeline run. Resource
IDs are namespaced with the module name to avoid collisions (e.g.
`microservice-vpc`, `static-assets-s3`).
```yaml
id: app
name: pricing-service-api
environment: dev
infrastructure:
microservice:
version: "1.0.0"
inputs: { ... }
static-assets:
version: "1.0.0"
inputs: { ... }
```
## Multiple contracts
A consumer repo may contain more than one contract (e.g. one per service or
one per environment). Each contract is a separate deployment; each is
referenced by a CI definition in `.github/workflows/` that invokes the
central reusable workflow with the contract path. See the
A consumer repo may also contain more than one contract file (e.g. one per
environment). Each contract is a separate deployment; each is referenced by a
CI definition in `.github/workflows/` that invokes the central reusable
workflow with the contract path. See the
[Consumer Guide](../consumer-guide/) for the multi-contract pattern.
+1 -1
View File
@@ -16,7 +16,7 @@ There are two kinds of repository in the ACDL model:
and the reusable workflow files. Platform engineers work here. A consumer
never clones it.
- **Consumer repo (yours).** A consumer repo contains only its application
code, one or more contracts (`.acdl/contract.yaml`), and one or more CI
code, one or more contracts (`.acdl/contract.yml`), and one or more CI
definitions (a thin `.github/workflows/deploy.yml` that `uses:` the central
reusable workflow, pointing at the appropriate environment + contract).
The consumer does not write infrastructure modules, workflow YAML, or
+2 -2
View File
@@ -6,7 +6,7 @@ are the single source of truth for the workflow files.
## CI pipeline
The CI pipeline runs on every push and pull request to `main`. It is defined
by [`pipelines/ci.yaml`](https://github.com/acdl/acdl/blob/main/pipelines/ci.yaml),
by [`pipelines/ci.yml`](https://github.com/acdl/acdl/blob/main/pipelines/ci.yml),
validated against
[`schemas/pipeline.schema.json`](https://github.com/acdl/acdl/blob/main/schemas/pipeline.schema.json).
Both platform-runner workflow files implement the same contract and are
@@ -31,7 +31,7 @@ bash scripts/run_ci.sh --quiet # suppress per-stage banners
## Deployment pipeline
The deployment pipeline runs when a consumer submits a contract. It is
defined by [`pipelines/deploy.yaml`](https://github.com/acdl/acdl/blob/main/pipelines/deploy.yaml),
defined by [`pipelines/contract.yml`](https://github.com/acdl/acdl/blob/main/pipelines/contract.yml),
validated against
[`schemas/deploy-pipeline.schema.json`](https://github.com/acdl/acdl/blob/main/schemas/deploy-pipeline.schema.json).
It is exposed to consumer repos as a **reusable workflow**:
+12 -7
View File
@@ -18,21 +18,26 @@ primitives by `name@semver`; the resolver picks the highest compatible.
Module versions are tracked in
[`registry.json`](https://github.com/acdl/acdl/blob/main/modules/registry.json).
## Deploy-pipeline versioning (the `uses:` tag)
## Deploy-pipeline versioning (the CI workflow `uses:` tag)
The central deploy pipeline is referenced by a **floating MAJOR + MINOR
tag** in a consumer's contract and CI definition:
tag** in a consumer's CI workflow definition:
```yaml
uses: acdl/pipelines/deploy.yaml@v1.6
jobs:
deploy:
uses: acdl/.github/workflows/deploy.yml@v1.6
with:
contract: .acdl/contract.yml
```
Version constraints cannot be expressed inside the contract, so the tag in
`uses:` is the only immutability lever a consumer has.
The version pin lives in the CI workflow reference (not in the contract
itself — the contract no longer carries a `uses:` field). The CI workflow
`uses:` tag 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. Pinning to a MAJOR+MINOR tag means:
`acdl/.github/workflows/deploy.yml``main` is constantly updated and can
cause unexpected failures. 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
+14 -14
View File
@@ -46,8 +46,8 @@ Every L1 primitive MUST contain, at minimum:
| `interface.json` | Angine-agnostic declaration: inputs, outputs, NFRs, optional multi-resource graph. |
| `instance.json` | A concrete instance used as the adapter regression baseline. |
| `README.md` | Plain-language documentation following `README-TEMPLATE.md` (see §7). |
| `examples/simple.yaml` | A minimal contract that uses the primitive with required inputs only. |
| `examples/complex.yaml` | A contract that exercises optional inputs, NFRs, and (if applicable) the multi-resource graph. |
| `examples/simple.yml` | A minimal contract that uses the primitive with required inputs only. |
| `examples/complex.yml` | A contract that exercises optional inputs, NFRs, and (if applicable) the multi-resource graph. |
Directory layout:
@@ -57,8 +57,8 @@ modules/l1/<name>/
instance.json
README.md
examples/
simple.yaml
complex.yaml
simple.yml
complex.yml
```
### 2.2 interface.json schema
@@ -225,8 +225,8 @@ asset site behind CloudFront + WAF). It is declared by a
|------|---------|
| `composition.json` | The composition tree: children, wires, outputs, optional features. |
| `README.md` | Plain-language documentation following `README-TEMPLATE.md` (see §7). |
| `examples/simple.yaml` | A minimal contract that uses the module with required inputs only. |
| `examples/complex.yaml` | A contract that exercises optional inputs and feature flags. |
| `examples/simple.yml` | A minimal contract that uses the module with required inputs only. |
| `examples/complex.yml` | A contract that exercises optional inputs and feature flags. |
Directory layout:
@@ -235,8 +235,8 @@ modules/l2/<name>/
composition.json
README.md
examples/
simple.yaml
complex.yaml
simple.yml
complex.yml
```
There is no `instance.json` for an L2 module — the L2 is deployed by
@@ -433,8 +433,8 @@ Every module README MUST follow the structure of
8. `## Compliance extension points` — resources or behaviors that could
be added for the future compliance milestone (GDPR, SOX, SOC2,
DORA). Not implemented yet; listed so the redesign can plan for them.
9. `## Examples` — links to `examples/simple.yaml` and
`examples/complex.yaml` with a one-line description of each.
9. `## Examples` — links to `examples/simple.yml` and
`examples/complex.yml` with a one-line description of each.
10. `## Versioning` — the module's semver policy: interface MAJOR,
behavior MINOR, lifecycle PATCH. MAJOR bumps require a new
`registry.json` entry (immutable publication); old entries enter a
@@ -514,12 +514,12 @@ must be checked before the module is registered and published.
- [ ] All required files present:
- L1: `interface.json`, `instance.json`, `README.md`,
`examples/simple.yaml`, `examples/complex.yaml`.
- L2: `composition.json`, `README.md`, `examples/simple.yaml`,
`examples/complex.yaml` (no `instance.json`).
`examples/simple.yml`, `examples/complex.yml`.
- L2: `composition.json`, `README.md`, `examples/simple.yml`,
`examples/complex.yml` (no `instance.json`).
- [ ] `interface.json` (L1) / `composition.json` (L2) validates against
`schemas/stack.schema.json`.
- [ ] `examples/simple.yaml` and `examples/complex.yaml` validate
- [ ] `examples/simple.yml` and `examples/complex.yml` validate
against `schemas/contract.schema.json`.
- [ ] Module registered in `modules/registry.json` at its semver with a
full ISO 8601 `published_at` and `deprecated: false`.
+26 -21
View File
@@ -73,37 +73,42 @@ pipeline validates them against `schemas/contract.schema.json`.
A minimal deployment:
[`examples/simple.yaml`](examples/simple.yaml)
[`examples/simple.yml`](examples/simple.yml)
```yaml
uses: acdl/pipelines/deploy.yaml@v1.6
module: alb
environment: dev
inputs:
name: my-alb
subnets: subnet-aaa,subnet-bbb
security_group: sg-xxx
port: 80
protocol: HTTP
region: us-east-1
id: alb
infrastructure:
alb:
inputs:
name: my-alb
port: 80
protocol: HTTP
region: us-east-1
security_group: sg-xxx
subnets: subnet-aaa,subnet-bbb
version: 1.0.0
name: alb-loadbalancer
```
### Complex
A production deployment with optional inputs:
[`examples/complex.yaml`](examples/complex.yaml)
[`examples/complex.yml`](examples/complex.yml)
```yaml
# Complex ALB with HTTPS + ACM cert (requires a consumer-supplied domain)
uses: acdl/pipelines/deploy.yaml@v1.6
module: alb
environment: dev
inputs:
name: my-production-alb
subnets: subnet-aaa,subnet-bbb
security_group: sg-xxx
port: 443
protocol: HTTPS
region: us-east-1
id: alb
infrastructure:
alb:
inputs:
name: my-production-alb
port: 443
protocol: HTTPS
region: us-east-1
security_group: sg-xxx
subnets: subnet-aaa,subnet-bbb
version: 1.0.0
name: alb-loadbalancer
```
## Versioning
-11
View File
@@ -1,11 +0,0 @@
# Complex ALB with HTTPS + ACM cert (requires a consumer-supplied domain)
uses: acdl/pipelines/deploy.yaml@v1.6
module: alb
environment: dev
inputs:
name: my-production-alb
subnets: subnet-aaa,subnet-bbb
security_group: sg-xxx
port: 443
protocol: HTTPS
region: us-east-1
+14
View File
@@ -0,0 +1,14 @@
# Complex ALB with HTTPS + ACM cert (requires a consumer-supplied domain)
environment: dev
id: alb
infrastructure:
alb:
inputs:
name: my-production-alb
port: 443
protocol: HTTPS
region: us-east-1
security_group: sg-xxx
subnets: subnet-aaa,subnet-bbb
version: 1.0.0
name: alb-loadbalancer
-10
View File
@@ -1,10 +0,0 @@
uses: acdl/pipelines/deploy.yaml@v1.6
module: alb
environment: dev
inputs:
name: my-alb
subnets: subnet-aaa,subnet-bbb
security_group: sg-xxx
port: 80
protocol: HTTP
region: us-east-1
+13
View File
@@ -0,0 +1,13 @@
environment: dev
id: alb
infrastructure:
alb:
inputs:
name: my-alb
port: 80
protocol: HTTP
region: us-east-1
security_group: sg-xxx
subnets: subnet-aaa,subnet-bbb
version: 1.0.0
name: alb-loadbalancer
+23 -19
View File
@@ -80,35 +80,39 @@ pipeline validates them against `schemas/contract.schema.json`.
A minimal deployment:
[`examples/simple.yaml`](examples/simple.yaml)
[`examples/simple.yml`](examples/simple.yml)
```yaml
# Simple CloudFront distribution (S3 origin, no WAF)
uses: acdl/pipelines/deploy.yaml@v1.6
module: cloudfront
environment: dev
inputs:
bucket_regional_domain_name: my-bucket.s3.us-east-1.amazonaws.com
region: us-east-1
id: cdn
infrastructure:
cloudfront:
inputs:
bucket_regional_domain_name: my-bucket.s3.us-east-1.amazonaws.com
region: us-east-1
version: 1.0.0
name: cloudfront
```
### Complex
A production deployment with optional inputs:
[`examples/complex.yaml`](examples/complex.yaml)
[`examples/complex.yml`](examples/complex.yml)
```yaml
# Complex CloudFront with WAF + custom TTL + viewer protocol redirect
uses: acdl/pipelines/deploy.yaml@v1.6
module: cloudfront
environment: dev
inputs:
bucket_regional_domain_name: my-bucket.s3.us-east-1.amazonaws.com
price_class: PriceClass_100
viewer_protocol_policy: redirect-to-https
default_ttl: 3600
max_ttl: 86400
waf_web_acl_arn: arn:aws:wafv2:us-east-1:000000000000:webacl/my-waf
region: us-east-1
id: cdn
infrastructure:
cloudfront:
inputs:
bucket_regional_domain_name: my-bucket.s3.us-east-1.amazonaws.com
default_ttl: 3600
max_ttl: 86400
price_class: PriceClass_100
region: us-east-1
viewer_protocol_policy: redirect-to-https
waf_web_acl_arn: arn:aws:wafv2:us-east-1:000000000000:webacl/my-waf
version: 1.0.0
name: cloudfront
```
## Versioning
@@ -1,12 +0,0 @@
# Complex CloudFront with WAF + custom TTL + viewer protocol redirect
uses: acdl/pipelines/deploy.yaml@v1.6
module: cloudfront
environment: dev
inputs:
bucket_regional_domain_name: my-bucket.s3.us-east-1.amazonaws.com
price_class: PriceClass_100
viewer_protocol_policy: redirect-to-https
default_ttl: 3600
max_ttl: 86400
waf_web_acl_arn: arn:aws:wafv2:us-east-1:000000000000:webacl/my-waf
region: us-east-1
@@ -0,0 +1,15 @@
# Complex CloudFront with WAF + custom TTL + viewer protocol redirect
environment: dev
id: cdn
infrastructure:
cloudfront:
inputs:
bucket_regional_domain_name: my-bucket.s3.us-east-1.amazonaws.com
default_ttl: 3600
max_ttl: 86400
price_class: PriceClass_100
region: us-east-1
viewer_protocol_policy: redirect-to-https
waf_web_acl_arn: arn:aws:wafv2:us-east-1:000000000000:webacl/my-waf
version: 1.0.0
name: cloudfront
@@ -1,7 +0,0 @@
# Simple CloudFront distribution (S3 origin, no WAF)
uses: acdl/pipelines/deploy.yaml@v1.6
module: cloudfront
environment: dev
inputs:
bucket_regional_domain_name: my-bucket.s3.us-east-1.amazonaws.com
region: us-east-1
+10
View File
@@ -0,0 +1,10 @@
# Simple CloudFront distribution (S3 origin, no WAF)
environment: dev
id: cdn
infrastructure:
cloudfront:
inputs:
bucket_regional_domain_name: my-bucket.s3.us-east-1.amazonaws.com
region: us-east-1
version: 1.0.0
name: cloudfront
+18 -13
View File
@@ -60,29 +60,34 @@ pipeline validates them against `schemas/contract.schema.json`.
A minimal deployment:
[`examples/simple.yaml`](examples/simple.yaml)
[`examples/simple.yml`](examples/simple.yml)
```yaml
uses: acdl/pipelines/deploy.yaml@v1.6
module: ecr
environment: dev
inputs:
name: my-repo
region: us-east-1
id: ecr
infrastructure:
ecr:
inputs:
name: my-repo
region: us-east-1
version: 1.0.0
name: ecr-repo
```
### Complex
A production deployment with optional inputs:
[`examples/complex.yaml`](examples/complex.yaml)
[`examples/complex.yml`](examples/complex.yml)
```yaml
# Complex ECR with lifecycle policy + image scanning
uses: acdl/pipelines/deploy.yaml@v1.6
module: ecr
environment: dev
inputs:
name: my-production-repo
region: us-east-1
id: ecr
infrastructure:
ecr:
inputs:
name: my-production-repo
region: us-east-1
version: 1.0.0
name: ecr-repo
```
## Versioning
-7
View File
@@ -1,7 +0,0 @@
# Complex ECR with lifecycle policy + image scanning
uses: acdl/pipelines/deploy.yaml@v1.6
module: ecr
environment: dev
inputs:
name: my-production-repo
region: us-east-1
+10
View File
@@ -0,0 +1,10 @@
# Complex ECR with lifecycle policy + image scanning
environment: dev
id: ecr
infrastructure:
ecr:
inputs:
name: my-production-repo
region: us-east-1
version: 1.0.0
name: ecr-repo
-6
View File
@@ -1,6 +0,0 @@
uses: acdl/pipelines/deploy.yaml@v1.6
module: ecr
environment: dev
inputs:
name: my-repo
region: us-east-1
+9
View File
@@ -0,0 +1,9 @@
environment: dev
id: ecr
infrastructure:
ecr:
inputs:
name: my-repo
region: us-east-1
version: 1.0.0
name: ecr-repo
+18 -13
View File
@@ -58,29 +58,34 @@ pipeline validates them against `schemas/contract.schema.json`.
A minimal deployment:
[`examples/simple.yaml`](examples/simple.yaml)
[`examples/simple.yml`](examples/simple.yml)
```yaml
uses: acdl/pipelines/deploy.yaml@v1.6
module: ecs-cluster
environment: dev
inputs:
name: my-cluster
region: us-east-1
id: clus
infrastructure:
ecs-cluster:
inputs:
name: my-cluster
region: us-east-1
version: 1.0.0
name: ecs-cluster
```
### Complex
A production deployment with optional inputs:
[`examples/complex.yaml`](examples/complex.yaml)
[`examples/complex.yml`](examples/complex.yml)
```yaml
# Complex ECS cluster with container insights
uses: acdl/pipelines/deploy.yaml@v1.6
module: ecs-cluster
environment: dev
inputs:
name: my-production-cluster
region: us-east-1
id: clus
infrastructure:
ecs-cluster:
inputs:
name: my-production-cluster
region: us-east-1
version: 1.0.0
name: ecs-cluster
```
## Versioning
@@ -1,7 +0,0 @@
# Complex ECS cluster with container insights
uses: acdl/pipelines/deploy.yaml@v1.6
module: ecs-cluster
environment: dev
inputs:
name: my-production-cluster
region: us-east-1
@@ -0,0 +1,10 @@
# Complex ECS cluster with container insights
environment: dev
id: clus
infrastructure:
ecs-cluster:
inputs:
name: my-production-cluster
region: us-east-1
version: 1.0.0
name: ecs cluster
@@ -1,6 +0,0 @@
uses: acdl/pipelines/deploy.yaml@v1.6
module: ecs-cluster
environment: dev
inputs:
name: my-cluster
region: us-east-1
@@ -0,0 +1,9 @@
environment: dev
id: clus
infrastructure:
ecs-cluster:
inputs:
name: my-cluster
region: us-east-1
version: 1.0.0
name: ecs cluster
+25 -20
View File
@@ -80,36 +80,41 @@ pipeline validates them against `schemas/contract.schema.json`.
A minimal deployment:
[`examples/simple.yaml`](examples/simple.yaml)
[`examples/simple.yml`](examples/simple.yml)
```yaml
uses: acdl/pipelines/deploy.yaml@v1.6
module: ecs-service
environment: dev
inputs:
name: my-service
region: us-east-1
image: public.ecr.aws/docker/library/nginx:latest
port: 80
id: svc
infrastructure:
ecs-service:
inputs:
image: public.ecr.aws/docker/library/nginx:latest
name: my-service
port: 80
region: us-east-1
version: 1.0.0
name: ecs-service
```
### Complex
A production deployment with optional inputs:
[`examples/complex.yaml`](examples/complex.yaml)
[`examples/complex.yml`](examples/complex.yml)
```yaml
# Complex ECS service with env vars + health check
uses: acdl/pipelines/deploy.yaml@v1.6
module: ecs-service
environment: dev
inputs:
name: my-production-service
region: us-east-1
image: public.ecr.aws/docker/library/nginx:latest
port: 8080
env:
LOG_LEVEL: info
ENVIRONMENT: production
id: svc
infrastructure:
ecs-service:
inputs:
env:
ENVIRONMENT: production
LOG_LEVEL: info
image: public.ecr.aws/docker/library/nginx:latest
name: my-production-service
port: 8080
region: us-east-1
version: 1.0.0
name: ecs-service
```
## Versioning
@@ -1,12 +0,0 @@
# Complex ECS service with env vars + health check
uses: acdl/pipelines/deploy.yaml@v1.6
module: ecs-service
environment: dev
inputs:
name: my-production-service
region: us-east-1
image: public.ecr.aws/docker/library/nginx:latest
port: 8080
env:
LOG_LEVEL: info
ENVIRONMENT: production
@@ -0,0 +1,15 @@
# Complex ECS service with env vars + health check
environment: dev
id: svc
infrastructure:
ecs-service:
inputs:
env:
ENVIRONMENT: production
LOG_LEVEL: info
image: public.ecr.aws/docker/library/nginx:latest
name: my-production-service
port: 8080
region: us-east-1
version: 1.0.0
name: ecs service
@@ -1,8 +0,0 @@
uses: acdl/pipelines/deploy.yaml@v1.6
module: ecs-service
environment: dev
inputs:
name: my-service
region: us-east-1
image: public.ecr.aws/docker/library/nginx:latest
port: 80
@@ -0,0 +1,11 @@
environment: dev
id: svc
infrastructure:
ecs-service:
inputs:
image: public.ecr.aws/docker/library/nginx:latest
name: my-service
port: 80
region: us-east-1
version: 1.0.0
name: ecs service
+18 -13
View File
@@ -66,29 +66,34 @@ pipeline validates them against `schemas/contract.schema.json`.
A minimal deployment:
[`examples/simple.yaml`](examples/simple.yaml)
[`examples/simple.yml`](examples/simple.yml)
```yaml
uses: acdl/pipelines/deploy.yaml@v1.6
module: iam-role
environment: dev
inputs:
name: my-task-role
region: us-east-1
id: role
infrastructure:
iam-role:
inputs:
name: my-task-role
region: us-east-1
version: 1.0.0
name: iam-role
```
### Complex
A production deployment with optional inputs:
[`examples/complex.yaml`](examples/complex.yaml)
[`examples/complex.yml`](examples/complex.yml)
```yaml
# Complex IAM role with managed policies
uses: acdl/pipelines/deploy.yaml@v1.6
module: iam-role
environment: dev
inputs:
name: my-production-task-role
region: us-east-1
id: role
infrastructure:
iam-role:
inputs:
name: my-production-task-role
region: us-east-1
version: 1.0.0
name: iam-role
```
## Versioning
@@ -1,7 +0,0 @@
# Complex IAM role with managed policies
uses: acdl/pipelines/deploy.yaml@v1.6
module: iam-role
environment: dev
inputs:
name: my-production-task-role
region: us-east-1
+10
View File
@@ -0,0 +1,10 @@
# Complex IAM role with managed policies
environment: dev
id: role
infrastructure:
iam-role:
inputs:
name: my-production-task-role
region: us-east-1
version: 1.0.0
name: iam role
-6
View File
@@ -1,6 +0,0 @@
uses: acdl/pipelines/deploy.yaml@v1.6
module: iam-role
environment: dev
inputs:
name: my-task-role
region: us-east-1
+9
View File
@@ -0,0 +1,9 @@
environment: dev
id: role
infrastructure:
iam-role:
inputs:
name: my-task-role
region: us-east-1
version: 1.0.0
name: iam role
+19 -13
View File
@@ -67,29 +67,35 @@ pipeline validates them against `schemas/contract.schema.json`.
A minimal deployment:
[`examples/simple.yaml`](examples/simple.yaml)
[`examples/simple.yml`](examples/simple.yml)
```yaml
uses: acdl/pipelines/deploy.yaml@v1.8
module: kms-key
environment: dev
inputs:
description: "Simple CMK for testing"
region: us-east-1
id: kms
infrastructure:
kms-key:
inputs:
description: Simple CMK for testing
region: us-east-1
version: 1.0.0
name: kms-key
```
### Complex
A production deployment with optional inputs:
[`examples/complex.yaml`](examples/complex.yaml)
[`examples/complex.yml`](examples/complex.yml)
```yaml
uses: acdl/pipelines/deploy.yaml@v1.8
module: kms-key
environment: dev
inputs:
description: "Production CMK with 90-day deletion window"
region: us-east-1
deletion_window_days: 90
id: kms
infrastructure:
kms-key:
inputs:
deletion_window_days: 90
description: Production CMK with 90-day deletion window
region: us-east-1
version: 1.0.0
name: kms-key
```
## Versioning
-7
View File
@@ -1,7 +0,0 @@
uses: acdl/pipelines/deploy.yaml@v1.8
module: kms-key
environment: dev
inputs:
description: "Production CMK with 90-day deletion window"
region: us-east-1
deletion_window_days: 90
+10
View File
@@ -0,0 +1,10 @@
environment: dev
id: kms
infrastructure:
kms-key:
inputs:
deletion_window_days: 90
description: Production CMK with 90-day deletion window
region: us-east-1
version: 1.0.0
name: kms key
-6
View File
@@ -1,6 +0,0 @@
uses: acdl/pipelines/deploy.yaml@v1.8
module: kms-key
environment: dev
inputs:
description: "Simple CMK for testing"
region: us-east-1
+9
View File
@@ -0,0 +1,9 @@
environment: dev
id: kms
infrastructure:
kms-key:
inputs:
description: Simple CMK for testing
region: us-east-1
version: 1.0.0
name: kms key
+45 -39
View File
@@ -95,42 +95,46 @@ pipeline validates them against `schemas/contract.schema.json`.
A minimal deployment:
[`examples/simple.yaml`](examples/simple.yaml)
[`examples/simple.yml`](examples/simple.yml)
```yaml
# Simple RDS postgres instance
uses: acdl/pipelines/deploy.yaml@v1.6
module: rds
environment: dev
inputs:
engine: postgres
engine_version: "16.4"
instance_class: db.t3.micro
allocated_storage: 20
db_name: my_app_db
username: db_admin
region: us-east-1
id: rds
infrastructure:
rds:
inputs:
allocated_storage: 20
db_name: my_app_db
engine: postgres
engine_version: '16.4'
instance_class: db.t3.micro
region: us-east-1
username: db_admin
version: 1.0.0
name: rds-instance
```
### Complex
A production deployment with optional inputs:
[`examples/complex.yaml`](examples/complex.yaml)
[`examples/complex.yml`](examples/complex.yml)
```yaml
# Complex RDS postgres with multi-AZ + encryption
uses: acdl/pipelines/deploy.yaml@v1.6
module: rds
environment: dev
inputs:
engine: postgres
engine_version: "16.4"
instance_class: db.r6g.large
allocated_storage: 100
db_name: my_production_db
username: db_admin
multi_az: true
storage_encrypted: true
region: us-east-1
id: rds
infrastructure:
rds:
inputs:
allocated_storage: 100
db_name: my_production_db
engine: postgres
engine_version: '16.4'
instance_class: db.r6g.large
multi_az: true
region: us-east-1
storage_encrypted: true
username: db_admin
version: 1.0.0
name: rds-instance
```
### Multi-engine variation
@@ -140,25 +144,27 @@ the `engine_version` must match.
#### PostgreSQL
[`examples/simple.yaml`](examples/simple.yaml) — postgres 16.4
[`examples/simple.yml`](examples/simple.yml) — postgres 16.4
#### MySQL
[`examples/mysql.yaml`](examples/mysql.yaml) — mysql 8.4
[`examples/mysql.yml`](examples/mysql.yml) — mysql 8.4
```yaml
# RDS mysql variation
uses: acdl/pipelines/deploy.yaml@v1.6
module: rds
environment: dev
inputs:
engine: mysql
engine_version: "8.4"
instance_class: db.t3.micro
allocated_storage: 20
db_name: my_mysql_db
username: db_admin
region: us-east-1
id: rds
infrastructure:
rds:
inputs:
allocated_storage: 20
db_name: my_mysql_db
engine: mysql
engine_version: '8.4'
instance_class: db.t3.micro
region: us-east-1
username: db_admin
version: 1.0.0
name: rds-instance
```
## Versioning
-14
View File
@@ -1,14 +0,0 @@
# Complex RDS postgres with multi-AZ + encryption
uses: acdl/pipelines/deploy.yaml@v1.6
module: rds
environment: dev
inputs:
engine: postgres
engine_version: "16.4"
instance_class: db.r6g.large
allocated_storage: 100
db_name: my_production_db
username: db_admin
multi_az: true
storage_encrypted: true
region: us-east-1
+17
View File
@@ -0,0 +1,17 @@
# Complex RDS postgres with multi-AZ + encryption
environment: dev
id: rds
infrastructure:
rds:
inputs:
allocated_storage: 100
db_name: my_production_db
engine: postgres
engine_version: '16.4'
instance_class: db.r6g.large
multi_az: true
region: us-east-1
storage_encrypted: true
username: db_admin
version: 1.0.0
name: rds-instance
-12
View File
@@ -1,12 +0,0 @@
# RDS mysql variation
uses: acdl/pipelines/deploy.yaml@v1.6
module: rds
environment: dev
inputs:
engine: mysql
engine_version: "8.4"
instance_class: db.t3.micro
allocated_storage: 20
db_name: my_mysql_db
username: db_admin
region: us-east-1
+15
View File
@@ -0,0 +1,15 @@
# RDS mysql variation
environment: dev
id: rds
infrastructure:
rds:
inputs:
allocated_storage: 20
db_name: my_mysql_db
engine: mysql
engine_version: '8.4'
instance_class: db.t3.micro
region: us-east-1
username: db_admin
version: 1.0.0
name: rds-instance
-12
View File
@@ -1,12 +0,0 @@
# Simple RDS postgres instance
uses: acdl/pipelines/deploy.yaml@v1.6
module: rds
environment: dev
inputs:
engine: postgres
engine_version: "16.4"
instance_class: db.t3.micro
allocated_storage: 20
db_name: my_app_db
username: db_admin
region: us-east-1
+15
View File
@@ -0,0 +1,15 @@
# Simple RDS postgres instance
environment: dev
id: rds
infrastructure:
rds:
inputs:
allocated_storage: 20
db_name: my_app_db
engine: postgres
engine_version: '16.4'
instance_class: db.t3.micro
region: us-east-1
username: db_admin
version: 1.0.0
name: rds-instance
+18 -12
View File
@@ -65,28 +65,34 @@ pipeline validates them against `schemas/contract.schema.json`.
A minimal deployment:
[`examples/simple.yaml`](examples/simple.yaml)
[`examples/simple.yml`](examples/simple.yml)
```yaml
uses: acdl/pipelines/deploy.yaml@v1.6
module: s3
environment: dev
inputs:
bucket_name: my-simple-bucket
region: us-east-1
id: s3a
infrastructure:
s3:
inputs:
bucket_name: my-simple-bucket
region: us-east-1
version: 1.0.0
name: s3-bucket
```
### Complex
A production deployment with optional inputs:
[`examples/complex.yaml`](examples/complex.yaml)
[`examples/complex.yml`](examples/complex.yml)
```yaml
uses: acdl/pipelines/deploy.yaml@v1.6
module: s3
environment: dev
inputs:
bucket_name: my-production-bucket
region: us-east-1
id: s3a
infrastructure:
s3:
inputs:
bucket_name: my-production-bucket
region: us-east-1
version: 1.0.0
name: s3-bucket
```
> **Note:** the s3 primitive's compliance extensions (Object Lock, access
-6
View File
@@ -1,6 +0,0 @@
uses: acdl/pipelines/deploy.yaml@v1.6
module: s3
environment: dev
inputs:
bucket_name: my-production-bucket
region: us-east-1
+9
View File
@@ -0,0 +1,9 @@
environment: dev
id: s3a
infrastructure:
s3:
inputs:
bucket_name: my-production-bucket
region: us-east-1
version: 1.0.0
name: s3-bucket
-6
View File
@@ -1,6 +0,0 @@
uses: acdl/pipelines/deploy.yaml@v1.6
module: s3
environment: dev
inputs:
bucket_name: my-simple-bucket
region: us-east-1
+9
View File
@@ -0,0 +1,9 @@
environment: dev
id: s3a
infrastructure:
s3:
inputs:
bucket_name: my-simple-bucket
region: us-east-1
version: 1.0.0
name: s3-bucket
+50 -44
View File
@@ -87,14 +87,17 @@ pipeline validates them against `schemas/contract.schema.json`.
A minimal deployment:
[`examples/simple.yaml`](examples/simple.yaml)
[`examples/simple.yml`](examples/simple.yml)
```yaml
uses: acdl/pipelines/deploy.yaml@v1.8
module: uptime
environment: dev
inputs:
region: us-east-1
feature_flag_enabled: true
id: up1
infrastructure:
uptime:
inputs:
feature_flag_enabled: true
region: us-east-1
version: 1.0.0
name: uptime-monitor
```
### Complex
@@ -102,46 +105,49 @@ inputs:
A production deployment with monitored endpoints, static checks, and
multiple alert channels:
[`examples/complex.yaml`](examples/complex.yaml)
[`examples/complex.yml`](examples/complex.yml)
```yaml
uses: acdl/pipelines/deploy.yaml@v1.8
module: uptime
environment: dev
inputs:
region: us-east-1
feature_flag_enabled: true
cpu: 512
memory: 1024
monitored_endpoints:
- name: "api-health"
url: "https://api.example.com/health"
type: "http"
interval_seconds: 30
timeout_seconds: 10
- name: "dns-check"
url: "example.com"
type: "dns"
interval_seconds: 60
timeout_seconds: 10
- name: "tcp-check"
url: "db.example.com:5432"
type: "tcp"
interval_seconds: 60
timeout_seconds: 10
static_checks:
- name: "google"
url: "https://google.com"
type: "http"
interval_seconds: 60
timeout_seconds: 10
alert_channels:
teams_webhook: "https://hooks.example.com/teams/webhook"
email_addresses:
- "oncall@example.com"
- "sre@example.com"
sms_numbers:
- "+1234567890"
github_issue_repo: "acdl/acdl"
id: up1
infrastructure:
uptime:
inputs:
alert_channels:
email_addresses:
- oncall@example.com
- sre@example.com
github_issue_repo: acdl/acdl
sms_numbers:
- '+1234567890'
teams_webhook: https://hooks.example.com/teams/webhook
cpu: 512
feature_flag_enabled: true
memory: 1024
monitored_endpoints:
- interval_seconds: 30
name: api-health
timeout_seconds: 10
type: http
url: https://api.example.com/health
- interval_seconds: 60
name: dns-check
timeout_seconds: 10
type: dns
url: example.com
- interval_seconds: 60
name: tcp-check
timeout_seconds: 10
type: tcp
url: db.example.com:5432
region: us-east-1
static_checks:
- interval_seconds: 60
name: google
timeout_seconds: 10
type: http
url: https://google.com
version: 1.0.0
name: uptime-monitor
```
## Versioning
-38
View File
@@ -1,38 +0,0 @@
uses: acdl/pipelines/deploy.yaml@v1.8
module: uptime
environment: dev
inputs:
region: us-east-1
feature_flag_enabled: true
cpu: 512
memory: 1024
monitored_endpoints:
- name: "api-health"
url: "https://api.example.com/health"
type: "http"
interval_seconds: 30
timeout_seconds: 10
- name: "dns-check"
url: "example.com"
type: "dns"
interval_seconds: 60
timeout_seconds: 10
- name: "tcp-check"
url: "db.example.com:5432"
type: "tcp"
interval_seconds: 60
timeout_seconds: 10
static_checks:
- name: "google"
url: "https://google.com"
type: "http"
interval_seconds: 60
timeout_seconds: 10
alert_channels:
teams_webhook: "https://hooks.example.com/teams/webhook"
email_addresses:
- "oncall@example.com"
- "sre@example.com"
sms_numbers:
- "+1234567890"
github_issue_repo: "acdl/acdl"
+41
View File
@@ -0,0 +1,41 @@
environment: dev
id: up1
infrastructure:
uptime:
inputs:
alert_channels:
email_addresses:
- oncall@example.com
- sre@example.com
github_issue_repo: acdl/acdl
sms_numbers:
- '+1234567890'
teams_webhook: https://hooks.example.com/teams/webhook
cpu: 512
feature_flag_enabled: true
memory: 1024
monitored_endpoints:
- interval_seconds: 30
name: api-health
timeout_seconds: 10
type: http
url: https://api.example.com/health
- interval_seconds: 60
name: dns-check
timeout_seconds: 10
type: dns
url: example.com
- interval_seconds: 60
name: tcp-check
timeout_seconds: 10
type: tcp
url: db.example.com:5432
region: us-east-1
static_checks:
- interval_seconds: 60
name: google
timeout_seconds: 10
type: http
url: https://google.com
version: 1.0.0
name: uptime-monitor
-6
View File
@@ -1,6 +0,0 @@
uses: acdl/pipelines/deploy.yaml@v1.8
module: uptime
environment: dev
inputs:
region: us-east-1
feature_flag_enabled: true
+9
View File
@@ -0,0 +1,9 @@
environment: dev
id: up1
infrastructure:
uptime:
inputs:
feature_flag_enabled: true
region: us-east-1
version: 1.0.0
name: uptime-monitor
+22 -17
View File
@@ -69,33 +69,38 @@ pipeline validates them against `schemas/contract.schema.json`.
A minimal deployment:
[`examples/simple.yaml`](examples/simple.yaml)
[`examples/simple.yml`](examples/simple.yml)
```yaml
uses: acdl/pipelines/deploy.yaml@v1.6
module: vpc
environment: dev
inputs:
cidr: 10.0.0.0/16
azs: us-east-1a,us-east-1b
name: my-vpc
region: us-east-1
id: vpc
infrastructure:
vpc:
inputs:
azs: us-east-1a,us-east-1b
cidr: 10.0.0.0/16
name: my-vpc
region: us-east-1
version: 1.0.0
name: vpc-network
```
### Complex
A production deployment with optional inputs:
[`examples/complex.yaml`](examples/complex.yaml)
[`examples/complex.yml`](examples/complex.yml)
```yaml
# Complex VPC with 3 AZs and a custom CIDR
uses: acdl/pipelines/deploy.yaml@v1.6
module: vpc
environment: dev
inputs:
cidr: 10.50.0.0/16
azs: us-east-1a,us-east-1b,us-east-1c
name: my-production-vpc
region: us-east-1
id: vpc
infrastructure:
vpc:
inputs:
azs: us-east-1a,us-east-1b,us-east-1c
cidr: 10.50.0.0/16
name: my-production-vpc
region: us-east-1
version: 1.0.0
name: vpc-network
```
## Versioning
-9
View File
@@ -1,9 +0,0 @@
# Complex VPC with 3 AZs and a custom CIDR
uses: acdl/pipelines/deploy.yaml@v1.6
module: vpc
environment: dev
inputs:
cidr: 10.50.0.0/16
azs: us-east-1a,us-east-1b,us-east-1c
name: my-production-vpc
region: us-east-1
+12
View File
@@ -0,0 +1,12 @@
# Complex VPC with 3 AZs and a custom CIDR
environment: dev
id: vpc
infrastructure:
vpc:
inputs:
azs: us-east-1a,us-east-1b,us-east-1c
cidr: 10.50.0.0/16
name: my-production-vpc
region: us-east-1
version: 1.0.0
name: vpc-network
-8
View File
@@ -1,8 +0,0 @@
uses: acdl/pipelines/deploy.yaml@v1.6
module: vpc
environment: dev
inputs:
cidr: 10.0.0.0/16
azs: us-east-1a,us-east-1b
name: my-vpc
region: us-east-1
+11
View File
@@ -0,0 +1,11 @@
environment: dev
id: vpc
infrastructure:
vpc:
inputs:
azs: us-east-1a,us-east-1b
cidr: 10.0.0.0/16
name: my-vpc
region: us-east-1
version: 1.0.0
name: vpc-network
+20 -16
View File
@@ -69,32 +69,36 @@ pipeline validates them against `schemas/contract.schema.json`.
A minimal deployment:
[`examples/simple.yaml`](examples/simple.yaml)
[`examples/simple.yml`](examples/simple.yml)
```yaml
# Simple WAF with managed rules only
uses: acdl/pipelines/deploy.yaml@v1.6
module: waf
environment: dev
inputs:
name: my-waf
region: us-east-1
id: waf
infrastructure:
waf:
inputs:
name: my-waf
region: us-east-1
version: 1.0.0
name: waf-firewall
```
### Complex
A production deployment with optional inputs:
[`examples/complex.yaml`](examples/complex.yaml)
[`examples/complex.yml`](examples/complex.yml)
```yaml
# Complex WAF with rate limiting + geo blocking (custom rules)
uses: acdl/pipelines/deploy.yaml@v1.6
module: waf
environment: dev
inputs:
name: my-production-waf
scope: cloudfront
default_action: allow
region: us-east-1
id: waf
infrastructure:
waf:
inputs:
default_action: allow
name: my-production-waf
region: us-east-1
scope: cloudfront
version: 1.0.0
name: waf-firewall
```
## Versioning
-9
View File
@@ -1,9 +0,0 @@
# Complex WAF with rate limiting + geo blocking (custom rules)
uses: acdl/pipelines/deploy.yaml@v1.6
module: waf
environment: dev
inputs:
name: my-production-waf
scope: cloudfront
default_action: allow
region: us-east-1
+12
View File
@@ -0,0 +1,12 @@
# Complex WAF with rate limiting + geo blocking (custom rules)
environment: dev
id: waf
infrastructure:
waf:
inputs:
default_action: allow
name: my-production-waf
region: us-east-1
scope: cloudfront
version: 1.0.0
name: waf-firewall
-7
View File
@@ -1,7 +0,0 @@
# Simple WAF with managed rules only
uses: acdl/pipelines/deploy.yaml@v1.6
module: waf
environment: dev
inputs:
name: my-waf
region: us-east-1
+10
View File
@@ -0,0 +1,10 @@
# Simple WAF with managed rules only
environment: dev
id: waf
infrastructure:
waf:
inputs:
name: my-waf
region: us-east-1
version: 1.0.0
name: waf-firewall
+34 -27
View File
@@ -41,13 +41,16 @@ The pattern references these primitives:
Define a contract referencing this module:
```yaml
uses: acdl/pipelines/deploy.yaml@v1.6
module: microservice
environment: dev
inputs:
image: 581513795199.dkr.ecr.us-east-1.amazonaws.com/acdl-microservice:latest
port: 8080
region: us-east-1
id: msvc
infrastructure:
microservice:
inputs:
image: 581513795199.dkr.ecr.us-east-1.amazonaws.com/acdl-microservice:latest
port: 8080
region: us-east-1
version: 1.0.0
name: microservice
```
## Compliance extension points
@@ -72,37 +75,41 @@ pipeline validates them against `schemas/contract.schema.json`.
A minimal deployment (minimal Fargate, no ALB):
[`examples/simple.yaml`](examples/simple.yaml)
[`examples/simple.yml`](examples/simple.yml)
```yaml
# Simple microservice deployment (minimal Fargate, no ALB)
uses: acdl/pipelines/deploy.yaml@v1.6
module: microservice
environment: dev
inputs:
bucket_name: my-microservice-demo
region: us-east-1
image: public.ecr.aws/docker/library/nginx:latest
port: 80
id: msvc
infrastructure:
microservice:
inputs:
bucket_name: my-microservice-demo
image: public.ecr.aws/docker/library/nginx:latest
port: 80
region: us-east-1
version: 1.0.0
name: microservice
```
### Complex
A production deployment with optional inputs (ALB + env vars + health check):
[`examples/complex.yaml`](examples/complex.yaml)
[`examples/complex.yml`](examples/complex.yml)
```yaml
# Complex microservice with ALB + env vars + health check
uses: acdl/pipelines/deploy.yaml@v1.6
module: microservice
environment: dev
inputs:
bucket_name: my-production-microservice
region: us-east-1
image: public.ecr.aws/docker/library/nginx:latest
port: 8080
env:
LOG_LEVEL: info
ENVIRONMENT: production
id: msvc
infrastructure:
microservice:
inputs:
bucket_name: my-production-microservice
env:
ENVIRONMENT: production
LOG_LEVEL: info
image: public.ecr.aws/docker/library/nginx:latest
port: 8080
region: us-east-1
version: 1.0.0
name: microservice
```
## Versioning
@@ -1,12 +0,0 @@
# Complex microservice with ALB + env vars + health check
uses: acdl/pipelines/deploy.yaml@v1.6
module: microservice
environment: dev
inputs:
bucket_name: my-production-microservice
region: us-east-1
image: public.ecr.aws/docker/library/nginx:latest
port: 8080
env:
LOG_LEVEL: info
ENVIRONMENT: production

Some files were not shown because too many files have changed in this diff Show More