2682719f24
Presentation changes (both Marp decks + source markdown): 1. Title slide: deck title as H1 (slightly bigger), 'Agentic Cloud Delivery Platform' as H3 subtitle — cleaner title hierarchy 2. DX deck: removed Local Reproducibility slide (not beneficial for DX) 3. DX deck: Safe Promotion Path slide redesigned with side-by-side layout for Approaches A and B (HTML table, two columns) 4. DX deck: 'an agent' → 'an AI agent' (slide 2 + Citizen Developer slide) 5. DX deck: What a Developer Does — diagram floated to the right side 6. Header simplified to just the deck name (subtitle now on title slide) HIPAA removal (25 files): - Completely removed all HIPAA references from all markdown documentation, presentation source files, module READMEs, and rendered HTML - Removed HIPAA from compliance milestone lists (GDPR, SOX, SOC2, DORA remain) - Removed HIPAA section references (§164.xxx) from compliance annotations - Cleaned up empty parentheses and broken commas left by removal - Re-rendered both HTML decks from updated Marp source ---ci--- phase: 47 milestone: v1.9 status: complete requirements: covered: [] partial: [] ---/ci---
588 lines
25 KiB
Markdown
588 lines
25 KiB
Markdown
# ACDL Module Engineering Standards
|
||
|
||
Standards for authoring and reviewing ACDL modules. These standards
|
||
govern the two module tiers — **L1 primitives** (single cloud resource
|
||
or small group of related resources) and **L2 modules** (compositions
|
||
that reference L1 primitives to deploy a complete stack) — and the
|
||
substrate adapter that compiles them to Terraform. They are written for
|
||
**platform engineers** and **AI agents** that author or review new
|
||
modules against the existing corpus (12 L1 primitives and 2 L2 modules
|
||
shipped in v1.8).
|
||
|
||
A module that fails any section below is not ready to publish.
|
||
|
||
## 1. Overview
|
||
|
||
These standards codify the conventions already established by the
|
||
shipped modules (`s3`, `vpc`, `ecs-cluster`, `ecs-service`, `iam-role`,
|
||
`alb`, `ecr`, `cloudfront`, `waf`, `rds`, `kms-key`, `uptime`; the L2
|
||
modules `static-assets` and `microservice`). They exist so that:
|
||
|
||
- platform engineers can review a new module against a fixed checklist;
|
||
- AI agents authoring modules produce code that passes review without
|
||
iteration; and
|
||
- the substrate adapter (`adapters/terraform/adapter.py`) can compile a
|
||
module instance with no module-specific code in the adapter beyond the
|
||
three tables in §8.
|
||
|
||
When this document and an existing module disagree, the existing module
|
||
is the authority for v1.x. A change to this document is a MINOR version
|
||
bump of the standards; a change that breaks shipped modules is a MAJOR
|
||
bump and requires a migration plan.
|
||
|
||
## 2. L1 Primitive Standards
|
||
|
||
An L1 primitive is a single cloud resource or a small group of related
|
||
resources (e.g. a VPC with subnets and a route table). It is declared by
|
||
an `interface.json` and realized by the substrate adapter; it does not
|
||
own Terraform code.
|
||
|
||
### 2.1 Required files
|
||
|
||
Every L1 primitive MUST contain, at minimum:
|
||
|
||
| File | Purpose |
|
||
|------|---------|
|
||
| `interface.json` | Substrate-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. |
|
||
|
||
Directory layout:
|
||
|
||
```
|
||
modules/l1/<name>/
|
||
interface.json
|
||
instance.json
|
||
README.md
|
||
examples/
|
||
simple.yaml
|
||
complex.yaml
|
||
```
|
||
|
||
### 2.2 interface.json schema
|
||
|
||
`interface.json` MUST be a JSON object with the following required
|
||
fields:
|
||
|
||
| Field | Type | Constraint |
|
||
|-------|------|------------|
|
||
| `name` | string | `^[a-z][a-z0-9-]*$`; MUST match the module folder name. |
|
||
| `version` | string | Semver (`^\d+\d+\.\d+$`); MUST match the registry entry semver. |
|
||
| `kind` | string | Literal `"l1"`. |
|
||
| `type` | string | Stack type in `aws:<service>:<kind>` format (see §2.7). |
|
||
| `description` | string | One or two sentences in plain language; no Terraform jargon. |
|
||
| `inputs` | object | Keyed by input name; each value is an input declaration (§2.3). MAY be empty. |
|
||
| `outputs` | object | Keyed by output name; each value is an output declaration (§2.4). MAY be empty. |
|
||
| `nfrs` | object | Keyed by NFR name; each value is an NFR declaration (§2.5). MUST include `deletion_protection` and `encryption_enabled`. |
|
||
|
||
Optional fields for multi-resource primitives:
|
||
|
||
| Field | Type | Constraint |
|
||
|-------|------|------------|
|
||
| `resources` | array | One entry per distinct cloud resource; see §2.6. |
|
||
| `intra_refs` | array | Internal wiring between resources; see §2.6. |
|
||
|
||
A primitive that creates a single resource (e.g. `s3`, `iam-role`,
|
||
`rds`, `kms-key`) omits `resources` and `intra_refs`; its `type` field
|
||
is the single resource's stack type. A primitive that creates a small
|
||
group of related resources (e.g. `vpc`, `alb`, `cloudfront`) declares
|
||
`resources[]` with one entry per resource and `intra_refs[]` for the
|
||
internal wiring; its `type` field is the *primary* resource's stack
|
||
type.
|
||
|
||
### 2.3 Input declaration
|
||
|
||
Each entry in `inputs` is an object:
|
||
|
||
| Field | Type | Required | Notes |
|
||
|-------|------|----------|-------|
|
||
| `type` | string | yes | One of: `string`, `number`, `boolean`, `array`, `object`. |
|
||
| `description` | string | yes | Plain language; no Terraform jargon. |
|
||
| `required` | boolean | yes | `true` if the consumer MUST supply this input. |
|
||
| `default` | (any) | no | Present only when `required` is `false`. MUST match the declared `type`. |
|
||
| `enum` | array | no | Allowed values for `string`/`number` inputs (e.g. RDS `engine`). |
|
||
|
||
`region` is a required `string` input on every primitive that creates a
|
||
regional resource. Global resources (e.g. CloudFront) still declare
|
||
`region` because the provider region is used for child resources (the
|
||
OAC in the `cloudfront` case).
|
||
|
||
Every primitive that holds at-rest data MUST declare an optional
|
||
`kms_key_arn` input (`string`, `required: false`); see §4.
|
||
|
||
### 2.4 Output declaration
|
||
|
||
Each entry in `outputs` is an object:
|
||
|
||
| Field | Type | Required | Notes |
|
||
|-------|------|----------|-------|
|
||
| `type` | string | yes | `arn` for ARN outputs; `string` for all others. |
|
||
| `description` | string | yes | Plain language. |
|
||
|
||
Use `arn` (not `string`) for any output that returns an AWS ARN — the
|
||
adapter and policy engine key off the `arn` type to apply ARN-scoped
|
||
rules.
|
||
|
||
### 2.5 NFR declaration
|
||
|
||
Each entry in `nfrs` is an object:
|
||
|
||
| Field | Type | Required | Notes |
|
||
|-------|------|----------|-------|
|
||
| `type` | string | yes | One of: `string`, `number`, `boolean`. |
|
||
| `description` | string | yes | Plain language. |
|
||
| `default` | (any) | yes | MUST match the declared `type`. NFRs always have a default. |
|
||
|
||
Mandatory NFRs on every L1:
|
||
|
||
| NFR | Type | Default | Notes |
|
||
|-----|------|---------|-------|
|
||
| `deletion_protection` | boolean | `true` | See §5. |
|
||
| `encryption_enabled` | boolean | `true` | See §4. |
|
||
|
||
A primitive for which an NFR does not conceptually apply (e.g. an IAM
|
||
role has no at-rest data) still declares it with `default: true` and a
|
||
description noting the non-applicability, so the standards check and the
|
||
adapter emit logic stay uniform. The shipped `iam-role` primitive is the
|
||
reference for this case.
|
||
|
||
Additional NFRs are encouraged where they carry operational meaning
|
||
(e.g. `s3.versioning`, `rds.backup_retention_period`,
|
||
`kms-key.enable_rotation`, `vpc.flow_logs_encrypted`). Name them in
|
||
lowercase snake_case.
|
||
|
||
### 2.6 Multi-resource pattern
|
||
|
||
A primitive that creates more than one cloud resource (e.g. `vpc`
|
||
creates `aws_vpc` + `aws_subnet` + `aws_route_table`; `alb` creates
|
||
`aws_lb` + `aws_lb_target_group` + `aws_lb_listener`; `cloudfront`
|
||
creates `aws_cloudfront_distribution` +
|
||
`aws_cloudfront_origin_access_control`) declares a `resources` array.
|
||
|
||
Each `resources[]` entry:
|
||
|
||
| Field | Type | Notes |
|
||
|-------|------|-------|
|
||
| `type` | string | The resource's stack type (`aws:<service>:<kind>`). |
|
||
| `description` | string | Plain language. |
|
||
| `inputs` | array | Names (strings) of inputs from the top-level `inputs` object that this resource consumes. |
|
||
| `outputs` | array | Names (strings) of outputs from the top-level `outputs` object that this resource produces. |
|
||
|
||
The top-level `inputs`/`outputs` objects remain the single source of
|
||
truth; `resources[].inputs` and `resources[].outputs` are arrays of
|
||
*names* referencing those objects, not re-declarations.
|
||
|
||
`intra_refs[]` wires outputs of one resource to inputs of another
|
||
within the same primitive. Each entry:
|
||
|
||
| Field | Type | Notes |
|
||
|-------|------|-------|
|
||
| `from` | string | `<resource-type>.<output-name>` — the producing side. |
|
||
| `to` | string | `<resource-type>.<input-name>` — the consuming side. |
|
||
|
||
Reference: `cloudfront/interface.json` declares an intra-ref from
|
||
`aws:cloudfront:distribution.oac_id` to
|
||
`aws:cloudfront:originaccesscontrol.oac_id`; `vpc/interface.json`
|
||
declares intra-refs from the subnet and route table to the VPC's
|
||
`vpc_id`.
|
||
|
||
### 2.7 Naming and stack types
|
||
|
||
- Module folder names and `interface.json` `name` values MUST match
|
||
`^[a-z][a-z0-9-]*$` (lowercase, hyphenated, leading letter). Examples:
|
||
`s3`, `ecs-cluster`, `kms-key`, `iam-role`, `uptime`.
|
||
- Input and output names are lowercase snake_case.
|
||
- Stack types follow `aws:<service>:<kind>`:
|
||
- `aws:s3:bucket`
|
||
- `aws:ec2:vpc`, `aws:ec2:subnet`, `aws:ec2:routetable`
|
||
- `aws:ecs:cluster`, `aws:ecs:task_definition`, `aws:ecs:service`,
|
||
`aws:ecs:uptime-service`
|
||
- `aws:iam:role`
|
||
- `aws:elbv2:loadbalancer`, `aws:elbv2:listener`,
|
||
`aws:elbv2:targetgroup`
|
||
- `aws:ecr:repository`
|
||
- `aws:cloudfront:distribution`, `aws:cloudfront:originaccesscontrol`
|
||
- `aws:wafv2:webacl`
|
||
- `aws:rds:instance`
|
||
- `aws:kms:key`, `aws:kms:alias`
|
||
- The substrate adapter's `TYPE_MAP` is the registry of stack types the
|
||
adapter can compile (see §8). A new stack type requires a `TYPE_MAP`
|
||
entry before the primitive can be deployed.
|
||
|
||
## 3. L2 Module Standards
|
||
|
||
An L2 module is a composition that references one or more L1 primitives
|
||
to deploy a complete stack (e.g. an ECS Fargate microservice, a static
|
||
asset site behind CloudFront + WAF). It is declared by a
|
||
`composition.json`; it does not own Terraform code and does not have an
|
||
`instance.json`.
|
||
|
||
### 3.1 Required files
|
||
|
||
| File | Purpose |
|
||
|------|---------|
|
||
| `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. |
|
||
|
||
Directory layout:
|
||
|
||
```
|
||
modules/l2/<name>/
|
||
composition.json
|
||
README.md
|
||
examples/
|
||
simple.yaml
|
||
complex.yaml
|
||
```
|
||
|
||
There is no `instance.json` for an L2 module — the L2 is deployed by
|
||
resolving the composition tree to L1 instances at compile time, not by
|
||
loading a pre-baked instance.
|
||
|
||
### 3.2 composition.json schema
|
||
|
||
`composition.json` MUST be a JSON object with the following fields:
|
||
|
||
| Field | Type | Required | Notes |
|
||
|-------|------|----------|-------|
|
||
| `name` | string | yes | `^[a-z][a-z0-9-]*$`; matches the module folder name. |
|
||
| `version` | string | yes | Semver; matches the registry entry. |
|
||
| `kind` | string | yes | Literal `"l2"`. |
|
||
| `depth` | integer | yes | Literal `1` in v1 (see §3.5). |
|
||
| `description` | string | yes | Plain language. |
|
||
| `children` | array | yes | One entry per referenced L1 module (§3.3). |
|
||
| `wires` | array | yes | Wires from contract inputs / child outputs to child inputs / stack outputs (§3.4). |
|
||
| `outputs` | array | yes | Wires from child outputs to stack outputs (§3.4). |
|
||
| `features` | object | no | Feature flags propagated to children by the resolver (§3.6). |
|
||
|
||
### 3.3 Children
|
||
|
||
Each `children[]` entry:
|
||
|
||
| Field | Type | Notes |
|
||
|-------|------|-------|
|
||
| `id` | string | The child id, unique within the composition. `^[a-z][a-z0-9-]*$`. The id is the local name used in wires (e.g. `vpc`, `cluster`, `kms`). |
|
||
| `module` | string | `<name>@<semver>` referencing a registered L1 module. |
|
||
|
||
Children MUST reference L1 modules registered in `registry.json` (see
|
||
§6). The referenced semver MUST exist in the registry. An L2 MUST NOT
|
||
reference another L2 (no L3 in v1; see §3.5).
|
||
|
||
Reference: `microservice/composition.json` declares seven children
|
||
(`vpc`, `cluster`, `ecr`, `roles`, `alb`, `service`, `kms`), each
|
||
referencing an L1 at `@1.0.0`.
|
||
|
||
### 3.4 Wire format
|
||
|
||
A wire is a JSON object `{"from": "<source>", "to": "<target>"}` with an
|
||
optional `default` field for contract-input wires.
|
||
|
||
Sources (the `from` side):
|
||
|
||
| Source form | Meaning |
|
||
|-------------|---------|
|
||
| `contract.inputs.<name>` | A value supplied by the consumer's contract YAML. |
|
||
| `<childId>.outputs.<name>` | An output produced by a child L1 module. |
|
||
|
||
Targets (the `to` side):
|
||
|
||
| Target form | Meaning |
|
||
|-------------|---------|
|
||
| `<childId>.inputs.<name>` | An input on a child L1 module. |
|
||
| `stack.outputs.<name>` | A value the L2 exposes as a stack output. |
|
||
|
||
Wires that source from `contract.inputs.<name>` MAY carry a `default`
|
||
value used when the consumer omits the input. Reference:
|
||
`microservice/composition.json` wires `contract.inputs.bucket_name` to
|
||
`vpc.inputs.cidr` with `default: "10.0.0.0/16"` (a historical quirk
|
||
preserved for regression).
|
||
|
||
The `outputs[]` array uses the same wire shape but its `to` is always
|
||
`stack.outputs.<name>` and its `from` is always
|
||
`<childId>.outputs.<name>`.
|
||
|
||
### 3.5 Maximum depth
|
||
|
||
`depth` is `1` for every L2 in v1. The composition tree is strictly L2
|
||
→ L1: an L2 may reference only L1 primitives, never another L2. There
|
||
is no L3 in v1. The stack schema permits `depth` up to 5 for forward
|
||
compatibility, but the v1 resolver and adapter only handle depth 1.
|
||
|
||
### 3.6 Feature flags
|
||
|
||
An L2 MAY declare a `features` object. Two flags are defined in v1:
|
||
|
||
| Flag | Type | Default | Effect |
|
||
|------|------|---------|--------|
|
||
| `deletion_protection` | boolean | `true` | When `true`, the resolver propagates `deletion_protection: true` to every child's NFRs. When `false`, children are deployed with `deletion_protection: false` (used by decommission; see §5). |
|
||
| `uptime_enabled` | boolean | `true` | When `true`, the uptime monitoring L1 is deployed after the L2 module in a separate terraform state. When `false`, the uptime deployment is skipped. |
|
||
|
||
Feature flags are propagated to children by the resolver; the L2
|
||
`composition.json` does not need to wire them explicitly as inputs. The
|
||
resolver reads `features` and injects the corresponding NFR/input on
|
||
each child.
|
||
|
||
## 4. Encryption by Default
|
||
|
||
Encryption is mandatory and on by default across the platform.
|
||
|
||
1. Every L1 MUST declare an `encryption_enabled` NFR (boolean, default
|
||
`true`) in `interface.json`. See §2.5.
|
||
2. Every L1 that holds at-rest data (S3, RDS, ECR, ECS task
|
||
definition env, VPC flow logs, CloudWatch log groups) MUST declare an
|
||
optional `kms_key_arn` input (`string`, `required: false`). When
|
||
supplied, the adapter wires it to the resource's KMS encryption
|
||
argument.
|
||
3. L2 modules MUST wire a per-stack customer-managed KMS key to all
|
||
children that accept `kms_key_arn`. The KMS key is a `kms-key` child
|
||
of the L2 — one key per L2 deployment, no shared keys. Reference:
|
||
both `static-assets` and `microservice` declare a `kms` child
|
||
(`kms-key@1.0.0`) and wire `kms.outputs.kms_key_arn` to every child
|
||
that accepts a CMK.
|
||
4. For a standalone L1 deployment (an L1 used outside an L2), if the
|
||
consumer does not supply `kms_key_arn`, the adapter falls back to the
|
||
AWS-managed default key for that service and emits a warning to
|
||
stderr. The primitive is still encrypted; only the key manager
|
||
differs.
|
||
5. The `kms-key` primitive enables key rotation by default
|
||
(`enable_rotation` NFR, default `true`), and the adapter emits
|
||
`enable_key_rotation = true` on the `aws_kms_key` resource.
|
||
|
||
A primitive that does not hold at-rest data (e.g. `iam-role`,
|
||
`ecs-cluster`, `alb`) still declares `encryption_enabled` for standards
|
||
uniformity (see §2.5) but does not declare `kms_key_arn`.
|
||
|
||
## 5. Deletion Protection by Default
|
||
|
||
Deletion protection is mandatory and on by default to prevent
|
||
accidental teardown of production infrastructure.
|
||
|
||
1. Every L1 MUST declare a `deletion_protection` NFR (boolean, default
|
||
`true`) in `interface.json`. See §2.5.
|
||
2. When `deletion_protection` is `true`, the substrate adapter emits a
|
||
`lifecycle { prevent_destroy = true }` block on the corresponding
|
||
Terraform resource. A `terraform destroy` against a protected
|
||
resource fails with an error naming the resource.
|
||
3. L2 modules expose `features.deletion_protection` (default `true`).
|
||
The resolver propagates the flag to every child's NFRs (see §3.6).
|
||
4. **Decommission mode.** To tear down a stack that was deployed with
|
||
deletion protection, the consumer sets `inputs.deletion_protection:
|
||
false` on the contract (or `features.deletion_protection: false` on
|
||
an L2) and re-applies. The decommission transform
|
||
(`decommission_transform`) zeroes capacity counts (e.g. ECS desired
|
||
count to 0, RDS allocated storage to the minimum) so that the
|
||
subsequent `destroy` applies against a quiesced stack. The transform
|
||
is applied by the resolver before the adapter emits resources.
|
||
|
||
## 6. Registry
|
||
|
||
Every module — L1 and L2 — MUST be registered in
|
||
`modules/registry.json` at its semver. The registry is the source of
|
||
truth for what is published; the adapter and resolver refuse to compile
|
||
a module that is not registered.
|
||
|
||
Registry entry shape:
|
||
|
||
```json
|
||
{
|
||
"<module-name>": {
|
||
"<semver>": {
|
||
"interface": "modules/<l1|l2>/<module-name>/<interface.json|composition.json>",
|
||
"published_at": "<ISO 8601 timestamp>",
|
||
"deprecated": false
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
- `interface` is the path (relative to the repo root) to the module's
|
||
interface file — `interface.json` for an L1, `composition.json` for
|
||
an L2.
|
||
- `published_at` is an ISO 8601 timestamp. Use a full
|
||
`YYYY-MM-DDTHH:MM:SSZ` form; do not omit the seconds or the timezone
|
||
designator.
|
||
- `deprecated` is `false` for a live module. A MAJOR version bump does
|
||
not delete the old entry; it flips `deprecated` to `true` and starts a
|
||
12-month deprecation window (see §7 Versioning).
|
||
|
||
A new semver of an existing module is a new key under the module's
|
||
object; old semvers are retained. The registry is append-only for
|
||
published semvers — a published semver is never edited or deleted.
|
||
|
||
## 7. README Standards
|
||
|
||
Every module README MUST follow the structure of
|
||
`modules/README-TEMPLATE.md`. Required sections, in order:
|
||
|
||
1. `# <name> — <plain-language description>` — title with the module
|
||
name and a one-line description.
|
||
2. `## Overview` — one or two sentences in plain language.
|
||
3. `## Resources` — a table of the Terraform resources the module
|
||
creates (L1) or the primitives it references (L2).
|
||
4. `## Inputs` — a table: `| Name | Type | Required | Default | Description |`.
|
||
5. `## Outputs` — a table: `| Name | Type | Description |`.
|
||
6. `## NFRs` — a table: `| Name | Type | Default | Description |`.
|
||
`deletion_protection` and `encryption_enabled` are mandatory NFRs
|
||
for every L1; they MUST appear in this table.
|
||
7. `## Usage` — a concrete snippet showing how a consumer references
|
||
the module in a contract.
|
||
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.
|
||
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
|
||
12-month deprecation window.
|
||
|
||
An L2 README's `## Resources` section lists the referenced L1 children
|
||
rather than Terraform resources, and its `## Inputs`/`## Outputs`
|
||
sections reflect the contract inputs and stack outputs of the
|
||
composition.
|
||
|
||
## 8. Adapter Extension Pattern
|
||
|
||
The Terraform adapter (`adapters/terraform/adapter.py`) is a thin
|
||
translator. It owns no module content; it only maps stack types and
|
||
names to Terraform types and arguments via three tables and, for
|
||
complex resources, a specialized emit branch.
|
||
|
||
### 8.1 The three tables
|
||
|
||
| Table | Purpose | Keys | Values |
|
||
|-------|---------|------|--------|
|
||
| `TYPE_MAP` | Stack type → Terraform resource type. | Stack type string (`aws:<service>:<kind>`). | Terraform resource type (`aws_s3_bucket`, `aws_db_instance`, etc.). |
|
||
| `INPUT_MAP` | Stack input name → Terraform argument name, per stack type. Only non-identity mappings are listed; an input not present uses the stack name as the Terraform arg (identity). | Stack type. | Object mapping input name → Terraform arg name. |
|
||
| `OUTPUT_MAP` | Stack output name → Terraform attribute name, per stack type. Only non-identity mappings are listed. | Stack type. | Object mapping output name → Terraform attribute name. |
|
||
|
||
Reference: `adapter.py:26` (`TYPE_MAP`), `adapter.py:51` (`INPUT_MAP`),
|
||
`adapter.py:75` (`OUTPUT_MAP`).
|
||
|
||
### 8.2 Specialized `_emit_resource` branches
|
||
|
||
Most resources emit with the generic loop in `_emit_resource`
|
||
(`adapter.py:156`): for each input, look up the Terraform arg in
|
||
`INPUT_MAP`, render the value, append `arg = value`. Resources with
|
||
nested HCL blocks need a specialized branch. The shipped examples:
|
||
|
||
- `aws:ecs:service` emits a `load_balancer {}` block from the
|
||
`lb_target_group_arn` input.
|
||
- `aws:elbv2:loadbalancer` wraps `subnets` and `security_group` in list
|
||
brackets.
|
||
- `aws:cloudfront:distribution` emits nested `origin {}`,
|
||
`default_cache_behavior {}`, and
|
||
`server_side_encryption_configuration {}` blocks.
|
||
- `aws:wafv2:webacl` emits nested `rules {}` blocks.
|
||
- `aws:ecs:task_definition` emits a `container_definitions` jsonencode
|
||
block from `image`/`port`/`env`.
|
||
|
||
A specialized branch lives inside `_emit_resource` and is keyed on the
|
||
stack type. It reads the input value, renders the nested block, and
|
||
appends the lines to `body`.
|
||
|
||
### 8.3 Adding a new L1 to the adapter
|
||
|
||
When a new L1 primitive is added:
|
||
|
||
1. Add one entry to `TYPE_MAP` for each stack type the primitive
|
||
declares (single resource → one entry; multi-resource → one entry
|
||
per resource in `resources[]`).
|
||
2. Add one entry to `INPUT_MAP` for each stack type, listing only the
|
||
inputs whose Terraform arg name differs from the stack input name
|
||
(identity mappings are omitted).
|
||
3. Add one entry to `OUTPUT_MAP` for each stack type, listing only the
|
||
outputs whose Terraform attribute name differs from the stack output
|
||
name.
|
||
4. If any resource requires nested HCL blocks, add a specialized branch
|
||
in `_emit_resource` keyed on that stack type.
|
||
|
||
If steps 1–3 are done and no specialized branch is needed, the
|
||
primitive deploys with no further adapter changes. The L1 content and
|
||
the contract YAML do not change when the adapter grows.
|
||
|
||
## 9. Code Review Checklist
|
||
|
||
Use this checklist when reviewing a new module (L1 or L2). Every box
|
||
must be checked before the module is registered and published.
|
||
|
||
### 9.1 Files and structure
|
||
|
||
- [ ] 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`).
|
||
- [ ] `interface.json` (L1) / `composition.json` (L2) validates against
|
||
`schemas/stack.schema.json`.
|
||
- [ ] `examples/simple.yaml` and `examples/complex.yaml` 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`.
|
||
|
||
### 9.2 Interface (L1)
|
||
|
||
- [ ] `name` matches the folder name and `^[a-z][a-z0-9-]*$`.
|
||
- [ ] `version` is semver and matches the registry entry.
|
||
- [ ] `kind` is `"l1"`.
|
||
- [ ] `type` follows `aws:<service>:<kind>`.
|
||
- [ ] Every input has `type`, `description`, `required`; optional inputs
|
||
carry a `default` of the correct type; `enum` present where the value
|
||
set is constrained.
|
||
- [ ] Every output has `type` (`arn` for ARNs, `string` otherwise) and
|
||
`description`.
|
||
- [ ] `nfrs` includes `deletion_protection` (boolean, default `true`)
|
||
and `encryption_enabled` (boolean, default `true`).
|
||
- [ ] `kms_key_arn` input present if the primitive holds at-rest data.
|
||
- [ ] Multi-resource primitives declare `resources[]` (with `inputs`/
|
||
`outputs` as arrays of names) and `intra_refs[]` with `{from, to}`.
|
||
|
||
### 9.3 Composition (L2)
|
||
|
||
- [ ] `kind` is `"l2"` and `depth` is `1`.
|
||
- [ ] Every `children[]` entry is `{id, module}` with `module` in
|
||
`<name>@<semver>` form referencing a registered L1.
|
||
- [ ] No child references an L2 (no L3 in v1).
|
||
- [ ] `wires[]` use the `contract.inputs.<name>` /
|
||
`<childId>.outputs.<name>` → `<childId>.inputs.<name>` /
|
||
`stack.outputs.<name>` forms.
|
||
- [ ] `outputs[]` use `<childId>.outputs.<name>` →
|
||
`stack.outputs.<name>`.
|
||
- [ ] A `kms` child (`kms-key@<semver>`) is present and its
|
||
`kms_key_arn` output is wired to every child that accepts a CMK.
|
||
- [ ] `features` (if present) only uses defined flags
|
||
(`deletion_protection`, `uptime_enabled`).
|
||
|
||
### 9.4 Adapter
|
||
|
||
- [ ] `TYPE_MAP` has an entry for every stack type the new primitive
|
||
declares.
|
||
- [ ] `INPUT_MAP` and `OUTPUT_MAP` have entries for every stack type,
|
||
listing only non-identity mappings.
|
||
- [ ] A specialized `_emit_resource` branch is added for any resource
|
||
that needs nested HCL blocks.
|
||
- [ ] The new primitive's `instance.json` round-trips through the
|
||
adapter without error (regression baseline).
|
||
|
||
### 9.5 README and docs
|
||
|
||
- [ ] README follows `README-TEMPLATE.md` with all required sections in
|
||
order (§7).
|
||
- [ ] `## NFRs` table lists `deletion_protection` and
|
||
`encryption_enabled` for an L1.
|
||
- [ ] `## Compliance extension points` lists at least one plausible
|
||
future extension.
|
||
|
||
### 9.6 Tests
|
||
|
||
- [ ] A test is added for the new primitive covering adapter emission
|
||
(the Terraform output for `instance.json` matches the expected
|
||
fixture) and interface validation (`interface.json` validates against
|
||
`stack.schema.json`).
|
||
- [ ] For an L2, a test is added that the composition resolves to the
|
||
expected set of L1 instances and that the adapter emits a root module
|
||
calling the L1 modules. |