| `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`). |
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
-`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, HIPAA,
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. |
assert"## NFRs"incontentor"## NFR"incontent,"README-TEMPLATE.md must have NFRs section"
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.