# l2-microservice — thin-composition (ECS Fargate microservice) The v1.2 L2. A thin-composition that references 6 L1s (depth 1): `l1-vpc`, `l1-ecs-cluster`, `l1-ecr`, `l1-iam-role`, `l1-alb`, `l1-ecs-service`. The contract's inputs (`name`, `cidr`, `azs`, `image`, `port`, `cpu`, `memory`, `env`, `protocol`, `region`, `role_name`, `assume_role_policy`, `managed_policies`) map to the children's inputs through two wire kinds. ## Composition (the IR-typed thin-composition tree) See `composition.json`: `kind=l2`, `depth=1`, six children. ### Children | child id | L1 module | IR type(s) | |----------|-----------|------------| | `vpc` | `l1-vpc@1.0.0` | `aws:ec2:vpc`, `aws:ec2:subnet`, `aws:ec2:routetable` | | `cluster` | `l1-ecs-cluster@1.0.0` | `aws:ecs:cluster` | | `ecr` | `l1-ecr@1.0.0` | `aws:ecr:repository` | | `roles` | `l1-iam-role@1.0.0` | `aws:iam:role` | | `alb` | `l1-alb@1.0.0` | `aws:elbv2:loadbalancer`, `aws:elbv2:listener`, `aws:elbv2:targetgroup` | | `service` | `l1-ecs-service@1.0.0` | `aws:ecs:task_definition`, `aws:ecs:service` | Multi-resource L1s (`vpc`, `alb`, `service`) declare a `resources` array in their `interface.json`; the resolver expands each child into one IR resource per `resources` entry (id scheme `-` where `type_suffix` is the last segment of the IR type with underscores stripped — e.g. `vpc-vpc`, `vpc-subnet`, `vpc-routetable`, `alb-loadbalancer`, `alb-targetgroup`, `alb-listener`, `service-taskdefinition`, `service-service`. The hyphen separator keeps the id valid against `schemas/ir.schema.json`'s `^[a-z][a-z0-9-]*$` resource id pattern). Single-resource L1s keep the child id verbatim (`cluster`, `ecr`, `roles`). ### Wire kinds 1. **Contract→child passthrough** — wire name = contract input name; target = child id, input = child's input name. For contract inputs that fan out to multiple children (`name`, `port`, `region`), the wire value is an array of `{target, input}` objects; otherwise a single object. Resolves to the concrete contract value. 2. **Child→child references** — wire with `source: "child:."`. The value is only known at apply time, so the resolver emits the IR input as the string `ref:.` (the IR resource id of the *producing* child's first resource — for single-resource L1s that is the child id, for multi-resource L1s it is `-` of the first resource in the `resources` array that declares the output). The adapter translates `ref:` to a Terraform interpolation. Wires used by this composition: - Passthrough: `name` (→vpc/cluster/ecr/alb), `cidr` (→vpc), `azs` (→vpc), `image` (→service), `port` (→service/alb), `cpu` (→service), `memory` (→service), `env` (→service), `protocol` (→alb), `region` (→all 6), `role_name` (→roles), `assume_role_policy` (→roles), `managed_policies` (→roles). - Child→child: `cluster_arn` (cluster→service), `subnet_ids` (vpc→service/alb `subnets`), `target_group_arn` (alb→service `lb_target_group_arn`), `role_arn` (roles→service/alb `security_group`). ## IR → Terraform mapping (D-P10-1) The Terraform adapter consumes the *resolved IR instance* (which has `kind=l2` + all 6 L1s expanded into one IR resource per entry in each L1's `resources` array, with `ref:` strings on the consumer inputs). For a depth-1 thin-composition, the L2 root module **IS** the union of the L1 resources — no separate `module "l1_x" { source = "..." }` blocks. The existing adapter `TYPE_MAP` + `INPUT_MAP` + `OUTPUT_MAP` tables handle every IR type. `ref:.` inputs are translated to `${..}` (attribute mapped through `OUTPUT_MAP` for the referenced resource's type). The `relationships` array records the parent composition tree; ordering is implicit in the resource list. v1.3+ may emit real `module "l1_x" { source = "..." }` blocks once L1s are published Terraform modules rather than inline resources. ## Versioning (W3.D) `1.0.0` — interface MAJOR, behavior MINOR, lifecycle PATCH. MAJOR bumps require a new registry entry (immutable publication); old entries enter a 12-month deprecation window.