Self-Review Checklist
4-layer adversarial review of generated IaC. Run after scaffold generates all files, before scaffold-manifest.json.
Rating System
- VERIFIED β confirmed correct by inspecting the generated code
- PLAUSIBLE β likely correct but cannot fully verify (e.g., API version exists but not checked against registry)
- FLAGGED β incorrect, missing, or contradicts the plan/patterns
Consume results: If any finding is FLAGGED β fix the IaC, then re-run validation. If all VERIFIED/PLAUSIBLE β proceed. Write findings to scaffold-manifest.json.selfReview.
β Halt on critical failures β if any finding is FLAGGED at L1 (Security) or L3 (Hallucination), do NOT proceed to deploy. Present findings and ask: "Fix / Continue with risks / Cancel".
β Step 12 validation remains mandatory regardless of self-review results β IaC may change during FLAGGED fixes.
Layer 1 β Security Claims Extraction
Extract every security claim from the generated IaC and check for internal contradictions.
| Check | Example |
|---|---|
| Managed identity declared but secret hardcoded | identity: { type: 'SystemAssigned' } but password: 'hardcoded' in same file |
| HTTPS enforced but HTTP endpoint exposed | httpsOnly: true but ingress allows HTTP |
| Resource accessed via managed identity but matching role not granted | KV secret read without KV Secrets User, ACR image pull without AcrPull, or any MIβresource dependency missing its roleAssignment β access fails at runtime β FLAGGED |
| β Role assignment scope targets wrong resource | scope: resourceGroup() on resource-specific roles β FLAGGED. Must scope to specific resource. |
principalType missing on role assignments |
Causes intermittent 30s+ delays |
| β Identity block missing on compute resource | β MANDATORY FAIL β ALL compute MUST have identity: { type: 'SystemAssigned' }. β HARD EXCEPTION β F1/D1 Linux: MI sidecar causes OOM on free tier β rate PLAUSIBLE, NEVER FLAGGED. The gen template intentionally omits MI for F1/D1. If F1/D1 detected in plan, this check MUST be PLAUSIBLE. |
SQL firewall 0.0.0.0/0 without private endpoint |
Prefer MI + private endpoint. AllowAzureServices genuinely needed β PLAUSIBLE. |
| β SCM/FTP auth policy missing on App Service | ALL App Service MUST have basicPublishingCredentialsPolicies: scm.allow: true, ftp.allow: false. Missing β FLAGGED. |
β KV URL uses environment().suffixes.keyvaultDns |
Leading dot β double-dot URL β ContainerAppSecretKeyVaultUrlInvalid. Use keyVault.name + .vault.azure.net or vaultUri output. β β FLAGGED |
Rating: Claims that contradict each other β FLAGGED. Consistent claims β VERIFIED.
Layer 2 β Pattern Validation
Validate against pattern files loaded at Steps 3β5 and rbac-roles.md.
Bicep
| Check | Source |
|---|---|
File structure: main.bicep β modules/*.bicep |
bicep-patterns.md |
main.parameters.json uses ARM JSON (not .bicepparam) |
bicep-patterns.md |
Naming: {prefix}{name}{token} β€32 chars |
bicep-patterns.md |
| System-assigned managed identity on all services | bicep-patterns-security.md |
No administratorLogin in generated Bicep |
bicep-patterns-security.md |
| KV uses RBAC authorization (not access policies) | bicep-patterns-security.md |
β enablePurgeProtection exists in KV module |
Remove β false rejected by ARM, true blocks KV deletion β β FLAGGED |
β KV deployer role assignment β Key Vault Secrets Officer for deployerObjectId scoped to KV resource |
bicep-patterns-security.md Β§ Key Vault Deployer RBAC. Without this, az keyvault secret set fails with 403. |
Prereq warnings[] each have a corresponding IaC fix |
Read env-var-secrets.md for SSL/TLS fixes |
Container Apps: two-phase ACR wiring, registries populated when ACR in plan, port alignment |
bicep-container-apps.md |
β BuildKit Dockerfile without Dockerfile.azure |
hasBuildKitSyntax == true but no Dockerfile.azure in files[] β β MANDATORY FAIL. ACR does not support BuildKit. |
β Role assignment scope targets specific resource, not resourceGroup() |
rbac-roles.md |
β No azure.yaml in scaffold-manifest.json.files[] |
pipeline-rules.md |
| Non-Azure TF in separate dir | terraform-patterns.md |
Cross-Module Reference Validation
Trace references BETWEEN modules β per-file checks miss broken cross-module wiring.
| Check | Rating |
|---|---|
Param wiring β every module call in main.bicep: verify every param without = default is passed |
Missing param β FLAGGED |
Secret ref completeness β every CA secrets[].keyVaultUrl has a matching KV secret resource |
Missing KV secret β FLAGGED |
Output ref validity β every moduleRef.outputs.X is declared in the referenced module |
Missing output β FLAGGED |
Terraform
| Check | Source |
|---|---|
File structure: main.tf, variables.tf, outputs.tf, backend.tf, modules/ |
mcp_azure_mcp_azureterraformbestpractices |
Provider: azurerm ~> 4.0 |
Terraform registry |
System-assigned managed identity, no administrator_login, KV RBAC |
terraform-patterns.md |
| Container Apps: two-phase ACR wiring | Same pattern as Bicep |
Rating: Matches β VERIFIED. Reasonable deviation β PLAUSIBLE. Violates β FLAGGED.
Layer 3 β Hallucination Detection
Catch fabricated resource types, API versions, SKU names, or properties.
Bicep (default path)
| Check | How |
|---|---|
| API versions, resource types, property names valid | bicep build β errors = FLAGGED |
β Deploy-time value validity (bicep build blind spot) |
The compiler accepts any schema-valid string, but ARM rejects wrong enum-like values (engine versions, region-restricted SKUs), wrong resource scope, and empty resource-ID properties. Any such value the generator chose from memory β not traceable to prepare-plan.json or a pattern file β MUST be confirmed against the provider capabilities API; unconfirmed β FLAGGED. |
SKU names match prepare-plan.json |
Cross-reference services[].sku |
OpenAI deployment uses scaleSettings instead of sku |
scaleSettings deprecated β FLAGGED |
Any resource uses -preview API version |
Use latest GA from MCP tool β FLAGGED |
| VNet subnets as separate child resources | Must be inline in properties.subnets[] β FLAGGED |
Run bicep build main.bicep --stdout > /dev/null as syntax + schema validation. Parse errors = FLAGGED.
β
az bicep buildis MANDATORY for L3. If unavailable, writeFLAGGEDwith "bicep build unavailable."
β Verify
main.bicephastargetScope = 'subscription'. Missing β FLAGGED (FIXABLE β add targetScope, RG resource with tags,scope: rgon modules).
mcp_bicep_build_bicep+az deployment sub what-ifalso appropriate at L3. β Do NOT useaz deployment sub validate(known bug).
Rating: Passes bicep build β VERIFIED. Build warning β PLAUSIBLE. Build error β FLAGGED.
Terraform (alternative path)
Run terraform init -backend=false && terraform validate + terraform plan -detailed-exitcode. Same rating criteria as Bicep.
Rating: Passes validate + plan β VERIFIED. Plan warning β PLAUSIBLE. Validate/plan error β FLAGGED.
Layer 4 β WAF Alignment
Per-pillar spot check against Azure Well-Architected Framework.
| Pillar | Check |
|---|---|
| Reliability | Health probes configured. β Verify probe path matches prereq-output.json.healthEndpoint β non-existent or mismatched path = FLAGGED. β ACA probes do NOT follow HTTP redirects β if app has trailing-slash normalization (Express redirect), use path WITHOUT trailing / (e.g., /app not /app/). |
| Security | No public blob access, TLS 1.2+, managed identity |
| Cost | SKU matches budget tier from prepare-plan.json |
| Ops | App Insights present + connected for APM, 5 AppOnboard tags on resources, all values parameterized. Present = VERIFIED. App Insights absent = PLAUSIBLE. β diagnostic-settings is not part of the plan β if the generator added one it MUST be gated (if (enableDiagnostics), default false) or absent; wired UNCONDITIONALLY in main.bicep = FLAGGED (blocks first deploy). |
| Performance | Autoscale rules present for production SKUs |
| Reliability | β Env var values compatible with app config validation (Pydantic Settings, Django settings.py). Typed fields reject wrong formats β FLAGGED |
Rating: Pillar addressed β VERIFIED. Not applicable for SKU β PLAUSIBLE. Missing for production SKU β FLAGGED.
Output
Write findings to scaffold-manifest.json.selfReview.findings[]: { "layer": "L1", "claim": "...", "rating": "FLAGGED", "detail": "..." } (layer is one of "L1"|"L2"|"L3"|"L4"). All FLAGGED must be resolved or surfaced at deploy gate. PLAUSIBLE = informational.