# STACKIT target draft

> Generated migration draft by StackToIT — architect review required. Not for apply without review.

Run `f6267c3f-2848-4aa5-89df-5fee8b15b3b9` · source digest `sha256:812c5ae3f6cc8530163abbf5b6fbe305ed3b363c4886ec6b87623db857ea0c70`

Generated for STACKIT Terraform provider `0.121.0`. Fill the variables without defaults, then run `tofu init && tofu validate && tofu plan` against a test project. StackToIT never runs apply.

| | |
|---|---:|
| Units generated | 38 |
| Units that need no STACKIT resource | 11 |
| Units left for the architect | 3 |
| Variables to fill or confirm | 60 |

Each block keeps a short comment: where it comes from, why its values are what they are, what is not carried and which decision is open. Its last line, `# trace: TR-…`, names the section of the trace file of this draft file in the report folder (`engineering/trace/<draft file>.md#TR-…`, listed in `engineering/trace-index.csv`) that holds the whole comment the generators wrote: the rule and the knowledge-base stamp, the references with their digests, the evidence of each chosen value and the reason of each attribute that is not written.

## Enable these services first

STACKIT services are enabled per project and region; the portal shows a disabled one as "not enabled in your region". Enable the services this draft uses before apply: Application Load Balancer, IaaS, KMS, Key Value Store, Kubernetes Engine, MariaDB, MongoDB Flex, OpenSearch, PostgreSQL Flex, RabbitMQ, Secrets Manager. Enabling Kubernetes Engine also enables IaaS, Load Balancer and Network Area. (A plan with -refresh=false does not need them; whether apply enables a service by itself is not verified.)

## Target maturity — not GA

The pinned STACKIT provider marks these generated resources as not generally available; each block says so in its comment. Beta resources "may be subject to breaking changes in the future"; experimental features are "likely going to undergo significant changes or be removed in the future" (provider notices). terraform.tf enables them explicitly.

- **EXPERIMENTAL (feature iam)** `stackit_authorization_project_role_assignment`: `stackit_authorization_project_role_assignment.retail_app_ecs_carts_service_task_execution_role`, `stackit_authorization_project_role_assignment.retail_app_ecs_carts_service_task_role`, `stackit_authorization_project_role_assignment.retail_app_ecs_catalog_service_task_execution_role`, `stackit_authorization_project_role_assignment.retail_app_ecs_catalog_service_task_role`, `stackit_authorization_project_role_assignment.retail_app_ecs_checkout_service_task_execution_role`, `stackit_authorization_project_role_assignment.retail_app_ecs_checkout_service_task_role`, `stackit_authorization_project_role_assignment.retail_app_ecs_orders_service_task_execution_role`, `stackit_authorization_project_role_assignment.retail_app_ecs_orders_service_task_role`, `stackit_authorization_project_role_assignment.retail_app_ecs_ui_service_task_execution_role`, `stackit_authorization_project_role_assignment.retail_app_ecs_ui_service_task_role`

## Target lifecycle

STACKIT publishes dates for the lifecycle of some capabilities this draft lands on. A state below is derived from those dates only, as of the day the sources were read; this draft does not read the clock, so compare the dates with today. The assessment's engineering/architect-report.md, section "Target lifecycle", has the dates, the effects and the sources.

- **SKE Kubernetes versions**, as of 2026-10-08 (the day the sources were read; not vouched for after 2027-01-06): 1.36 SUPPORTED, 1.35 UNKNOWN, 1.34 EXPIRING (expires 2026-10-14), 1.33 EXPIRED, 1.32 EXPIRED. Suitable as a landing in production: 1.36.

## Target security checklist

Each row is filled from a check or a table at generation, not written by hand. A row is evidence of what the generated files hold, not of what you will add to them.

| Check | Result |
|---|---|
| Generated static secrets | **0** — the credential-pattern table of StackToIT (set v5) found no literal credential in the 25 generated files that are not Markdown notes; a secret input is a `sensitive = true` variable without a default |
| Sensitive variables | **0** of 60 declared variables are `sensitive = true`: none. Pass them with `TF_VAR_<name>`, never in a committed file |
| Secrets the provider writes into the state | written by the provider at apply: `stackit_mariadb_credential` (`password`, `uri`), `stackit_opensearch_credential` (`password`, `uri`), `stackit_postgresflex_user` (`password`, `uri`), `stackit_rabbitmq_credential` (`password`, `uri`), `stackit_secretsmanager_user` (`password`), `stackit_valkey_credential` (`password`, `uri`). `sensitive` hides a value from the plan output only; the state holds it in plain text, so the state is itself a secret (the pinned provider schema) |
| Write-only or ephemeral alternative | none: the pinned provider schema has no write-only or ephemeral form for these attributes, so the values stay in the state: keep the state encrypted (OpenTofu state encryption or an encrypted backend) with read access limited to the pipeline |
| Provider authentication | Authenticate the provider with Workload Identity Federation (`use_oidc = true` and `service_account_email`, a short-lived OIDC token exchanged for a STACKIT access token) instead of a long-lived service account key: the provider documents it as the way that "eliminates the need for long-lived secrets" in CI/CD (the STACKIT provider documentation, index page, section "Workload Identity Federation", and its guide "Workload Identity Federation"; knowledge-base evidence, not in this folder). |
| Terraform state backend | The draft declares no backend, so its state is a local file until you configure one. The provider documentation states that `use_lockfile = true` on the S3 backend has no effect with a STACKIT Object Storage bucket, and for state locking it points to a PostgreSQL backend (the STACKIT provider documentation, index page, section "Backend configuration"; knowledge-base evidence, not in this folder). Do not rely on locking with an S3 backend on STACKIT Object Storage: run apply from one job at a time, or use the PostgreSQL backend the provider points to. StackToIT did not test locking on any backend. |

## Generated

Resources written to the .tf files in this folder.

| Source | Class | Generated |
|---|---|---|
| `module.vpc.module.vpc.aws_subnet.private` | RECONFIGURE / MEDIUM | `stackit_network.vpc_vpc_private` |
| `module.vpc.module.vpc.aws_subnet.public` | RECONFIGURE / MEDIUM | `stackit_network.vpc_vpc_public` |
| `module.retail_app_ecs.module.alb.aws_security_group.this` | RECONFIGURE / MEDIUM | `stackit_security_group.retail_app_ecs_alb_this` |
| `module.retail_app_ecs.module.alb_sg.aws_security_group.this_name_prefix` | RECONFIGURE / MEDIUM | `stackit_security_group.retail_app_ecs_alb_sg_this_name_prefix` |
| `module.retail_app_ecs.module.carts_service.aws_security_group.this` | RECONFIGURE / MEDIUM | `stackit_security_group.retail_app_ecs_carts_service_this` |
| `module.retail_app_ecs.module.catalog_service.aws_security_group.this` | RECONFIGURE / MEDIUM | `stackit_security_group.retail_app_ecs_catalog_service_this` |
| `module.retail_app_ecs.module.checkout_service.aws_security_group.this` | RECONFIGURE / MEDIUM | `stackit_security_group.retail_app_ecs_checkout_service_this` |
| `module.retail_app_ecs.module.orders_service.aws_security_group.this` | RECONFIGURE / MEDIUM | `stackit_security_group.retail_app_ecs_orders_service_this` |
| `module.retail_app_ecs.module.ui_service.aws_security_group.this` | RECONFIGURE / MEDIUM | `stackit_security_group.retail_app_ecs_ui_service_this` |
| `module.retail_app_ecs.aws_kms_key.cmk` | RECONFIGURE / MEDIUM | `stackit_kms_key.retail_app_ecs_cmk` — symmetric AES-256-GCM assumed (AWS default key spec) |
| `module.dependencies.module.catalog_rds.aws_rds_cluster.this` | REPLATFORM / LOW | `stackit_mariadb_instance.dependencies_catalog_rds_this` |
| `module.dependencies.module.orders_rds.aws_rds_cluster.this` | REPLATFORM / MEDIUM | `stackit_postgresflex_instance.dependencies_orders_rds_this` |
| `module.dependencies.module.checkout_elasticache_redis.aws_elasticache_replication_group.default` | RECONFIGURE / MEDIUM | `stackit_valkey_instance.dependencies_checkout_elasticache_redis_default` |
| `module.dependencies.module.catalog_opensearch.aws_opensearch_domain.this` | REPLATFORM / LOW | `stackit_opensearch_instance.dependencies_catalog_opensearch_this` |
| `module.dependencies.aws_mq_broker.mq` | RECONFIGURE / MEDIUM | `stackit_rabbitmq_instance.dependencies_mq` |
| `module.retail_app_ecs.module.alb.aws_lb.this` | RECONFIGURE / MEDIUM | `stackit_application_load_balancer.retail_app_ecs_alb_this` |
| `module.dependencies.module.dynamodb_carts.aws_dynamodb_table.this` | REDESIGN / MEDIUM | `stackit_mongodbflex_instance.dynamodb_tables` — shared instance; the table becomes a collection (alternative, data-model rewrite) |
| `module.retail_app_ecs.module.carts_service.aws_iam_role.task_execution_role` | RECONFIGURE / MEDIUM | `stackit_service_account.retail_app_ecs_carts_service_task_execution_role` |
| `module.retail_app_ecs.module.carts_service.aws_iam_role.task_role` | RECONFIGURE / LOW | `stackit_service_account.retail_app_ecs_carts_service_task_role` |
| `module.retail_app_ecs.module.catalog_service.aws_iam_role.task_execution_role` | RECONFIGURE / LOW | `stackit_service_account.retail_app_ecs_catalog_service_task_execution_role` |
| `module.retail_app_ecs.module.catalog_service.aws_iam_role.task_role` | RECONFIGURE / MEDIUM | `stackit_service_account.retail_app_ecs_catalog_service_task_role` |
| `module.retail_app_ecs.module.checkout_service.aws_iam_role.task_execution_role` | RECONFIGURE / MEDIUM | `stackit_service_account.retail_app_ecs_checkout_service_task_execution_role` |
| `module.retail_app_ecs.module.checkout_service.aws_iam_role.task_role` | RECONFIGURE / LOW | `stackit_service_account.retail_app_ecs_checkout_service_task_role` |
| `module.retail_app_ecs.module.orders_service.aws_iam_role.task_execution_role` | RECONFIGURE / LOW | `stackit_service_account.retail_app_ecs_orders_service_task_execution_role` |
| `module.retail_app_ecs.module.orders_service.aws_iam_role.task_role` | RECONFIGURE / MEDIUM | `stackit_service_account.retail_app_ecs_orders_service_task_role` |
| `module.retail_app_ecs.module.ui_service.aws_iam_role.task_execution_role` | RECONFIGURE / MEDIUM | `stackit_service_account.retail_app_ecs_ui_service_task_execution_role` |
| `module.retail_app_ecs.module.ui_service.aws_iam_role.task_role` | RECONFIGURE / MEDIUM | `stackit_service_account.retail_app_ecs_ui_service_task_role` |
| `module.retail_app_ecs.aws_secretsmanager_secret.catalog_db` | RECONFIGURE / MEDIUM | `stackit_secretsmanager_instance.migrated_secrets` — shared instance |
| `module.retail_app_ecs.aws_secretsmanager_secret.catalog_opensearch` | RECONFIGURE / MEDIUM | `stackit_secretsmanager_instance.migrated_secrets` — shared instance |
| `module.retail_app_ecs.aws_secretsmanager_secret.mq` | RECONFIGURE / MEDIUM | `stackit_secretsmanager_instance.migrated_secrets` — shared instance |
| `module.retail_app_ecs.aws_secretsmanager_secret.orders_db` | RECONFIGURE / MEDIUM | `stackit_secretsmanager_instance.migrated_secrets` — shared instance |
| `module.retail_app_ecs.aws_cloudwatch_log_group.ecs_tasks` | RECONFIGURE / MEDIUM | `stackit_logs_instance.logs_retention_180d` — shared instance |
| `module.retail_app_ecs.aws_ecs_cluster.cluster` | REPLATFORM / MEDIUM | `stackit_ske_cluster.retail_app_ecs_cluster` — workloads: kubernetes/<service>/manifests.yaml of its ECS services |
| `module.retail_app_ecs.module.carts_service.aws_ecs_service.this` | REPLATFORM / LOW | `stackit_ske_cluster.retail_app_ecs_cluster` — Kubernetes manifests kubernetes/carts/manifests.yaml on stackit_ske_cluster.retail_app_ecs_cluster |
| `module.retail_app_ecs.module.catalog_service.aws_ecs_service.this` | REPLATFORM / LOW | `stackit_ske_cluster.retail_app_ecs_cluster` — Kubernetes manifests kubernetes/catalog/manifests.yaml on stackit_ske_cluster.retail_app_ecs_cluster |
| `module.retail_app_ecs.module.checkout_service.aws_ecs_service.this` | REPLATFORM / MEDIUM | `stackit_ske_cluster.retail_app_ecs_cluster` — Kubernetes manifests kubernetes/checkout/manifests.yaml on stackit_ske_cluster.retail_app_ecs_cluster |
| `module.retail_app_ecs.module.orders_service.aws_ecs_service.this` | REPLATFORM / LOW | `stackit_ske_cluster.retail_app_ecs_cluster` — Kubernetes manifests kubernetes/orders/manifests.yaml on stackit_ske_cluster.retail_app_ecs_cluster |
| `module.retail_app_ecs.module.ui_service.aws_ecs_service.this` | REPLATFORM / MEDIUM | `stackit_ske_cluster.retail_app_ecs_cluster` — Kubernetes manifests kubernetes/ui/manifests.yaml on stackit_ske_cluster.retail_app_ecs_cluster |

## Kubernetes manifests — not Terraform

The workloads of ECS services are Kubernetes manifests, not Terraform: `tofu validate` and `tofu apply` do not read them. Apply each file with the kubeconfig of the SKE cluster this draft creates (`kubectl apply -n <namespace> -f <file>`). The header of each file lists the required inputs (a `REQUIRED_` token is refused by the API server in a field that is not a string, such as a number of pods, and accepted in a string field such as an image: replace every token before you apply), the secrets to create and what is not carried.

How to get the kubeconfig of the cluster is a DEFINED step, not something this draft does: `handover/cluster-access/kubeconfig.md` lists the commands (the STACKIT CLI; no `stackit_ske_kubeconfig` is drafted because the admin kubeconfig would be kept in the Terraform state).

- `kubernetes/carts/manifests.yaml`
- `kubernetes/catalog/manifests.yaml`
- `kubernetes/checkout/manifests.yaml`
- `kubernetes/orders/manifests.yaml`
- `kubernetes/ui/manifests.yaml`

## Not carried for any unit

- **Tags and labels** of the source resources: where the draft makes one STACKIT resource of the same kind from a tagged AWS resource (server, volume, key pair, network, public IP, network interface, security group, routing table, application load balancer, VPN gateway and connection), the tags are written as its `labels` when STACKIT's label rules take them (a key of up to 63 letters, digits, `-`, `_` and `.`, beginning and ending with a letter or digit, not starting with `stackit-`, optionally after a domain prefix (a lowercase DNS name of up to 250 characters, not stackit.cloud) and one `/`; a value empty or of up to 63 such characters; at most 64 labels; the rules are the ones the STACKIT IaaS API answered in a sandbox probe, 2026-10-16; the application load balancer and the VPN follow their own specifications and take no domain prefix; a routing table follows the IaaS specification and could not be probed) and the scan can read them. The servers of an Auto Scaling group carry its tags that propagate at launch; the data volumes of an instance's inline block devices carry the tags of their block (or volume_tags) and the provider's default tags. Every other tag is named NOT CARRIED next to its block with the reason, none is rewritten, and no value is copied from a variable as if it were a fact of the source (those labels are a variable of the draft). Not carried at all, each named NOT CARRIED where the resource is drafted: the tags of every other resource, the tags of the root volume (the boot volume has no labels), the tags a launch template puts on instances (how they merge with the instance's or the group's is not documented), the tags of file storage (it takes labels, no format is documented) and of a network load balancer (it has none), and tags of the other clouds. The `default_tags` of an AWS provider block are merged under the resource's own tags (the resource wins on a key both set) and its `ignore_tags` are respected (a tag it names is not a label and is named NOT CARRIED); what the scan cannot read of them is named NOT CARRIED on the block, and what the environment adds (`TF_AWS_DEFAULT_TAGS_*`, `TF_AWS_IGNORE_TAGS_*`) is invisible to a scan. **A label is not an access control**: STACKIT role assignments are (resource, role, subject) with no condition (pinned provider schema), so a policy, service control policy, key policy, bucket policy or trust policy that conditions on aws:ResourceTag, aws:RequestTag, aws:PrincipalTag or aws:TagKeys is not reproduced by the label copy; each stays an open critical item of the resource that holds the policy. Behaviour that selects resources by tag (backup selections, cost allocation) must be rebuilt.
- **Placement**: every resource is created in `var.region` inside the project `var.project_id`. A source spread over several regions, resource groups or projects is collapsed into one: each resource that sets one of those values says so in a NOT CARRIED line next to its block.

## Alternative drafts — not equivalents

STACKIT has no counterpart for these services (the pinned provider has no function, API-gateway or key-value NoSQL resource). The draft proposes an alternative; each block (or manifest) lists under NOT CARRIED what does not carry over. Cloud Foundry manifests are in `cloudfoundry/`, not Terraform.

- 1 DynamoDB table(s) → `stackit_mongodbflex_instance.dynamodb_tables` (one collection per table; data-model rewrite).

## PostgreSQL data move

StackToIT moves no data and opens no connection; this section is the preparation, and every step in the documents below is DEFINED (written, not executed): it is APPLIED once it has been carried out and VERIFIED once its result on STACKIT was checked. The draft creates an empty database. Each document defines both ways to fill it (a dump restored with `pg_restore`, and logical replication) with what each needs, because which one applies depends on the size of the data and the write-freeze window, which is the open field `write-freeze window` of this step in the plan, UNKNOWN until the customer states it, and the source states neither; it also defines the checks before the move, a row count and checksum comparison with an application query, the cutover window and the rollback. Nothing in it was run against STACKIT.

- `module.dependencies.module.orders_rds.aws_rds_cluster.this` (Aurora, `stackit_postgresflex_instance.dependencies_orders_rds_this`): data move, verification, cutover and rollback: `handover/database-dependencies_orders_rds_this/data-move.md` (DEFINED)

## Not needed on STACKIT

These source resources have no counterpart to create; the platform covers them implicitly.

| Source | Class | Why |
|---|---|---|
| `module.dependencies.aws_security_group.catalog_opensearch_sg` | RECONFIGURE / MEDIUM | every unit that names the group is a managed data service of STACKIT, and a STACKIT data service has no security group (the pinned provider schema has no attribute that names one) and takes its clients from an access list of its own: module.dependencies.module.catalog_opensearch.aws_opensearch_domain.this (stackit_opensearch_instance, access list parameters.sgw_acl). The group would be listed by no resource, so no stackit_security_group and no rule is drafted for it. What the group admitted reaches the access lists as addresses only, on the one endpoint port of the service: whether the clients of a rule are in the default of a list is stated on the block of the service (its DEFAULT line or its NOT CARRIED lines), and who may connect is the open decision EXPOSURE_WIDENED of the service; a rule whose clients no address names, or that reaches another port, is not carried. The egress rules of the group are about the outbound traffic of the instance itself, which a managed data service exposes no filter for: they are not carried |
| `module.dependencies.aws_security_group.mq` | RECONFIGURE / MEDIUM | every unit that names the group is a managed data service of STACKIT, and a STACKIT data service has no security group (the pinned provider schema has no attribute that names one) and takes its clients from an access list of its own: module.dependencies.aws_mq_broker.mq (stackit_rabbitmq_instance, access list parameters.sgw_acl). The group would be listed by no resource, so no stackit_security_group and no rule is drafted for it. What the group admitted reaches the access lists as addresses only, on the one endpoint port of the service: whether the clients of a rule are in the default of a list is stated on the block of the service (its DEFAULT line or its NOT CARRIED lines), and who may connect is the open decision EXPOSURE_WIDENED of the service; a rule whose clients no address names, or that reaches another port, is not carried. The egress rules of the group are about the outbound traffic of the instance itself, which a managed data service exposes no filter for: they are not carried |
| `module.dependencies.module.catalog_rds.aws_security_group.this` | RECONFIGURE / MEDIUM | every unit that names the group is a managed data service of STACKIT, and a STACKIT data service has no security group (the pinned provider schema has no attribute that names one) and takes its clients from an access list of its own: module.dependencies.module.catalog_rds.aws_rds_cluster.this (stackit_mariadb_instance, access list parameters.sgw_acl). The group would be listed by no resource, so no stackit_security_group and no rule is drafted for it. What the group admitted reaches the access lists as addresses only, on the one endpoint port of the service: whether the clients of a rule are in the default of a list is stated on the block of the service (its DEFAULT line or its NOT CARRIED lines), and who may connect is the open decision EXPOSURE_WIDENED of the service; a rule whose clients no address names, or that reaches another port, is not carried. The egress rules of the group are about the outbound traffic of the instance itself, which a managed data service exposes no filter for: they are not carried |
| `module.dependencies.module.checkout_elasticache_redis.module.aws_security_group.aws_security_group.cbd` | RECONFIGURE / MEDIUM | every unit that names the group is a managed data service of STACKIT, and a STACKIT data service has no security group (the pinned provider schema has no attribute that names one) and takes its clients from an access list of its own: module.dependencies.module.checkout_elasticache_redis.aws_elasticache_replication_group.default (stackit_valkey_instance, access list parameters.sgw_acl). The group would be listed by no resource, so no stackit_security_group and no rule is drafted for it. What the group admitted reaches the access lists as addresses only, on the one endpoint port of the service: whether the clients of a rule are in the default of a list is stated on the block of the service (its DEFAULT line or its NOT CARRIED lines), and who may connect is the open decision EXPOSURE_WIDENED of the service; a rule whose clients no address names, or that reaches another port, is not carried. The egress rules of the group are about the outbound traffic of the instance itself, which a managed data service exposes no filter for: they are not carried |
| `module.dependencies.module.orders_rds.aws_security_group.this` | RECONFIGURE / MEDIUM | every unit that names the group is a managed data service of STACKIT, and a STACKIT data service has no security group (the pinned provider schema has no attribute that names one) and takes its clients from an access list of its own: module.dependencies.module.orders_rds.aws_rds_cluster.this (stackit_postgresflex_instance, access list network.acl). The group would be listed by no resource, so no stackit_security_group and no rule is drafted for it. What the group admitted reaches the access lists as addresses only, on the one endpoint port of the service: whether the clients of a rule are in the default of a list is stated on the block of the service (its DEFAULT line or its NOT CARRIED lines), and who may connect is the open decision EXPOSURE_WIDENED of the service; a rule whose clients no address names, or that reaches another port, is not carried. The egress rules of the group are about the outbound traffic of the instance itself, which a managed data service exposes no filter for: they are not carried |
| `module.vpc.module.vpc.aws_eip.nat` | RECONFIGURE / MEDIUM | Belonged to the NAT gateway, which is implicit on STACKIT. |
| `module.vpc.module.vpc.aws_internet_gateway.this` | RECONFIGURE / MEDIUM | No internet-gateway object: a routed STACKIT network reaches the internet with no route and no routing table (observed on the target in a project outside a Network Area, tested on 2026-10-10); inbound access is decided by public IPs and security groups. |
| `module.vpc.module.vpc.aws_nat_gateway.this` | RECONFIGURE / MEDIUM | No NAT-gateway object: outbound traffic leaves through the network's default routing (observed on the target, tested on 2026-10-10), by the project's shared publicIp, which is not fixed and cannot be set - the NAT gateway's fixed egress address is lost; a fixed address needs a STACKIT public IP per server. |
| `module.vpc.module.vpc.aws_route_table.private` | RECONFIGURE / LOW | A route to a NAT gateway, in a project outside a Network Area: STACKIT's default routing replaces the table (observed on the target, tested on 2026-10-10), but the NAT gateway's fixed egress address is not kept - the network leaves by its project's shared publicIp, which is not fixed and cannot be set; a fixed address needs a STACKIT public IP per server. |
| `module.vpc.module.vpc.aws_route_table.public` | RECONFIGURE / MEDIUM | Only internet-gateway routes, in a project outside a Network Area: STACKIT's default routing replaces the table (observed on the target, tested on 2026-10-10: a routed network reaches the internet with no route and no routing table). A server without a public IP, which has no internet path through an internet gateway in the source, leaves through the network's shared SNAT address on STACKIT: that egress is new and is decided on the subnet. |
| `module.vpc.module.vpc.aws_vpc.this` | RECONFIGURE / MEDIUM | No VPC container on STACKIT: each subnet became a stackit_network in the project (add a Network Area only if networks must route privately to each other). |

## Left for the architect

Design decisions StackToIT does not make (REDESIGN, MANUAL_REVIEW, BLOCKED), targets without a STACKIT Terraform resource, objects that are not STACKIT resources, or no generator yet; and a conditional unit that is switched off as the variables stand (listed with the note "Decided when OpenTofu plans"): it has no resource now and is the architect's to decide once the variable is set. See engineering/architect-report.md for the reasoning.

| Source | Class | Why |
|---|---|---|
| `module.retail_app_ecs.aws_cloudwatch_event_rule.ecs_events` | REDESIGN / MEDIUM | Decided when OpenTofu plans: its count is `(var.container_insights_setting == "enhanced" && var.lifecycle_events_enabled) ? 1 : 0`, which reads 0 as the variables stand, so the draft writes no resource for it. It is not dropped: assess again with a --var-file that sets the input, or decide it after a plan. |
| `module.retail_app_ecs.aws_cloudwatch_log_group.ecs_container_insights` | RECONFIGURE / MEDIUM | Decided when OpenTofu plans: its count is `(var.container_insights_setting == "enhanced" && var.lifecycle_events_enabled) ? 1 : 0`, which reads 0 as the variables stand, so the draft writes no resource for it. It is not dropped: assess again with a --var-file that sets the input, or decide it after a plan. |
| `module.retail_app_ecs.aws_cloudwatch_log_resource_policy.eventbridge_ecs` | RECONFIGURE / LOW | Decided when OpenTofu plans: its count is `(var.container_insights_setting == "enhanced" && var.lifecycle_events_enabled) ? 1 : 0`, which reads 0 as the variables stand, so the draft writes no resource for it. It is not dropped: assess again with a --var-file that sets the input, or decide it after a plan. |

## Support resources and execution helpers

These source resources are not units of the assessment and the draft writes no block of its own for them. A support resource is configuration other resources read (a target group, a subnet group, a policy attachment, a backup vault); an execution helper (a random value, a time delay, a null resource) belongs to the Terraform run and has no counterpart in the cloud. They are listed so that no resource of the source is missing from the package. The critical settings a support resource states itself are listed for a person's decision in `engineering/architect-report.md` (Support resources) and `engineering/review.csv` (the rows whose domain is support); a resource without a critical setting has none listed. The execution plan binds each of them to the unit that reads the resource (`execution-plan.json` coverage.units.support_settings, and `assurance/critical-semantics.json`): carried by that unit's OpenTofu step where its block writes the setting as the source states it (confirm only), else a gap its step names; none is a step of its own. A value a helper generates when OpenTofu applies (a password, a key) exists only in the state of the source: the draft does not carry it.

| Source | Kind | Read by |
|---|---|---|
| `module.dependencies.module.catalog_rds.aws_db_parameter_group.this` | support resource | `module.dependencies.module.catalog_rds.aws_rds_cluster.this` |
| `module.dependencies.module.orders_rds.aws_db_parameter_group.this` | support resource | `module.dependencies.module.orders_rds.aws_rds_cluster.this` |
| `module.dependencies.module.catalog_rds.aws_db_subnet_group.this` | support resource | `module.dependencies.module.catalog_rds.aws_rds_cluster.this` |
| `module.dependencies.module.orders_rds.aws_db_subnet_group.this` | support resource | `module.dependencies.module.orders_rds.aws_rds_cluster.this` |
| `module.dependencies.module.checkout_elasticache_redis.aws_elasticache_parameter_group.default` | support resource | `module.dependencies.module.checkout_elasticache_redis.aws_elasticache_replication_group.default` |
| `module.dependencies.module.checkout_elasticache_redis.aws_elasticache_subnet_group.default` | support resource | `module.dependencies.module.checkout_elasticache_redis.aws_elasticache_replication_group.default` |
| `module.dependencies.aws_iam_policy.carts_dynamo` | support resource | `module.retail_app_ecs.module.carts_service.aws_iam_role.task_role`, `module.retail_app_ecs.module.checkout_service.aws_iam_role.task_role` |
| `module.retail_app_ecs.aws_iam_policy.catalog_policy` | support resource | `module.retail_app_ecs.module.catalog_service.aws_iam_role.task_execution_role` |
| `module.retail_app_ecs.aws_iam_policy.orders_policy` | support resource | `module.retail_app_ecs.module.orders_service.aws_iam_role.task_execution_role` |
| `module.retail_app_ecs.module.carts_service.aws_iam_policy.cloudwatch_logs` | support resource | `module.retail_app_ecs.module.carts_service.aws_iam_role.task_role` |
| `module.retail_app_ecs.module.carts_service.aws_iam_policy.ecs_exec` | support resource | `module.retail_app_ecs.module.carts_service.aws_iam_role.task_role` |
| `module.retail_app_ecs.module.catalog_service.aws_iam_policy.cloudwatch_logs` | support resource | `module.retail_app_ecs.module.catalog_service.aws_iam_role.task_role` |
| `module.retail_app_ecs.module.catalog_service.aws_iam_policy.ecs_exec` | support resource | `module.retail_app_ecs.module.catalog_service.aws_iam_role.task_role` |
| `module.retail_app_ecs.module.checkout_service.aws_iam_policy.cloudwatch_logs` | support resource | `module.retail_app_ecs.module.checkout_service.aws_iam_role.task_role` |
| `module.retail_app_ecs.module.checkout_service.aws_iam_policy.ecs_exec` | support resource | `module.retail_app_ecs.module.checkout_service.aws_iam_role.task_role` |
| `module.retail_app_ecs.module.orders_service.aws_iam_policy.cloudwatch_logs` | support resource | `module.retail_app_ecs.module.orders_service.aws_iam_role.task_role` |
| `module.retail_app_ecs.module.orders_service.aws_iam_policy.ecs_exec` | support resource | `module.retail_app_ecs.module.orders_service.aws_iam_role.task_role` |
| `module.retail_app_ecs.module.ui_service.aws_iam_policy.cloudwatch_logs` | support resource | `module.retail_app_ecs.module.ui_service.aws_iam_role.task_role` |
| `module.retail_app_ecs.module.ui_service.aws_iam_policy.ecs_exec` | support resource | `module.retail_app_ecs.module.ui_service.aws_iam_role.task_role` |
| `module.retail_app_ecs.module.alb.aws_lb_target_group.main` | support resource | `module.retail_app_ecs.module.alb.aws_lambda_permission.lb`, `module.retail_app_ecs.module.alb.aws_lb.this`, `module.retail_app_ecs.module.ui_service.aws_ecs_service.this` |
| `module.dependencies.module.catalog_rds.aws_rds_cluster_parameter_group.this` | support resource | `module.dependencies.module.catalog_rds.aws_rds_cluster.this` |
| `module.dependencies.module.orders_rds.aws_rds_cluster_parameter_group.this` | support resource | `module.dependencies.module.orders_rds.aws_rds_cluster.this` |
| `module.retail_app_ecs.aws_service_discovery_private_dns_namespace.this` | support resource | `module.retail_app_ecs.module.carts_service.aws_ecs_service.this`, `module.retail_app_ecs.module.catalog_service.aws_ecs_service.this`, `module.retail_app_ecs.module.checkout_service.aws_ecs_service.this`, `module.retail_app_ecs.module.orders_service.aws_ecs_service.this`, `module.retail_app_ecs.module.ui_service.aws_ecs_service.this` |
| `module.retail_app_ecs.random_id.eventbridge_suffix` | execution helper | `module.retail_app_ecs.aws_cloudwatch_event_rule.ecs_events`, `module.retail_app_ecs.aws_cloudwatch_log_group.ecs_container_insights` |
| `module.dependencies.random_password.mq_password` | execution helper | `module.dependencies.aws_mq_broker.mq`, `module.retail_app_ecs.aws_secretsmanager_secret.mq` |
| `module.dependencies.random_string.catalog_db_master` | execution helper | `module.dependencies.module.catalog_rds.aws_rds_cluster.this` |
| `module.dependencies.random_string.catalog_opensearch_master` | execution helper | `module.dependencies.module.catalog_opensearch.aws_opensearch_domain.this`, `module.retail_app_ecs.aws_secretsmanager_secret.catalog_opensearch` |
| `module.dependencies.random_string.orders_db_master` | execution helper | `module.dependencies.module.orders_rds.aws_rds_cluster.this` |
| `module.retail_app_ecs.random_string.random_catalog_secret` | execution helper | `module.retail_app_ecs.aws_secretsmanager_secret.catalog_db`, `module.retail_app_ecs.aws_secretsmanager_secret.catalog_opensearch` |
| `module.retail_app_ecs.random_string.random_mq_secret` | execution helper | `module.retail_app_ecs.aws_secretsmanager_secret.mq` |
| `module.retail_app_ecs.random_string.random_orders_secret` | execution helper | `module.retail_app_ecs.aws_secretsmanager_secret.orders_db` |

## Switched off by a plan-time input

These source resources are no units of their own and read 0 instances as the variables stand: their count or for_each reads the default of a root variable (or the workspace). The draft writes nothing of them, and they are not dropped: if production sets the input so that one exists, it needs what its row says — decide or confirm it, and assess again with a --var-file that sets the input as production does. The step of its parent in the execution plan says the same (coverage.units.conditional).

| Source | Decided by | Condition | If production switches it on, it needs |
|---|---|---|---|
| `module.retail_app_ecs.aws_cloudwatch_event_target.ecs_events_target` | `var.container_insights_setting`, `var.lifecycle_events_enabled` | its count is `(var.container_insights_setting == "enhanced" && var.lifecycle_events_enabled) ? 1 : 0`, which reads 0 as the variables stand | its aws_cloudwatch_event_target configuration decided with module.retail_app_ecs.aws_cloudwatch_event_rule.ecs_events, for which the draft writes no resource either; it reads module.retail_app_ecs.aws_cloudwatch_log_group.ecs_container_insights, module.retail_app_ecs.random_id.eventbridge_suffix |
| `module.retail_app_ecs.module.carts_service.aws_iam_policy.cloudwatch_agent` | `var.opentelemetry_enabled` | its count is `var.opentelemetry_enabled ? 1 : 0`, which reads 0 as the variables stand | a STACKIT counterpart of its own for the aws_iam_policy, decided like any other resource of the source (no unit of this scan carries it, and the draft writes nothing of it); it reads module.retail_app_ecs.aws_cloudwatch_log_group.ecs_tasks |
| `module.retail_app_ecs.module.carts_service.aws_iam_role_policy_attachment.cloudwatch_agent` | `var.opentelemetry_enabled` | its count is `var.opentelemetry_enabled ? 1 : 0`, which reads 0 as the variables stand | its aws_iam_role_policy_attachment configuration added to stackit_service_account.retail_app_ecs_carts_service_task_role, the STACKIT target of module.retail_app_ecs.module.carts_service.aws_iam_role.task_role (the draft writes nothing of it as the variables stand); it reads module.retail_app_ecs.module.carts_service.aws_iam_policy.cloudwatch_agent |
| `module.retail_app_ecs.module.catalog_service.aws_iam_policy.cloudwatch_agent` | `var.opentelemetry_enabled` | its count is `var.opentelemetry_enabled ? 1 : 0`, which reads 0 as the variables stand | a STACKIT counterpart of its own for the aws_iam_policy, decided like any other resource of the source (no unit of this scan carries it, and the draft writes nothing of it); it reads module.retail_app_ecs.aws_cloudwatch_log_group.ecs_tasks |
| `module.retail_app_ecs.module.catalog_service.aws_iam_role_policy_attachment.cloudwatch_agent` | `var.opentelemetry_enabled` | its count is `var.opentelemetry_enabled ? 1 : 0`, which reads 0 as the variables stand | its aws_iam_role_policy_attachment configuration added to stackit_service_account.retail_app_ecs_catalog_service_task_role, the STACKIT target of module.retail_app_ecs.module.catalog_service.aws_iam_role.task_role (the draft writes nothing of it as the variables stand); it reads module.retail_app_ecs.module.catalog_service.aws_iam_policy.cloudwatch_agent |
| `module.retail_app_ecs.module.checkout_service.aws_iam_policy.cloudwatch_agent` | `var.opentelemetry_enabled` | its count is `var.opentelemetry_enabled ? 1 : 0`, which reads 0 as the variables stand | a STACKIT counterpart of its own for the aws_iam_policy, decided like any other resource of the source (no unit of this scan carries it, and the draft writes nothing of it); it reads module.retail_app_ecs.aws_cloudwatch_log_group.ecs_tasks |
| `module.retail_app_ecs.module.checkout_service.aws_iam_role_policy_attachment.cloudwatch_agent` | `var.opentelemetry_enabled` | its count is `var.opentelemetry_enabled ? 1 : 0`, which reads 0 as the variables stand | its aws_iam_role_policy_attachment configuration added to stackit_service_account.retail_app_ecs_checkout_service_task_role, the STACKIT target of module.retail_app_ecs.module.checkout_service.aws_iam_role.task_role (the draft writes nothing of it as the variables stand); it reads module.retail_app_ecs.module.checkout_service.aws_iam_policy.cloudwatch_agent |
| `module.retail_app_ecs.module.orders_service.aws_iam_policy.cloudwatch_agent` | `var.opentelemetry_enabled` | its count is `var.opentelemetry_enabled ? 1 : 0`, which reads 0 as the variables stand | a STACKIT counterpart of its own for the aws_iam_policy, decided like any other resource of the source (no unit of this scan carries it, and the draft writes nothing of it); it reads module.retail_app_ecs.aws_cloudwatch_log_group.ecs_tasks |
| `module.retail_app_ecs.module.orders_service.aws_iam_role_policy_attachment.cloudwatch_agent` | `var.opentelemetry_enabled` | its count is `var.opentelemetry_enabled ? 1 : 0`, which reads 0 as the variables stand | its aws_iam_role_policy_attachment configuration added to stackit_service_account.retail_app_ecs_orders_service_task_role, the STACKIT target of module.retail_app_ecs.module.orders_service.aws_iam_role.task_role (the draft writes nothing of it as the variables stand); it reads module.retail_app_ecs.module.orders_service.aws_iam_policy.cloudwatch_agent |
| `module.retail_app_ecs.module.ui_service.aws_iam_policy.cloudwatch_agent` | `var.opentelemetry_enabled` | its count is `var.opentelemetry_enabled ? 1 : 0`, which reads 0 as the variables stand | a STACKIT counterpart of its own for the aws_iam_policy, decided like any other resource of the source (no unit of this scan carries it, and the draft writes nothing of it); it reads module.retail_app_ecs.aws_cloudwatch_log_group.ecs_tasks |
| `module.retail_app_ecs.module.ui_service.aws_iam_role_policy_attachment.cloudwatch_agent` | `var.opentelemetry_enabled` | its count is `var.opentelemetry_enabled ? 1 : 0`, which reads 0 as the variables stand | its aws_iam_role_policy_attachment configuration added to stackit_service_account.retail_app_ecs_ui_service_task_role, the STACKIT target of module.retail_app_ecs.module.ui_service.aws_iam_role.task_role (the draft writes nothing of it as the variables stand); it reads module.retail_app_ecs.module.ui_service.aws_iam_policy.cloudwatch_agent |

## Semantic ledger

The security or resilience properties the draft tracks in this ledger, and what it does with each: PRESERVED, TRANSFORMED (kept another way), UNSUPPORTED (STACKIT has no equivalent), REVIEW (an architect decides or fills in a value). One row can close several critical semantics of the unit (the ones that name it as `ledger:<property>` in engineering/architect-report.md): a semantic is carried when its row is PRESERVED or TRANSFORMED and stays open when the row is REVIEW or UNSUPPORTED. The other critical semantics of a unit are decided in engineering/architect-report.md, not here.

| Source | Property | Source value | Draft | Status |
|---|---|---|---|---|
| `module.dependencies.aws_security_group.catalog_opensearch_sg` | security group rules | security group rules | no rule is generated: the access lists of the members of the group carry it | **REVIEW** |
| `module.dependencies.aws_security_group.mq` | security group rules | security group rules | no rule is generated: the access lists of the members of the group carry it | **REVIEW** |
| `module.dependencies.module.catalog_rds.aws_rds_cluster.this` | backup retention | 7 days | service-managed backups, retention not set in this draft | **REVIEW** |
| `module.dependencies.module.catalog_rds.aws_security_group.this` | security group rules | security group rules | no rule is generated: the access lists of the members of the group carry it | **REVIEW** |
| `module.dependencies.module.checkout_elasticache_redis.module.aws_security_group.aws_security_group.cbd` | security group rules | security group rules | no rule is generated: the access lists of the members of the group carry it | **REVIEW** |
| `module.dependencies.module.orders_rds.aws_rds_cluster.this` | backup retention | 7 days | retention_days = 32 | **TRANSFORMED** |
| `module.dependencies.module.orders_rds.aws_security_group.this` | security group rules | security group rules | no rule is generated: the access lists of the members of the group carry it | **REVIEW** |
| `module.retail_app_ecs.module.alb.aws_lb.this` | network exposure | internet-facing | public address (ASSUMPTION: the unmanaged default network ACL of the subnets of module.retail_app_ecs.module.alb.aws_lb.this is taken as unrestricted, not verified) (SECURITY_OBSERVATION Plaintext public listener of module.retail_app_ecs.module.alb.aws_lb.this, port 80) | **REVIEW** |
| `module.retail_app_ecs.module.alb_sg.aws_security_group.this_name_prefix` | security group rules | security group rules | ORPHANED: no block of the draft attaches the group, so its rules restrict nothing on STACKIT | **REVIEW** |
| `module.retail_app_ecs.module.carts_service.aws_security_group.this` | security group rules | security group rules | ORPHANED: no block of the draft attaches the group, so its rules restrict nothing on STACKIT | **REVIEW** |
| `module.retail_app_ecs.module.catalog_service.aws_security_group.this` | security group rules | security group rules | ORPHANED: no block of the draft attaches the group, so its rules restrict nothing on STACKIT | **REVIEW** |
| `module.retail_app_ecs.module.checkout_service.aws_security_group.this` | security group rules | security group rules | ORPHANED: no block of the draft attaches the group, so its rules restrict nothing on STACKIT | **REVIEW** |
| `module.retail_app_ecs.module.orders_service.aws_security_group.this` | security group rules | security group rules | ORPHANED: no block of the draft attaches the group, so its rules restrict nothing on STACKIT | **REVIEW** |
| `module.retail_app_ecs.module.ui_service.aws_security_group.this` | security group rules | security group rules | ORPHANED: no block of the draft attaches the group, so its rules restrict nothing on STACKIT | **REVIEW** |
