Scaffolds or extends a Terraform module for this project's AWS infrastructure, wired to input.yaml, tagged per the cost-allocation strategy, and following least-privilege IAM. Use when the user asks to "create a Terraform module", "provision the VPC/Aurora/Lambda/Cognito", "add infrastructure for", or mentions Terraform, IaC, or AWS resource provisioning for this project.
80
100%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Passed
No known issues
Build or extend a Terraform module for $ARGUMENTS (e.g. "the network layer", "Aurora", "the Cognito user pool",
"the migration Lambda") following docs/guidelines/architecture.md. This skill provisions infrastructure — it does
not write application code (/implement-backend, /implement-frontend) or migrations (/alembic-migration).
input.yaml via local.input.<key> (architecture.md
§5.1–§5.2) — never a literal in a .tf file..tfvars files or a -var-file flag. This project reads input.yaml via yamldecode(file(...)) into
local.input (architecture.md §5.2) — don't reintroduce the HCL-tfvars pattern.default_tags
(architecture.md §5.3) — a per-resource tags = {...} block for the four mandated tags
(Environment/Project/Owner/ManagedBy) is redundant and risks drifting from the standard set.*) resource or action grant. Every Lambda role gets only the specific
Aurora/CloudWatch/Secrets Manager actions it needs (architecture.md §3, requirements.md NFR-005)..tfstate file..id in an aws_api_gateway_deployment's triggers block for a resource whose content can change
in place (e.g. aws_api_gateway_gateway_response's response_parameters/response_templates, or a method's
request_parameters). A resource's .id is stable across in-place updates, so an edit to a gateway response's
CORS headers (for example) updates the API's config but the trigger hash never changes — no new deployment is
created, and the live stage silently keeps serving the old behavior. Hash the mutable attribute itself
(jsonencode(...response_parameters), etc.) alongside or instead of .id for anything not immutable on update.The S3 bucket + DynamoDB lock table the DO NOT list above requires do not create themselves — terraform init for
an environment's backend "s3" {} block fails if they don't already exist, and that block can't read
state_bucket_name/lock_table_name from input.yaml either (Terraform resolves the backend before any
variables/locals). Both are handled by a separate, already-scaffolded bootstrap step, once per AWS account:
terraform/bootstrap/<env>/ — a small, local-state-only root module (not wired to any remote backend, since it's
what creates the backend) that provisions just the state bucket and lock table for that account. dev and prod
are separate AWS accounts in this project, so each has its own copy under bootstrap/dev/ and bootstrap/prod/,
but each reads its credentials from ../../environments/<env>/secrets.yaml rather than keeping a second copy —
bootstrap/<env>/ and environments/<env>/ always target the same AWS account, so one credentials file per
account, not one per module. Apply this by hand, once per account, before ever running terraform init in the
corresponding terraform/environments/<env>/.terraform/environments/<env>/backend.hcl — the partial backend config (bucket/key/region/dynamodb_table) an
environment's root module is initialized with via terraform init -backend-config=backend.hcl. Its values must
match that environment's bootstrap/<env>/input.yaml exactly. This .hcl file is the one deliberate exception to
the "no separate var files, only input.yaml" rule — it exists only because of Terraform's own backend-resolution
ordering, not because this project reintroduced .tfvars.Confirm both exist for the target environment before scaffolding that environment's root module; if they don't, build the bootstrap module first (it is out of scope for $ARGUMENTS unless the user is explicitly asking for it).
Unlike the network/persistence/identity layers, the application layer (Lambda + API Gateway) depends on an artifact
from an entirely different tool, not just another Terraform module's outputs: aws_lambda_function needs an actual
deployment package, which means backend/app (from /implement-backend) has to exist first. There's nothing to
package otherwise. If it doesn't exist yet, building this layer is either out of scope for now, or needs a
placeholder handler the user has explicitly asked for (see architecture.md's note on this trade-off).
Once backend/app exists, the deployment package still can't just be zip -r of a locally-installed virtualenv:
compiled dependencies (e.g. psycopg[binary]) ship platform-specific wheels, and a wheel built on a developer's Mac
won't load on Lambda's Linux runtime (architecture.md §2.3, "Cross-Platform Packaging"). Cross-install explicitly
(uv pip install --python-platform aarch64-manylinux_2_28 --python-version 3.12 --target <dir>, or the equivalent
for the chosen architecture) rather than assuming the build machine's platform matches Lambda's.
docs/guidelines/architecture.md §5 for the full input-variable matrix and the input.yaml pattern, plus
whichever specific section covers the layer being built (§2.1 frontend/CloudFront, §2.2 API Gateway/CORS, §2.4
persistence/Aurora/connection management, §2.5 Cognito user provisioning, §3 network/security, §4.1 state
backend).terraform/ modules for naming conventions, module boundaries, and provider configuration —
follow them.local.input.<key>, not hardcoded values or bare var.x at the
environment root.default_tags at the provider block; do not add per-resource tag blocks.terraform validate, tflint, and checkov -d ., and resolve every finding — this is a CI gate
(architecture.md §4.2), not optional cleanup.input.yaml and confirm nothing that should be configurable is hardcoded.docs/guidelines/architecture.md — the primary source for every infrastructure decision in this project.docs/guidelines/requirements.md — constraints (C-xxx) and NFRs with the concrete numbers a module must satisfy
(concurrency caps, backup retention days, tagging).../../rules/mcp-servers.md.dee4c03
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.