feat(P58): single platform VPC + deterministic env-aware state keys

EXECUTE stage. Fixes the 4-VPC bug: adds a single shared VPC to
terraform/platform, drops the vpc child from the microservice composition
(references the platform VPC via data source), and makes state keys
env-aware (spike/{id}/{env}/terraform.tfstate — stable across lifecycle).

Platform VPC (terraform/platform/main.tf):
- aws_vpc.acdl_shared (10.0.0.0/16) + 2 subnets + IGW + route table + SG
- Outputs: vpc_id, subnet_ids, ecs_security_group_id

Microservice composition (modules/l2/microservice/composition.json):
- Dropped the vpc child (no per-contract VPC ever again).
- Added data_sources block: platform_vpc → terraform_remote_state (platform).
- Wires: vpc.outputs.subnet_ids → platform_vpc.outputs.subnet_ids.
- Wires: platform_vpc.outputs.vpc_id → alb.inputs.vpc_id.
- Wires: platform_vpc.outputs.ecs_security_group_id → service.inputs.security_group.

Contract resolver (core/contract_resolver.py):
- Added environment to the stack instance (stack.environment).
- Added data_sources handling: pseudo-children with outputs but no resources.
- data_sources propagated through fragment merge to the final stack instance.

Adapter (adapters/terraform/adapter.py):
- State key: spike/{stack_name}/{environment}/terraform.tfstate (env-aware).
- Emits data "terraform_remote_state" "platform" block when data_sources present.
- ref:platform_vpc.<output> → data.terraform_remote_state.platform.outputs.<output>.

Tests (tests/test_adapter.py):
- test_adapt_env_aware_state_key: spike/msvc/prod/terraform.tfstate.
- test_adapt_emits_data_source_block: data.terraform_remote_state.platform.
- test_adapt_no_vpc_for_microservice: no resource "aws_vpc" in microservice output.
- Updated existing state key assertion (spike/s3/dev/terraform.tfstate).

Regression: 467 passed, 0 skipped, 5 deselected. run_platform.sh --check-only
passes for both microservice (9 resources, no VPC) and static-assets (5 resources).

---ci---
project: acdl
phase: P58
milestone: v1.11
status: execute
---/ci---
This commit is contained in:
Jon Chery
2026-07-28 15:46:42 +00:00
parent 962ba24379
commit fda4564a7f
5 changed files with 208 additions and 19 deletions
+17
View File
@@ -232,6 +232,9 @@ def _resolve_l2(module_name, version, inputs, registry, repo_root):
# that actually declares that input (P1-1 — desired_count -> aws:ecs:service,
# family -> aws:ecs:task_definition).
child_input_map = {}
# data_source_names: set of child ids that are data sources (not modules)
# The adapter emits `data` blocks for these instead of `module` blocks.
data_source_names = set()
resources = []
# Expand children to resources
@@ -300,6 +303,15 @@ def _resolve_l2(module_name, version, inputs, registry, repo_root):
child_outputs[child_id] = child_out_map
child_input_map[child_id] = child_in_map
# P58: Process data_sources — pseudo-children that reference platform
# infrastructure via terraform_remote_state. They have outputs but no
# resources (the adapter emits `data` blocks, not `module` blocks).
for ds in composition.get("data_sources", []):
ds_name = ds["name"]
data_source_names.add(ds_name)
ds_outputs = ds.get("outputs", [])
child_outputs[ds_name] = {out: ds_name for out in ds_outputs}
# Resolve wires to populate inputs
for wire in composition.get("wires", []):
to_expr = wire["to"]
@@ -378,6 +390,7 @@ def _resolve_l2(module_name, version, inputs, registry, repo_root):
"resources": resources,
"features": features,
"outputs": stack_outputs,
"data_sources": list(data_source_names),
}
@@ -526,6 +539,7 @@ def resolve(contract_path, repo_root=None, environment_override=None):
# Merge fragments into a single stack instance
all_resources = []
all_data_sources = []
max_depth = 1
any_l2 = False
merged_features = {}
@@ -538,6 +552,7 @@ def resolve(contract_path, repo_root=None, environment_override=None):
any_l2 = True
max_depth = max(max_depth, fragment["depth"])
merged_features.update(fragment.get("features", {}))
all_data_sources.extend(fragment.get("data_sources", []))
if multi_module:
# Namespace resource IDs to avoid cross-module collisions
@@ -569,8 +584,10 @@ def resolve(contract_path, repo_root=None, environment_override=None):
"name": contract["id"],
"kind": kind,
"depth": max_depth,
"environment": contract.get("environment", "dev"),
},
"resources": all_resources,
"data_sources": all_data_sources,
}
# Add the human-readable title