GitHub Copilot ChatGPT Claude Codex CLI Cursor opencode Skill Text

azure-validate

Pre-deployment validation for Azure readiness. Run deep checks on configuration, infrastructure (Bicep or Terraform), RBAC role assignments, managed identity permissions, and prerequisites before deploying. WHEN: validate my app, check deployment readiness, run preflight checks,

Ciza · 0 points · 22 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download microsoft-skills-.github_plugins_azure-skills_skills_azure-validate-e58528d.zip · 39 KB
Part of microsoft/skills — 195 skills

Install

skills CLI npx skills add https://github.com/microsoft/skills/tree/main/.github/plugins/azure-skills/skills/azure-validate
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install microsoft-skills@llmmart
Git git clone https://github.com/microsoft/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole microsoft/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Azure Validate

AUTHORITATIVE GUIDANCE — Follow these instructions exactly unless they contradict security policies given to you.

⛔ STOP — PREREQUISITE CHECK REQUIRED

Before proceeding, verify this prerequisite is met:

azure-prepare was invoked and completed → .azure/deployment-plan.md exists with status Approved or later

If the plan is missing, STOP IMMEDIATELY and invoke azure-prepare first.

The complete workflow ensures success:

azure-prepare → azure-validate → azure-deploy

Triggers

  • Check if app is ready to deploy
  • Validate azure.yaml or Bicep
  • Run preflight checks
  • Troubleshoot deployment errors

Rules

  1. Run after azure-prepare, before azure-deploy
  2. All checks must pass—do not deploy with failures
  3. ⛔ Destructive actions require ask_user — global-rules

Steps

Run the workflow script and follow its instructions. It walks you through each validation step one at a time, recording progress in .azure/validate-status.json. Use references/scripts/workflow.ps1 on Windows or references/scripts/workflow.sh on macOS/Linux.

Start by calling the script without the completed-step argument:

pwsh references/scripts/workflow.ps1 -WorkspacePath <workspace-path>
# macOS/Linux: bash references/scripts/workflow.sh --workspace-path <workspace-path>

Each run prints the next action and the value to pass next. Perform the action, then re-run with that value (-CompletedStep <value> for pwsh, --completed-step <value> for bash). Repeat until it reports the azure-validate workflow is complete.

The steps reference recipe details in references/recipes/README.md and role checks in references/role-verification.md.

⛔ VALIDATION AUTHORITY

This skill is the officially verified way to set plan status to Validated. You MUST follow the script's instructions to completion before setting status to Validated. Do NOT set status to Validated without doing so.


⚠️ NEXT STEP — DEPENDS ON USER INTENT

After ALL validations pass, check whether the user asked to deploy:

  • If the user explicitly requested deployment, you MUST invoke azure-deploy to execute it. Do NOT run azd up, azd deploy, or any deployment commands directly — let azure-deploy handle execution.
  • If the user only asked to validate or prepare (not deploy), STOP after recording proof and setting status to Validated. Report the validation results and do NOT invoke azure-deploy.

If any validation failed, fix the issues and re-run azure-validate before proceeding.

Files (skills)
  • references
    • recipes
      • azcli
        • errors.md 521 B
          # AZCLI Validation Errors
          
          | Error | Fix |
          |-------|-----|
          | `AADSTS700082: Token expired` | `az login` |
          | `Please run 'az login'` | `az login` |
          | `AADSTS50076: MFA required` | `az login --use-device-code` |
          | `AuthorizationFailed` | Request Contributor role |
          | `npm ci` fails with `missing: package-lock.json` | Run `npm install --package-lock-only` in the service directory before building |
          | `Template validation failed` | Check Bicep syntax |
          
          ## Debug
          
          ```bash
          az <command> --verbose --debug
          ```
          
        • README.md 2.8 KB
          # AZCLI Validation
          
          Validation steps for Azure CLI deployments.
          
          ## Prerequisites
          
          - `./infra/main.bicep` exists
          - Docker available (if containerized)
          
          ## Validation Steps
          
          - [ ] 1. Core Validation (CLI, auth, build, validate, what-if) — run [`validate-deployment` script](../scripts/validate-deployment.sh)
          - [ ] 2. Docker Build (if containerized)
          - [ ] 3. Azure Policy Validation
          
          ## Validation Details
          
          ### 1. Core Validation Script
          
          The core validation checks are a fixed, deterministic sequence. Run the shared
          **validate-deployment** helper instead of executing and parsing each command by hand. It
          confirms the Azure CLI is installed and authenticated, compiles the Bicep template
          (`az bicep build`), validates it against the target scope (`az deployment ... validate`),
          and runs a what-if preview — printing a compact PASS/FAIL summary plus a what-if change
          count (Create/Modify/Delete).
          
          - Bash: [`../scripts/validate-deployment.sh`](../scripts/validate-deployment.sh)
          - PowerShell: [`../scripts/validate-deployment.ps1`](../scripts/validate-deployment.ps1)
          
          **Subscription scope:**
          
          ```bash
          ../scripts/validate-deployment.sh --scope sub --location <location>
          ```
          ```powershell
          ../scripts/validate-deployment.ps1 -Scope sub -Location <location>
          ```
          
          **Resource group scope:**
          
          ```bash
          ../scripts/validate-deployment.sh --scope group --resource-group <rg-name>
          ```
          ```powershell
          ../scripts/validate-deployment.ps1 -Scope group -ResourceGroup <rg-name>
          ```
          
          Defaults: `--template ./infra/main.bicep`, `--parameters ./infra/main.parameters.json`
          (skipped if absent). Pass `--subscription <id>` to target a specific subscription.
          
          **Interpreting results:**
          
          - `OVERALL: PASS` — all five checks passed; record the summary in Section 7 (Validation Proof).
          - Any step `FAIL` — the script prints the failing command's error. Remediate:
            - **Authenticated** fails → `az login`, then `az account set --subscription <id>`.
            - **Azure CLI installed** fails → install via `mcp_azure_mcp_extension_cli_install(cli-type: "az")`.
            - Otherwise see [Error handling](./errors.md).
          
          ### 2. Docker Build (if containerized)
          
          **Before building**, validate the Docker build context:
          
          1. Read the `Dockerfile` in `./src/<service>`
          2. If the Dockerfile contains `npm ci`, verify `package-lock.json` exists in the same directory
          3. If `package-lock.json` is missing, generate it:
          
          ```bash
          cd ./src/<service>
          npm install --package-lock-only
          ```
          
          **Then build:**
          
          ```bash
          docker build -t <image>:test ./src/<service>
          ```
          
          ### 3. Azure Policy Validation
          
          See [Policy Validation Guide](../../policy-validation.md) for instructions on retrieving and validating Azure policies for your subscription.
          
          ## References
          
          - [Error handling](./errors.md)
          
          ## Next
          
          All checks pass → **azure-deploy**
          
      • azd
        • scripts
          • set-aspire-aca-env.ps1 3.5 KB · in bundle
          • set-aspire-aca-env.sh 3.6 KB
            #!/usr/bin/env bash
            # Set the Container Apps environment variables that Aspire "limited mode" leaves unpopulated.
            #
            # When Aspire runs in "limited mode", `azd provision` creates the Azure resources
            # (Container Registry, Managed Identity, Container Apps Environment) but does NOT populate the
            # env vars that `azd deploy` needs to reference them. This script fills that gap.
            #
            # Run it AFTER `azd provision` but BEFORE `azd deploy`.
            #
            # USAGE:
            #   ./set-aspire-aca-env.sh [-e <azd-env-name>]
            #
            #   -e, --environment   Optional azd environment name (forwarded to `azd env` calls).
            #                       Defaults to the current/default azd environment.
            #
            # The script only sets a variable if it is currently missing, and prints what it did so the
            # result can be understood without re-inspecting `azd env get-values`:
            #   AZURE_CONTAINER_REGISTRY_ENDPOINT              <- az acr list ... [0].loginServer
            #   AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID   <- az identity list ... [0].id
            #   MANAGED_IDENTITY_CLIENT_ID                     <- az identity list ... [0].clientId
            
            set -e
            
            AZD_ENV_NAME=""
            while [ $# -gt 0 ]; do
              case "$1" in
                -e|--environment)
                  AZD_ENV_NAME="$2"
                  shift 2
                  ;;
                *)
                  echo "ERROR: Unknown argument: $1" >&2
                  echo "USAGE: ./set-aspire-aca-env.sh [-e <azd-env-name>]" >&2
                  exit 1
                  ;;
              esac
            done
            
            # Build the shared `-e <name>` argument list for azd calls (empty when no env name given).
            AZD_ENV_ARGS=""
            if [ -n "$AZD_ENV_NAME" ]; then
              AZD_ENV_ARGS="-e $AZD_ENV_NAME"
            fi
            
            # Capture azd environment values via command substitution so `set -e` aborts if the
            # `azd env get-values` call itself fails (rather than silently continuing with no values).
            AZD_VALUES=$(azd env get-values $AZD_ENV_ARGS)
            
            # get_env_value <KEY> — print the (unquoted) value of KEY from AZD_VALUES, empty if absent.
            # Uses only POSIX-friendly tools so it works on the widely-available Bash 3.2 (e.g. macOS).
            get_env_value() {
              printf '%s\n' "$AZD_VALUES" \
                | grep "^$1=" \
                | head -n 1 \
                | sed -e "s/^$1=//" -e 's/^"\(.*\)"$/\1/' -e "s/^'\(.*\)'$/\1/"
            }
            
            RG_NAME=$(get_env_value AZURE_RESOURCE_GROUP)
            if [ -z "$RG_NAME" ]; then
              echo "ERROR: AZURE_RESOURCE_GROUP is not set in the azd environment." >&2
              echo "Run 'azd provision' before this script so the resource group is available." >&2
              exit 1
            fi
            
            # set_if_missing <ENV_VAR_NAME> <description> <resolver command...>
            # The resolver command is passed as arguments and run via "$@" — no eval.
            set_if_missing() {
              var_name="$1"
              description="$2"
              shift 2
            
              existing=$(get_env_value "$var_name")
              if [ -n "$existing" ]; then
                echo "$var_name: already present ($existing)"
                return 0
              fi
            
              value=$("$@")
              if [ -z "$value" ]; then
                echo "ERROR: Could not resolve $var_name ($description) in resource group '$RG_NAME'." >&2
                echo "Confirm 'azd provision' completed and the resource exists." >&2
                exit 1
              fi
            
              azd env set $AZD_ENV_ARGS "$var_name" "$value"
              echo "$var_name: set to $value"
            }
            
            echo "Resource group: $RG_NAME"
            
            set_if_missing \
              "AZURE_CONTAINER_REGISTRY_ENDPOINT" \
              "container registry login server" \
              az acr list --resource-group "$RG_NAME" --query "[0].loginServer" -o tsv
            
            set_if_missing \
              "AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID" \
              "managed identity resource id" \
              az identity list --resource-group "$RG_NAME" --query "[0].id" -o tsv
            
            set_if_missing \
              "MANAGED_IDENTITY_CLIENT_ID" \
              "managed identity client id" \
              az identity list --resource-group "$RG_NAME" --query "[0].clientId" -o tsv
            
            echo "Aspire Container Apps environment variables are ready for 'azd deploy'."
            
        • aspire.md 1.7 KB
          # Aspire Validation
          
          > ⚠️ **Only load this file when the project is a .NET Aspire application.**
          
          Validation steps specific to .NET Aspire projects deployed via AZD.
          
          ## Detection
          
          A project is Aspire-based if any of these are true:
          
          | Indicator | Check |
          |-----------|-------|
          | AppHost project | `find . -name "*.AppHost.csproj"` |
          | Aspire.Hosting package | `grep -r "Aspire.Hosting" . --include="*.csproj"` |
          
          **If none found → skip this file entirely.**
          
          ---
          
          ## Pre-Provisioning: Functions Secret Storage
          
          > ⚠️ **CRITICAL — Must run BEFORE `azd provision`.**
          
          Check if the project uses Azure Functions within Aspire and ensure `AzureWebJobsSecretStorageType` is configured.
          See [Aspire Functions Secrets Reference](../../aspire-functions-secrets.md) for detection commands, fix examples, and full details.
          
          **If `AddAzureFunctionsProject` is NOT found**, skip this section.
          
          ---
          
          ## Post-Provisioning: Container Apps Environment Variables
          
          > ⚠️ **CRITICAL — Run AFTER `azd provision` but BEFORE `azd deploy`.**
          
          When using Aspire with Container Apps in "limited mode" (in-memory infrastructure generation), `azd provision` creates Azure resources but doesn't automatically populate environment variables that `azd deploy` needs.
          
          **Run the helper script** ([scripts/set-aspire-aca-env.sh](scripts/set-aspire-aca-env.sh) or [scripts/set-aspire-aca-env.ps1](scripts/set-aspire-aca-env.ps1)). It sets `AZURE_CONTAINER_REGISTRY_ENDPOINT`, `AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID`, and `MANAGED_IDENTITY_CLIENT_ID` (only the ones that are missing).
          
          **bash:**
          ```bash
          ./scripts/set-aspire-aca-env.sh          # or: -e <azd-env-name>
          ```
          
          **PowerShell:**
          ```powershell
          ./scripts/set-aspire-aca-env.ps1         # or: -Environment <azd-env-name>
          ```
          
        • environment.md 2 KB
          # AZD Environment Setup
          
          > **⛔ MANDATORY**: You MUST set up an AZD environment before running any deployment commands.
          
          ## Step 1: Check Existing Environments
          
          ```bash
          azd env list
          ```
          
          **If an environment is already selected** (marked with `*`), check its current values:
          
          ```bash
          azd env get-values
          ```
          
          If `AZURE_ENV_NAME`, `AZURE_SUBSCRIPTION_ID`, and `AZURE_LOCATION` are already set, confirm with user:
          
          ```
          Question: "An AZD environment is already configured. Would you like to use it?"
          
            Environment: {env-name}
            Subscription: {subscription-id}
            Location: {location}
          
          Choices: [
            "Yes, use this environment (Recommended)",
            "No, create a new environment"
          ]
          ```
          
          If user confirms → skip to **Verify Configuration**. Otherwise → continue to Step 2.
          
          **If NO environment exists or none is selected:**
          - Continue to Step 2
          
          ---
          
          ## Step 2: Create New Environment
          
          > **⛔ DO NOT use generic names like "dev", "prod", or "test"**
          >
          > These cause naming conflicts in Azure resource groups and resources. Always generate a unique name.
          
          ### Generate Suggested Name
          
          Use this pattern:
          ```
          {project-name}-{random-4-chars}
          ```
          
          **Examples:**
          - `dadjokes-x7k2`
          - `todoapp-m3p9`
          - `myapi-q5w8`
          
          ### Prompt User
          
          **You MUST use `ask_user` to confirm the environment name:**
          
          ```
          I need to create an AZD environment for this deployment.
          
          Suggested name: {project-name}-{random-4-chars}
          
          Would you like to use this name or enter a custom one?
          ```
          
          ### Create Environment
          
          After user confirms:
          ```bash
          azd env new <environment-name> --no-prompt
          ```
          
          ---
          
          ## Step 3: Configure Environment
          
          Set subscription and location:
          
          ```bash
          azd env set AZURE_SUBSCRIPTION_ID <subscription-id>
          azd env set AZURE_LOCATION <location>
          ```
          
          ---
          
          ## Verify Configuration
          
          ```bash
          azd env get-values
          ```
          
          Confirm these values are set:
          - `AZURE_ENV_NAME`
          - `AZURE_SUBSCRIPTION_ID`
          - `AZURE_LOCATION`
          
          ---
          
          ## Only Then Proceed
          
          After environment is configured, proceed with `azd up --no-prompt`.
          
        • errors.md 2.7 KB
          # AZD Validation Errors
          
          ## Pre-Deployment Errors
          
          These errors can be caught **before** running `azd up`:
          
          | Error | Cause | Resolution |
          |-------|-------|------------|
          | `Please run 'az login'` | Not authenticated | `az login` or `azd auth login` |
          | `No environment selected` | Missing azd environment | `azd env select <name>` or `azd env new <name> --no-prompt` |
          | `no default response for prompt 'Enter a unique environment name'` | No azd environment created, or missing `-e` flag | Run `azd env new <name> --no-prompt` OR use `azd init --from-code -e <name> --no-prompt` with the `-e` flag |
          | `no default response for prompt 'Enter a value for the 'environmentName'` | Environment variables not set | Run `azd env set AZURE_ENV_NAME <name>` |
          | `Service not found` | Service name mismatch | Check service name in azure.yaml |
          | `Invalid azure.yaml` | YAML syntax error | Fix YAML syntax |
          | `Project path does not exist` | Wrong service project path | Fix service project path in azure.yaml |
          | `Cannot connect to Docker daemon` | Docker not running | Start Docker Desktop |
          | `npm ci` fails with `missing: package-lock.json` | Dockerfile uses `npm ci` but `package-lock.json` not in build context | Run `npm install --package-lock-only` in the service directory before building |
          | `Could not find a part of the path 'infra\main.bicep'` | Missing infrastructure files | Generate infra/ folder before `azd up` |
          | `Invalid resource group location '<loc>'. The Resource group already exists in location '<other>'` | RG exists in different region | Check RG location first with `az group show`, use that region or new env name |
          | `expecting only '1' resource tagged with 'azd-service-name: web', but found '2'` | Multiple resources with same tag **in the same RG** | Delete duplicate or rename service |
          
          ## Static Web App Errors
          
          | Error | Cause | Fix |
          |-------|-------|-----|
          | `language 'html' is not supported` | Invalid language value | Omit `language` for pure static sites |
          | `language 'static' is not supported` | Invalid language value | Omit `language` for pure static sites |
          | `dist folder not found` | Wrong dist path or missing build | Check `dist` is relative to `project`; add `language: js` if build needed |
          | `LocationNotAvailableForResourceType` | SWA not in region | See [Region Availability](../../region-availability.md) for valid regions |
          
          ## SWA Path Validation
          
          Before deployment, verify:
          1. `project` path exists and contains source files
          2. For framework apps: `language: js` is set
          3. `dist` is relative to `project` (not project root)
          4. Bicep has `azd-service-name` tag matching service name
          
          ## Debug
          
          ```bash
          azd <command> --debug
          ```
          
        • README.md 5.4 KB
          # AZD Validation
          
          Validation steps for Azure Developer CLI projects.
          
          ## Prerequisites
          
          - `azure.yaml` exists in project root
          - Infrastructure files exist:
            - For Bicep: `./infra/` contains Bicep files
            - For Terraform: `./infra/` contains `.tf` files and `azure.yaml` has `infra.provider: terraform`
          
          ## Validation Steps
          
          - [ ] 1. AZD Installation
          - [ ] 2. Schema Validation
          - [ ] 3. Environment Setup
          - [ ] 4. Authentication Check
          - [ ] 5. Subscription/Location Check
          - [ ] 6. Aspire Pre-Provisioning Checks
          - [ ] 7. Provision Preview
          - [ ] 8. Build Verification
          - [ ] 9. Docker Build Context Validation
          - [ ] 10. Package Validation
          - [ ] 11. Azure Policy Validation
          - [ ] 12. Aspire Post-Provisioning Checks
          
          ## Validation Details
          
          ### 1. AZD Installation
          
          Verify AZD is installed:
          
          ```bash
          azd version
          ```
          
          **If not installed:**
          ```
          mcp_azure_mcp_extension_cli_install(cli-type: "azd")
          ```
          
          ### 2. Schema Validation
          
          Validate azure.yaml against official schema:
          
          ```
          mcp_azure_mcp_azd(command: "validate_azure_yaml", parameters: { path: "./azure.yaml" })
          ```
          
          ### 3. Environment Setup
          
          Verify AZD environment exists and is configured. See [Environment Setup](environment.md) for detailed steps.
          
          ### 4. Authentication Check
          
          ```bash
          azd auth login --check-status
          ```
          
          **If not logged in:**
          ```bash
          azd auth login
          ```
          
          ### 5. Subscription/Location Check
          
          Check environment values:
          ```bash
          azd env get-values
          ```
          
          **If AZURE_SUBSCRIPTION_ID or AZURE_LOCATION not set:**
          
          Use Azure MCP tools to list subscriptions:
          ```
          mcp_azure_mcp_subscription_list
          ```
          
          Use Azure MCP tools to list resource groups (check for conflicts):
          ```
          mcp_azure_mcp_group_list
            subscription: <subscription-id>
          ```
          
          Prompt user to confirm subscription and location before continuing.
          
          Refer to the region availability reference to select a region supported by all services in this template:
          - [Region availability](../../region-availability.md)
          
          ```bash
          azd env set AZURE_SUBSCRIPTION_ID <subscription-id>
          azd env set AZURE_LOCATION <location>
          ```
          
          ### 6. Aspire Pre-Provisioning Checks
          
          **If this is a .NET Aspire project** (detected by `*.AppHost.csproj` or `Aspire.Hosting` package reference), run the **Pre-Provisioning** checks in [Aspire Validation](aspire.md) before continuing. **If not Aspire, skip this step.**
          
          ### 7. Provision Preview
          
          Validate IaC is ready (must complete without error):
          
          ```bash
          azd provision --preview --no-prompt
          ```
          
          > 💡 **Note:** This works for both Bicep and Terraform. azd will automatically detect the provider from `azure.yaml` and run the appropriate validation (`bicep build` or `terraform plan`).
          
          **If `azd provision --preview` fails with `unsupported resource type`:**
          
          ⛔ **Stop deployment immediately.** Do NOT attempt to fix or work around the error.
          
          This error means the Aspire AppHost contains custom resource types that have no Azure deployment target (e.g., `HealthChecksUI`, custom child resources, or local-only integrations). These resources are intentionally designed for local development tooling and cannot be meaningfully deployed to Azure.
          
          **Required actions:**
          1. ⛔ **Do NOT modify source code** to suppress the error (e.g., do not add `.ExcludeFromManifest()`).
          2. ⛔ **Do NOT proceed with `azd provision` or `azd deploy`.**
          3. ✅ Inform the user: "This application contains custom Aspire resource types (`unsupported resource type: <type-name>`) that cannot be deployed to Azure. The application is designed for local development only."
          4. ✅ Record a blocker: "Deployment blocked — AppHost contains unsupported resource types with no Azure deployment target."
          
          > ⚠️ Adding `.ExcludeFromManifest()` to suppress the error may allow provisioning to proceed, but the resulting deployment will not represent the application's actual functionality and is not a valid deployment.
          
          ### 8. Build Verification
          
          Build the project and verify there are no errors. If the build fails, fix the issues and re-build until it succeeds. Do NOT proceed to packaging or deployment with build errors.
          
          ### 9. Docker Build Context Validation
          
          **If any service in `azure.yaml` uses a Dockerfile** (check the service's `project` path from `azure.yaml` for a `Dockerfile`), validate the build context before packaging:
          
          1. Read each service's `Dockerfile`
          2. If the Dockerfile contains `npm ci`, verify `package-lock.json` exists in the same directory
          3. If `package-lock.json` is missing, generate it in the service's `project` path directory before proceeding:
          
          ```bash
          cd <service-project-path>
          npm install --package-lock-only
          ```
          
          > ⚠️ **Warning:** `npm ci` will fail during Docker build if `package-lock.json` is missing. This check prevents Docker build failures during `azd package` and `azd up`.
          
          ### 10. Package Validation
          
          Confirm all services package successfully:
          
          ```bash
          azd package --no-prompt
          ```
          
          ### 11. Azure Policy Validation
          
          See [Policy Validation Guide](../../policy-validation.md) for instructions on retrieving and validating Azure policies for your subscription.
          
          ### 12. Aspire Post-Provisioning Checks
          
          **If this is a .NET Aspire project**, run the **Post-Provisioning** checks in [Aspire Validation](aspire.md) before proceeding to deployment. **If not Aspire, skip this step.**
          
          ## References
          
          - [Environment Setup](environment.md)
          - [Aspire Validation](aspire.md)
          - [Error Handling](./errors.md)
          
          ## Next
          
          All checks pass → **azure-deploy**
          
      • bicep
        • errors.md 389 B
          # Bicep Validation Errors
          
          | Error | Fix |
          |-------|-----|
          | `BCP035: Invalid type` | Check API version |
          | `BCP037: Not a member` | Check resource schema |
          | `BCP018: Expected character` | Fix syntax |
          | `Module not found` | Check relative paths |
          | `Template validation failed` | Review error details |
          
          ## Debug
          
          ```bash
          az bicep build --file ./infra/main.bicep 2>&1
          ```
          
        • README.md 2.6 KB
          # Bicep Validation
          
          Validation steps for standalone Bicep deployments.
          
          ## Prerequisites
          
          - `./infra/main.bicep` exists
          - `./infra/main.parameters.json` exists
          - Azure CLI authenticated
          
          ## Validation Steps
          
          - [ ] 1. Core Validation (CLI, auth, build, validate, what-if) — run [`validate-deployment` script](../scripts/validate-deployment.sh)
          - [ ] 2. Linting (optional)
          - [ ] 3. Azure Policy Validation
          
          ## Validation Details
          
          ### 1. Core Validation Script
          
          The core validation checks are a fixed, deterministic sequence — identical to the AZCLI
          recipe. Run the shared **validate-deployment** helper instead of executing and parsing each
          command by hand. It confirms the Azure CLI is installed and authenticated, compiles the Bicep
          template (`az bicep build`), validates it against the target scope (`az deployment ...
          validate`), and runs a what-if preview — printing a compact PASS/FAIL summary plus a what-if
          change count (Create/Modify/Delete).
          
          - Bash: [`../scripts/validate-deployment.sh`](../scripts/validate-deployment.sh)
          - PowerShell: [`../scripts/validate-deployment.ps1`](../scripts/validate-deployment.ps1)
          
          **Subscription scope:**
          
          ```bash
          ../scripts/validate-deployment.sh --scope sub --location <location>
          ```
          ```powershell
          ../scripts/validate-deployment.ps1 -Scope sub -Location <location>
          ```
          
          **Resource group scope:**
          
          ```bash
          ../scripts/validate-deployment.sh --scope group --resource-group <rg-name>
          ```
          ```powershell
          ../scripts/validate-deployment.ps1 -Scope group -ResourceGroup <rg-name>
          ```
          
          Defaults: `--template ./infra/main.bicep`, `--parameters ./infra/main.parameters.json`
          (skipped if absent). Pass `--subscription <id>` to target a specific subscription.
          
          **Interpreting results:**
          
          - `OVERALL: PASS` — all checks passed; record the summary in Section 7 (Validation Proof).
          - Any step `FAIL` — the script prints the failing command's error. If **Authenticated** fails,
            run `az login`. Otherwise see [Error handling](./errors.md).
          
          ### 2. Linting (optional)
          
          Use Bicep linter rules:
          
          ```bash
          az bicep lint --file ./infra/main.bicep
          ```
          
          ### 3. Azure Policy Validation
          
          See [Policy Validation Guide](../../policy-validation.md) for instructions on retrieving and validating Azure policies for your subscription.
          
          ## Checklist
          
          | Check | Command | Pass |
          |-------|---------|------|
          | Core validation (CLI, auth, build, validate, what-if) | `validate-deployment` script | ☐ |
          | Policies validated | MCP Policy tool | ☐ |
          
          ## References
          
          - [Error handling](./errors.md)
          
          ## Next
          
          All checks pass → **azure-deploy**
          
      • scripts
        • validate-deployment.ps1 5.2 KB · in bundle
        • validate-deployment.sh 6 KB
          #!/usr/bin/env bash
          # validate-deployment.sh
          # Runs the standard Azure CLI pre-deployment validation sequence for a Bicep
          # template and reports PASS/FAIL for each step. Shared by the AZCLI and Bicep
          # validation recipes.
          #
          # Steps (in order):
          #   1. az version         - Azure CLI is installed
          #   2. az account show    - authenticated to Azure
          #   3. az bicep build     - template compiles cleanly
          #   4. az deployment ... validate  - template validates against the target scope
          #   5. az deployment ... what-if   - preview changes (with a Create/Modify/Delete summary)
          #
          # Usage:
          #   ./validate-deployment.sh --scope sub   --location <location>       [options]
          #   ./validate-deployment.sh --scope group --resource-group <rg-name>  [options]
          #
          # Options:
          #   --scope <sub|group>       Deployment scope (required)
          #   --location <location>     Location (required when --scope sub)
          #   --resource-group <name>   Resource group (required when --scope group)
          #   --template <path>         Bicep template (default: ./infra/main.bicep)
          #   --parameters <path>       Parameters file (default: ./infra/main.parameters.json;
          #                             skipped automatically if the file does not exist)
          #   --subscription <id>       Subscription to target (optional)
          #
          # Examples:
          #   ./validate-deployment.sh --scope sub --location eastus
          #   ./validate-deployment.sh --scope group --resource-group my-rg \
          #       --template ./infra/main.bicep --parameters ./infra/main.parameters.json
          #
          # Exit codes:
          #   0 - every validation step passed
          #   1 - a validation step failed
          #   2 - usage / argument error (unknown or valueless option, missing required flag)
          
          set -uo pipefail
          
          # Ensure an option that consumes a value actually has one ($@ = remaining args).
          need_val() {
              [ "$#" -ge 2 ] || { echo "ERROR: $1 requires a value." >&2; exit 2; }
          }
          
          SCOPE=""
          LOCATION=""
          RESOURCE_GROUP=""
          TEMPLATE="./infra/main.bicep"
          PARAMETERS="./infra/main.parameters.json"
          SUBSCRIPTION=""
          
          while [ $# -gt 0 ]; do
              case "$1" in
                  --scope)          need_val "$@"; SCOPE="$2"; shift 2 ;;
                  --location)       need_val "$@"; LOCATION="$2"; shift 2 ;;
                  --resource-group) need_val "$@"; RESOURCE_GROUP="$2"; shift 2 ;;
                  --template)       need_val "$@"; TEMPLATE="$2"; shift 2 ;;
                  --parameters)     need_val "$@"; PARAMETERS="$2"; shift 2 ;;
                  --subscription)   need_val "$@"; SUBSCRIPTION="$2"; shift 2 ;;
                  -h|--help)
                      grep '^#' "$0" | grep -v '^#!' | sed 's/^# \{0,1\}//'
                      exit 0 ;;
                  *)
                      echo "Unknown argument: $1" >&2
                      exit 2 ;;
              esac
          done
          
          # Validate arguments
          if [ "$SCOPE" != "sub" ] && [ "$SCOPE" != "group" ]; then
              echo "ERROR: --scope must be 'sub' or 'group'." >&2
              exit 2
          fi
          if [ "$SCOPE" = "sub" ] && [ -z "$LOCATION" ]; then
              echo "ERROR: --location is required when --scope is 'sub'." >&2
              exit 2
          fi
          if [ "$SCOPE" = "group" ] && [ -z "$RESOURCE_GROUP" ]; then
              echo "ERROR: --resource-group is required when --scope is 'group'." >&2
              exit 2
          fi
          
          # Build shared argument arrays
          SUB_ARGS=()
          [ -n "$SUBSCRIPTION" ] && SUB_ARGS=(--subscription "$SUBSCRIPTION")
          
          PARAM_ARGS=()
          if [ -f "$PARAMETERS" ]; then
              PARAM_ARGS=(--parameters "$PARAMETERS")
          else
              echo "NOTE: parameters file '$PARAMETERS' not found; validating without --parameters."
          fi
          
          if [ "$SCOPE" = "sub" ]; then
              SCOPE_TARGET_ARGS=(--location "$LOCATION")
              SCOPE_DESC="subscription (location: $LOCATION)"
          else
              SCOPE_TARGET_ARGS=(--resource-group "$RESOURCE_GROUP")
              SCOPE_DESC="resource group '$RESOURCE_GROUP'"
          fi
          
          # Track overall result (0 = all passed, 1 = at least one failure).
          OVERALL=0
          
          echo "=== Azure deployment validation ==="
          echo "Template:   $TEMPLATE"
          echo "Scope:      $SCOPE_DESC"
          echo ""
          
          # Step 1: Azure CLI installed
          echo "--- Step 1: Azure CLI installed (az version) ---"
          if az version >/dev/null 2>&1; then
              echo "PASS: Azure CLI is installed."
          else
              echo "FAIL: Azure CLI not found. Install it, then re-run."
              # Nothing else can run without the CLI.
              echo ""
              echo "OVERALL: FAIL"
              exit 1
          fi
          echo ""
          
          # Step 2: Authenticated
          echo "--- Step 2: Authenticated (az account show) ---"
          if ACCOUNT_NAME=$(az account show "${SUB_ARGS[@]}" --query name -o tsv 2>/dev/null); then
              echo "PASS: Authenticated (subscription: ${ACCOUNT_NAME:-unknown})."
          else
              echo "FAIL: Not logged in. Run 'az login' (and 'az account set --subscription <id>')."
              OVERALL=1
          fi
          echo ""
          
          # Step 3: Bicep compilation
          echo "--- Step 3: Bicep compilation (az bicep build) ---"
          BUILD_OUTPUT=$(az bicep build --file "$TEMPLATE" 2>&1)
          BUILD_RC=$?
          if [ $BUILD_RC -eq 0 ]; then
              echo "PASS: Template compiles cleanly."
          else
              echo "FAIL: Bicep compilation errors:"
              echo "$BUILD_OUTPUT"
              OVERALL=1
          fi
          echo ""
          
          # Step 4: Template validation
          echo "--- Step 4: Template validation (az deployment $SCOPE validate) ---"
          VALIDATE_OUTPUT=$(az deployment "$SCOPE" validate "${SCOPE_TARGET_ARGS[@]}" \
              --template-file "$TEMPLATE" \
              "${PARAM_ARGS[@]}" "${SUB_ARGS[@]}" 2>&1)
          VALIDATE_RC=$?
          if [ $VALIDATE_RC -eq 0 ]; then
              echo "PASS: Template validated against the target scope."
          else
              echo "FAIL: Template validation errors:"
              echo "$VALIDATE_OUTPUT"
              OVERALL=1
          fi
          echo ""
          
          # Step 5: What-if preview
          echo "--- Step 5: What-if preview (az deployment $SCOPE what-if) ---"
          WHATIF_OUTPUT=$(az deployment "$SCOPE" what-if "${SCOPE_TARGET_ARGS[@]}" \
              --template-file "$TEMPLATE" \
              "${PARAM_ARGS[@]}" "${SUB_ARGS[@]}" 2>&1)
          WHATIF_RC=$?
          if [ $WHATIF_RC -eq 0 ]; then
              CREATE_COUNT=$(echo "$WHATIF_OUTPUT" | grep -c '^[[:space:]]*+ ')
              MODIFY_COUNT=$(echo "$WHATIF_OUTPUT" | grep -c '^[[:space:]]*~ ')
              DELETE_COUNT=$(echo "$WHATIF_OUTPUT" | grep -c '^[[:space:]]*- ')
              echo "PASS: What-if completed. Changes -> Create: $CREATE_COUNT, Modify: $MODIFY_COUNT, Delete: $DELETE_COUNT"
          else
              echo "FAIL: What-if errors:"
              echo "$WHATIF_OUTPUT"
              OVERALL=1
          fi
          echo ""
          
          # Overall result
          if [ $OVERALL -eq 0 ]; then
              echo "OVERALL: PASS"
          else
              echo "OVERALL: FAIL"
          fi
          exit $OVERALL
          
      • terraform
        • scripts
          • validate-terraform.ps1 6.8 KB · in bundle
          • validate-terraform.sh 6.6 KB
            #!/usr/bin/env bash
            # validate-terraform.sh
            # Runs the Terraform pre-deployment validation preflight sequence and prints a
            # compact PASS/FAIL/SKIP summary plus the captured error text for any failed step.
            #
            # The script runs every check even if an earlier one fails, so you get a complete
            # verdict in a single call. It never fixes anything - it only runs and reports, so
            # you can jump straight to remediation for any failed step.
            #
            # Steps: terraform present, az present, authenticated, init, fmt -check, validate,
            #        plan, state list, Go-style {{ .Env.* }} template-variable scan.
            #
            # Usage:
            #   ./validate-terraform.sh [infra-dir] [subscription-id]
            #
            # Arguments:
            #   infra-dir         Path to the Terraform infra directory (default: ./infra).
            #   subscription-id   Optional subscription to select before checks.
            #
            # Examples:
            #   ./validate-terraform.sh                       # Validate ./infra
            #   ./validate-terraform.sh ./infra               # Validate an explicit directory
            #   ./validate-terraform.sh ./infra 00000000-0000-0000-0000-000000000000
            #
            # Exit code: 0 when every non-skipped step passes, 1 when any step fails.
            
            set -uo pipefail
            
            INFRA_DIR="${1:-./infra}"
            SUBSCRIPTION_ID="${2:-}"
            
            # --- result tracking ---------------------------------------------------------
            STEP_NAMES=()
            STEP_STATUS=()   # PASS | FAIL | SKIP
            STEP_ERRORS=()   # captured error text (empty unless FAIL)
            
            record() {
                # record <name> <status> [error-text]
                # Results are collected here and rendered once in the summary at the end.
                STEP_NAMES+=("$1")
                STEP_STATUS+=("$2")
                STEP_ERRORS+=("${3:-}")
            }
            
            echo "Terraform validation preflight - infra dir: $INFRA_DIR"
            echo ""
            
            # --- 1. Terraform installed --------------------------------------------------
            if command -v terraform >/dev/null 2>&1; then
                record "Terraform installed" "PASS"
            else
                record "Terraform installed" "FAIL" "terraform not found on PATH. Install: https://developer.hashicorp.com/terraform/install"
            fi
            
            # --- 2. Azure CLI installed --------------------------------------------------
            if command -v az >/dev/null 2>&1; then
                record "Azure CLI installed" "PASS"
            else
                record "Azure CLI installed" "FAIL" "az not found on PATH. Install the Azure CLI: mcp_azure_mcp_extension_cli_install(cli-type: \"az\")"
            fi
            
            # --- 3. Authentication -------------------------------------------------------
            if command -v az >/dev/null 2>&1; then
                if [ -n "$SUBSCRIPTION_ID" ]; then
                    if SUB_OUT=$(az account set --subscription "$SUBSCRIPTION_ID" 2>&1); then
                        record "Select subscription" "PASS"
                    else
                        record "Select subscription" "FAIL" "$SUB_OUT"
                    fi
                fi
                if ACCOUNT_OUT=$(az account show -o none 2>&1); then
                    record "Authenticated (az account show)" "PASS"
                else
                    record "Authenticated (az account show)" "FAIL" "$ACCOUNT_OUT"
                fi
            else
                record "Authenticated (az account show)" "SKIP" "Azure CLI not installed"
            fi
            
            # --- infra dir presence gate -------------------------------------------------
            HAVE_TF=false
            if command -v terraform >/dev/null 2>&1 && [ -d "$INFRA_DIR" ]; then
                HAVE_TF=true
            fi
            
            run_tf() {
                # run_tf <name> <terraform args...>
                local name="$1"; shift
                if [ "$HAVE_TF" != true ]; then
                    record "$name" "SKIP" "terraform unavailable or infra dir '$INFRA_DIR' not found"
                    return
                fi
                # Stream output to a temp file so large output (e.g. terraform plan) is not
                # held in memory; only read it back when the command fails.
                local tmp
                tmp=$(mktemp)
                if (cd "$INFRA_DIR" && terraform "$@") >"$tmp" 2>&1; then
                    record "$name" "PASS"
                else
                    record "$name" "FAIL" "$(cat "$tmp")"
                fi
                rm -f "$tmp"
            }
            
            # --- 4. Initialize -----------------------------------------------------------
            run_tf "terraform init" init -input=false
            
            # --- 5. Format check ---------------------------------------------------------
            run_tf "terraform fmt -check" fmt -check -recursive
            
            # --- 6. Validate syntax ------------------------------------------------------
            run_tf "terraform validate" validate
            
            # --- 7. Plan preview ---------------------------------------------------------
            run_tf "terraform plan" plan -input=false -out=tfplan
            
            # --- 8. State backend --------------------------------------------------------
            run_tf "terraform state list" state list
            
            # --- 9. Go-style template-variable scan --------------------------------------
            if [ -d "$INFRA_DIR" ]; then
                TEMPLATE_HITS=$(grep -rn '{{ *\.Env\.' "$INFRA_DIR" --include='*.tf' --include='*.tfvars.json' 2>/dev/null || true)
                if [ -n "$TEMPLATE_HITS" ]; then
                    record "Template-variable scan ({{ .Env.* }})" "FAIL" \
                        "Found unresolved Go-style template variables - replace {{ .Env.VAR }} with \${VAR} (azd envsubst format):
            $TEMPLATE_HITS"
                else
                    record "Template-variable scan ({{ .Env.* }})" "PASS"
                fi
            else
                record "Template-variable scan ({{ .Env.* }})" "SKIP" "infra dir '$INFRA_DIR' not found"
            fi
            
            # --- 10. main.tfvars.json JSON syntax ----------------------------------------
            TFVARS="$INFRA_DIR/main.tfvars.json"
            if [ -f "$TFVARS" ]; then
                if command -v python3 >/dev/null 2>&1; then
                    if JSON_ERR=$(python3 -c "import json,sys; json.load(open(sys.argv[1]))" "$TFVARS" 2>&1); then
                        record "main.tfvars.json is valid JSON" "PASS"
                    else
                        record "main.tfvars.json is valid JSON" "FAIL" "$JSON_ERR"
                    fi
                elif command -v jq >/dev/null 2>&1; then
                    if JSON_ERR=$(jq empty "$TFVARS" 2>&1); then
                        record "main.tfvars.json is valid JSON" "PASS"
                    else
                        record "main.tfvars.json is valid JSON" "FAIL" "$JSON_ERR"
                    fi
                else
                    record "main.tfvars.json is valid JSON" "SKIP" "no JSON parser (python3/jq) available"
                fi
            else
                record "main.tfvars.json is valid JSON" "SKIP" "$TFVARS not found"
            fi
            
            # --- summary -----------------------------------------------------------------
            echo ""
            echo "==================== SUMMARY ===================="
            printf '%-40s %s\n' "STEP" "RESULT"
            printf '%-40s %s\n' "----" "------"
            FAILED=0
            for i in "${!STEP_NAMES[@]}"; do
                printf '%-40s %s\n' "${STEP_NAMES[$i]}" "${STEP_STATUS[$i]}"
                [ "${STEP_STATUS[$i]}" = "FAIL" ] && FAILED=$((FAILED + 1))
            done
            echo "================================================="
            
            if [ "$FAILED" -gt 0 ]; then
                echo ""
                echo "----- FAILURE DETAILS -----"
                for i in "${!STEP_NAMES[@]}"; do
                    if [ "${STEP_STATUS[$i]}" = "FAIL" ]; then
                        echo ""
                        echo "### ${STEP_NAMES[$i]}"
                        echo "${STEP_ERRORS[$i]}"
                    fi
                done
                echo ""
                echo "RESULT: $FAILED step(s) failed. See remediation guidance in README.md."
                exit 1
            fi
            
            echo ""
            echo "RESULT: All checks passed. Ready for azure-deploy."
            exit 0
            
        • errors.md 2.9 KB
          # Terraform Validation Errors
          
          | Error | Fix |
          |-------|-----|
          | `Backend init failed` | Check storage account access |
          | `Provider version conflict` | Update required_providers |
          | `State lock failed` | Wait or force unlock |
          | `Validation failed` | Check terraform validate output |
          | `Error: Cycle:` | See [Cycle Errors](#cycle-errors) below |
          
          ## Cycle Errors
          
          `terraform validate` reports a cycle when two or more resources reference each other's attributes, creating a circular dependency.
          
          ### Common Pattern: CORS Cross-Reference
          
          Multi-service App Service deployments often introduce a cycle when the API's CORS configuration references the frontend hostname and the frontend's app settings reference the API hostname:
          
          ```
          Error: Cycle: azurerm_linux_web_app.frontend, azurerm_linux_web_app.api
          ```
          
          **Cause — circular attribute references:**
          
          ```hcl
          # API references frontend.default_hostname in CORS
          resource "azurerm_linux_web_app" "api" {
            site_config {
              cors {
                allowed_origins = ["https://${azurerm_linux_web_app.frontend.default_hostname}"]
              }
            }
          }
          
          # Frontend references api.default_hostname in app_settings
          resource "azurerm_linux_web_app" "frontend" {
            app_settings = {
              API_URL = "https://${azurerm_linux_web_app.api.default_hostname}"
            }
          }
          ```
          
          ### Fix Strategies
          
          **Option A (recommended):** Use a Terraform variable for the frontend origin so CORS is restrictive by default and the cycle is broken. Define the variable with a sensible default and pass the real frontend URL after the first deployment:
          
          ```hcl
          variable "frontend_origin" {
            type        = string
            description = "Frontend origin for API CORS. Set after first deployment."
            default     = ""
          }
          
          resource "azurerm_linux_web_app" "api" {
            site_config {
              cors {
                allowed_origins     = var.frontend_origin != "" ? [var.frontend_origin] : ["*"]
                support_credentials = var.frontend_origin != "" ? true : false
              }
            }
          }
          ```
          
          > ⚠️ **Warning:** If using `["*"]` as a temporary bootstrap value, you **must** set `frontend_origin` to the actual URL (e.g., `https://app-web-*.azurewebsites.net`) and re-run `terraform apply` in the same deployment session before reporting success. Do not leave wildcard CORS in a completed deployment.
          
          **Option B:** Use `azurerm_app_service_custom_hostname_binding` or a `null_resource` with a `local-exec` provisioner to configure CORS after both resources are created, breaking the dependency chain.
          
          **Option C:** Use `lifecycle { ignore_changes = [site_config[0].cors] }` on the API resource and configure CORS via a separate `azurerm_web_app_active_slot` or post-deployment script.
          
          ### After Fixing
          
          1. Run `terraform fmt -recursive` to fix formatting
          2. Re-run `terraform validate` to confirm the cycle is resolved
          3. Run `terraform plan` to verify the configuration is correct
          
          ## Debug
          
          ```bash
          TF_LOG=DEBUG terraform plan
          ```
          
        • README.md 4.1 KB
          # Terraform Validation
          
          Validation steps for Terraform deployments.
          
          ## Prerequisites
          
          - `./infra/main.tf` exists
          - State backend accessible
          
          ## Run the preflight script
          
          Run the pre-built validation script instead of executing each check by hand. It runs the
          full deterministic preflight sequence in one call and prints a compact **PASS / FAIL / SKIP**
          summary plus captured error text for any failed step — jump straight to remediation without
          re-parsing raw command output.
          
          | Script | Purpose |
          |--------|---------|
          | [`scripts/validate-terraform.sh`](scripts/validate-terraform.sh) | Bash preflight runner |
          | [`scripts/validate-terraform.ps1`](scripts/validate-terraform.ps1) | PowerShell preflight runner |
          
          The script runs, in order: Terraform installed → Azure CLI installed → authenticated
          (`az account show`) → `terraform init` → `fmt -check` → `validate` → `plan` →
          `state list` → Go-style `{{ .Env.* }}` template-variable scan → `main.tfvars.json`
          JSON-syntax check. A subscription-selection step is added when a subscription id is
          supplied. It runs **every** check even if an earlier one fails, and exits non-zero when
          any step fails.
          
          **Usage:**
          
          ```bash
          ./scripts/validate-terraform.sh [infra-dir] [subscription-id]   # infra-dir defaults to ./infra
          ```
          ```powershell
          .\scripts\validate-terraform.ps1 [-InfraDir <path>] [-SubscriptionId <id>]
          ```
          
          **Examples:**
          
          ```bash
          ./scripts/validate-terraform.sh                 # validate ./infra
          ./scripts/validate-terraform.sh ./infra 00000000-0000-0000-0000-000000000000
          ```
          ```powershell
          .\scripts\validate-terraform.ps1 -InfraDir ./infra
          ```
          
          **Reading the output:** the summary table lists every step as `PASS`, `FAIL`, or `SKIP`
          (skipped when a prerequisite such as Terraform or the infra directory is missing). Each
          `FAIL` is expanded in a **FAILURE DETAILS** section with the captured error text. Fix
          failed steps using the guidance below, then re-run the script.
          
          ## Remediation
          
          The script only **runs and reports** — fixing failures is manual. Guidance per step:
          
          ### Terraform / Azure CLI not installed
          
          - Terraform: see https://developer.hashicorp.com/terraform/install
          - Azure CLI: `mcp_azure_mcp_extension_cli_install(cli-type: "az")`
          
          ### Not authenticated / wrong subscription
          
          ```bash
          az login
          az account set --subscription <subscription-id>
          ```
          
          ### Format check failed
          
          ```bash
          terraform fmt -recursive
          ```
          
          ### Init / validate / plan / state failures
          
          Read the captured error text in the script output, then consult
          [Error handling](./errors.md).
          
          ### Azure Policy Validation
          
          The script does not cover policy checks. See
          [Policy Validation Guide](../../policy-validation.md) for retrieving and validating Azure
          policies for your subscription.
          
          ### Template Variable Resolution (AZD+Terraform)
          
          > ⚠️ **CRITICAL for azd+Terraform projects.** azd substitutes `${VAR}` references in
          > `main.tfvars.json` via envsubst, but does NOT interpolate Go-style template variables
          > (`{{ .Env.* }}`). Unresolved Go-style template strings passed to Terraform cause
          > cascading deployment failures, state conflicts, and timeouts.
          
          When the template-variable scan reports `FAIL`:
          
          1. **Fix the syntax** in `main.tfvars.json` — replace `{{ .Env.VAR }}` with `${VAR}`:
             ```json
             { "environment_name": "${AZURE_ENV_NAME}", "location": "${AZURE_LOCATION}" }
             ```
          2. For additional variables, use **`TF_VAR_*` environment variables**:
             ```bash
             azd env set TF_VAR_environment_name "$(azd env get-value AZURE_ENV_NAME)"
             ```
          3. **Verify** that `variables.tf` declares all required variables.
          4. **Re-run** the script to confirm `terraform validate` / `plan` and the scan now pass.
          
          > Prefer putting static defaults in `variables.tf` `default` values. Using `terraform.tfvars`
          > (HCL) for static defaults is acceptable if your team prefers it; this restriction is
          > specifically about avoiding Go-style template expressions in `.tfvars.json` files.
          
          ## References
          
          - [Error handling](./errors.md)
          
          ## Next
          
          All checks pass → **azure-deploy**
          
      • README.md 384 B
        # Recipes
        
        Validation recipes for different infrastructure approaches.
        
        | Recipe | When to Use |
        |--------|-------------|
        | [AZD](azd/README.md) | Projects using Azure Developer CLI |
        | [AZCLI](azcli/README.md) | Projects using Azure CLI scripts |
        | [Bicep](bicep/README.md) | Projects using Bicep templates |
        | [Terraform](terraform/README.md) | Projects using Terraform |
        
    • scripts
      • scan-aspire-functions-secrets.ps1 3 KB · in bundle
      • scan-aspire-functions-secrets.sh 3.3 KB
        #!/usr/bin/env bash
        # scan-aspire-functions-secrets.sh
        # Aspire + Azure Functions secret-storage pre-provisioning scan.
        #
        # Decides whether the AzureWebJobsSecretStorageType=Files fix is required by
        # scanning C# source for the Aspire Functions builder call and the setting.
        #
        # Logic:
        #   1. Find *.cs files that call AddAzureFunctionsProject.
        #   2. For each, check whether the same file already sets AzureWebJobsSecretStorageType.
        #   3. Files with the call but missing the setting -> fix required.
        #
        # Usage:
        #   ./scan-aspire-functions-secrets.sh [directory]
        #
        # Examples:
        #   ./scan-aspire-functions-secrets.sh              # Scan current directory
        #   ./scan-aspire-functions-secrets.sh ./src        # Scan a specific directory
        #
        # Output: a single verdict — NOT APPLICABLE, ALREADY CONFIGURED, or FIX REQUIRED
        # (with the matching file(s) and line(s)). Exit code is 0 for every verdict;
        # a non-zero exit only indicates a usage/environment error.
        
        set -euo pipefail
        
        ROOT="${1:-.}"
        
        if [ ! -d "$ROOT" ]; then
          echo "ERROR: '$ROOT' is not a directory." >&2
          exit 2
        fi
        
        CALL="AddAzureFunctionsProject"
        SETTING="AzureWebJobsSecretStorageType"
        
        # file_contains <fixed-string> <file>
        # Returns 0 if the file contains the literal string, 1 if not.
        # A grep read error (exit code 2) is fatal: this is a critical pre-provision
        # scan, so a file we cannot read must not be silently reported as "no match".
        file_contains() {
          local pattern="$1" file="$2" rc
          if grep -Fq -- "$pattern" "$file"; then
            return 0
          fi
          rc=$?
          if [ "$rc" -eq 2 ]; then
            echo "ERROR: failed to read '$file' while scanning for '$pattern'." >&2
            exit 3
          fi
          return 1
        }
        
        # first_line <fixed-string> <file>
        # Prints the 1-based line number of the first literal match (or nothing).
        first_line() {
          grep -Fn -- "$1" "$2" | head -n1 | cut -d: -f1
        }
        
        # Collect *.cs files that reference AddAzureFunctionsProject into an array.
        # find ... -print0 + read -d '' keeps paths intact even if they contain
        # newlines or spaces (genuinely NUL-safe, unlike a newline-joined string).
        matches=()
        while IFS= read -r -d '' file; do
          if file_contains "$CALL" "$file"; then
            matches+=("$file")
          fi
        done < <(find "$ROOT" -type f -name "*.cs" -print0)
        
        if [ "${#matches[@]}" -eq 0 ]; then
          echo "VERDICT: NOT APPLICABLE"
          echo "No '$CALL' call found in any *.cs file under '$ROOT'."
          echo "The Functions secret-storage check does not apply — skip it."
          exit 0
        fi
        
        # Partition matching files by whether they already configure the setting.
        needs_fix=()
        configured=()
        for file in "${matches[@]}"; do
          if file_contains "$SETTING" "$file"; then
            configured+=("$file")
          else
            needs_fix+=("$file")
          fi
        done
        
        if [ "${#needs_fix[@]}" -eq 0 ]; then
          echo "VERDICT: ALREADY CONFIGURED"
          echo "Every file that calls '$CALL' already sets '$SETTING':"
          for file in "${configured[@]}"; do
            echo "  - $file (line $(first_line "$SETTING" "$file"))"
          done
          echo "No change required."
          exit 0
        fi
        
        echo "VERDICT: FIX REQUIRED"
        echo "The following file(s) call '$CALL' but do NOT set '$SETTING':"
        for file in "${needs_fix[@]}"; do
          echo "  - $file (line $(first_line "$CALL" "$file"))"
        done
        echo ""
        echo "Add .WithEnvironment(\"$SETTING\", \"Files\") to the AddAzureFunctionsProject"
        echo "builder chain in each file above BEFORE running 'azd provision'."
        exit 0
        
      • workflow.ps1 5.2 KB · in bundle
      • workflow.sh 5.7 KB
        #!/usr/bin/env bash
        # workflow.sh
        # Walks the agent through the azure-validate workflow, one step at a time.
        #
        # Usage:
        #   ./workflow.sh --workspace-path <path> [--completed-step <step>]
        #
        # Options:
        #   --workspace-path <path>    Path to the workspace being validated (required).
        #   --completed-step <step>    The workflow step the agent just completed. Omit
        #                              on the first call to start the workflow. The
        #                              script records the value in
        #                              .azure/validate-status.json and returns the next
        #                              action to take, along with the value to pass as
        #                              --completed-step on the next call.
        #
        # Exit codes:
        #   0 - next action emitted (or workflow complete)
        #   2 - usage / argument error (missing workspace path or invalid step)
        
        set -uo pipefail
        
        # Valid workflow steps, in order.
        VALID_STEPS=(None LoadPlan AddValidationSteps RunValidation BuildVerification \
            StaticRoleVerification RecordProof ResolveErrors UpdateStatus)
        
        # Ensure an option that consumes a value actually has one ($@ = remaining args).
        need_val() {
            [ "$#" -ge 2 ] || { echo "ERROR: $1 requires a value." >&2; exit 2; }
        }
        
        WORKSPACE_PATH=""
        COMPLETED_STEP=""
        
        while [ $# -gt 0 ]; do
            case "$1" in
                --workspace-path) need_val "$@"; WORKSPACE_PATH="$2"; shift 2 ;;
                --completed-step) need_val "$@"; COMPLETED_STEP="$2"; shift 2 ;;
                -h|--help)
                    grep '^#' "$0" | grep -v '^#!' | sed 's/^# \{0,1\}//'
                    exit 0 ;;
                *)
                    echo "Unknown argument: $1" >&2
                    exit 2 ;;
            esac
        done
        
        if [ -z "$WORKSPACE_PATH" ]; then
            echo "ERROR: --workspace-path is required." >&2
            exit 2
        fi
        
        if [ ! -d "$WORKSPACE_PATH" ]; then
            echo "Error: --workspace-path '$WORKSPACE_PATH' does not exist or is not a directory." >&2
            exit 2
        fi
        
        # Resolve the step the agent just completed (case-insensitive).
        # Omitting --completed-step signals the start of the workflow (None).
        STEP="None"
        if [ -n "$COMPLETED_STEP" ]; then
            STEP=""
            for valid in "${VALID_STEPS[@]}"; do
                if [ "$(printf '%s' "$COMPLETED_STEP" | tr '[:upper:]' '[:lower:]')" = \
                     "$(printf '%s' "$valid" | tr '[:upper:]' '[:lower:]')" ]; then
                    STEP="$valid"
                    break
                fi
            done
            if [ -z "$STEP" ]; then
                printf 'Error: '\''--completed-step %s'\'' is not a valid step. Valid values: %s\n' \
                    "$COMPLETED_STEP" "$(printf '%s, ' "${VALID_STEPS[@]}" | sed 's/, $//')" >&2
                exit 2
            fi
        fi
        
        # Record progress in .azure/validate-status.json (creating it if needed).
        AZURE_DIR="$WORKSPACE_PATH/.azure"
        mkdir -p "$AZURE_DIR"
        VALIDATE_STATUS_PATH="$AZURE_DIR/validate-status.json"
        printf '{\n  "completedStep": "%s"\n}\n' "$STEP" > "$VALIDATE_STATUS_PATH"
        
        # Emit the next action based on the step just completed.
        case "$STEP" in
            None)
                # Step 1: Load Plan
                echo "Action: Read \`.azure/deployment-plan.md\` for recipe and configuration. If missing, run azure-prepare first, then come back to workflow.sh."
                echo "Next: re-run workflow.sh with --completed-step LoadPlan after completing the action."
                echo "Reference: \`.azure/deployment-plan.md"
                ;;
            LoadPlan)
                # Step 2: Add Validation Steps
                echo "Action: Copy the recipe's \`Validation Steps\` into \`.azure/deployment-plan.md\` as children of \`All validation checks pass\`."
                echo "Next: re-run workflow.sh with --completed-step AddValidationSteps after completing the action."
                echo "Reference: references/recipes/README.md, \`.azure/deployment-plan.md"
                ;;
            AddValidationSteps)
                # Step 3: Run Validation
                echo "Action: Execute the recipe-specific validation commands."
                echo "Next: re-run workflow.sh with --completed-step RunValidation after completing the action."
                echo "Reference: references/recipes/README.md"
                ;;
            RunValidation)
                # Step 4: Build Verification
                echo "Action: Build the project and fix any errors before proceeding."
                echo "Next: re-run workflow.sh with --completed-step BuildVerification after completing the action."
                echo "Reference: See the recipe for build details."
                ;;
            BuildVerification)
                # Step 5: Static Role Verification
                echo "Action: Review the Bicep/Terraform for correct RBAC role assignments in code."
                echo "Next: re-run workflow.sh with --completed-step StaticRoleVerification after completing the action."
                echo "Reference: references/role-verification.md"
                ;;
            StaticRoleVerification)
                # Step 6: Record Proof
                echo "Action: Populate **Section 7: Validation Proof** in the plan with the commands run and their results."
                echo "Next: re-run workflow.sh with --completed-step RecordProof after completing the action."
                echo "Reference: \`.azure/deployment-plan.md"
                ;;
            RecordProof)
                # Step 7: Resolve Errors
                echo "Action: Fix any validation failures before proceeding."
                echo "Next: re-run workflow.sh with --completed-step ResolveErrors after completing the action."
                echo "Reference: See the recipe's errors.md."
                ;;
            ResolveErrors)
                # Step 8: Update Status
                echo "Action: Only after ALL checks pass, set the plan status to \`Validated\`."
                echo "Next: re-run workflow.sh with --completed-step UpdateStatus after completing the action."
                echo "Reference: \`.azure/deployment-plan.md"
                ;;
            UpdateStatus)
                # Step 9: Deploy (workflow complete)
                echo "Action: The azure-validate workflow is complete. If the user explicitly requested deployment, invoke azure-deploy. Otherwise STOP and report the validation results."
                ;;
        esac
        
        exit 0
        
    • aspire-functions-secrets.md 3.7 KB
      # Aspire + Azure Functions: Secret Storage Validation
      
      > ⚠️ **Pre-provisioning check** — Run this BEFORE `azd provision`.
      
      ## When This Applies
      
      This check is required when **all** of these are true:
      
      | Condition | How to detect |
      |-----------|--------------|
      | .NET Aspire project | `*.AppHost.csproj` exists or `Aspire.Hosting` package reference |
      | Azure Functions component | `AddAzureFunctionsProject` call in `AppHost.cs` or `Program.cs` |
      | Identity-based storage | `WithHostStorage` call (Aspire default) |
      
      ## Detection
      
      Run the scan script — it finds `*.cs` files that call `AddAzureFunctionsProject`, checks whether each one already sets `AzureWebJobsSecretStorageType`, and prints a single verdict so you don't have to parse raw grep output.
      
      **Bash** — [`scripts/scan-aspire-functions-secrets.sh`](scripts/scan-aspire-functions-secrets.sh):
      ```bash
      ./scripts/scan-aspire-functions-secrets.sh [directory]   # directory defaults to .
      ```
      
      **PowerShell** — [`scripts/scan-aspire-functions-secrets.ps1`](scripts/scan-aspire-functions-secrets.ps1):
      ```powershell
      .\scripts\scan-aspire-functions-secrets.ps1 [-Path <directory>]   # -Path defaults to .
      ```
      
      The script prints one of three verdicts:
      
      | Verdict | Meaning | Action |
      |---------|---------|--------|
      | `NOT APPLICABLE` | No `AddAzureFunctionsProject` call found | Skip this check |
      | `ALREADY CONFIGURED` | Every matching file already sets `AzureWebJobsSecretStorageType` | No change required |
      | `FIX REQUIRED` | Matching file(s) call `AddAzureFunctionsProject` but omit the setting (file/line listed) | Apply the **Fix** below to each listed file |
      
      ## Fix
      
      Add `.WithEnvironment("AzureWebJobsSecretStorageType", "Files")` to the Azure Functions project builder chain in the AppHost source file that contains the `AddAzureFunctionsProject` call (often `Program.cs` in the `*.AppHost` project).
      
      ### Before
      
      ```csharp
      var functions = builder.AddAzureFunctionsProject<Projects.MyFunctions>("functions")
          .WithHostStorage(storage)
          .WithReference(queues);
      ```
      
      ### After
      
      ```csharp
      var functions = builder.AddAzureFunctionsProject<Projects.MyFunctions>("functions")
          .WithHostStorage(storage)
          .WithEnvironment("AzureWebJobsSecretStorageType", "Files")
          .WithReference(queues);
      ```
      
      > 💡 **Tip:** Place `.WithEnvironment(...)` immediately after `.WithHostStorage(...)` for clarity.
      
      ## Why This Is Required
      
      Azure Functions needs storage for managing host secrets/keys (function keys, host keys, master key). By default, it stores them as blobs in the `AzureWebJobsStorage` account.
      
      When Aspire configures identity-based storage access (via `WithHostStorage`), it sets URI-based environment variables like `AzureWebJobsStorage__blobServiceUri` instead of a connection string. The Functions runtime's secret manager does **not** support these identity-based URIs — it requires either a connection string or SAS token.
      
      Setting `AzureWebJobsSecretStorageType=Files` switches the Functions host to file-system-based key storage, bypassing the blob storage dependency for secrets.
      
      ## Error Without This Setting
      
      ```
      System.InvalidOperationException: Secret initialization from Blob storage failed
      due to missing both an Azure Storage connection string and a SAS connection URI.
      For Blob Storage, please provide at least one of these.
      ```
      
      ## When This Check Does NOT Apply
      
      | Scenario | Why |
      |----------|-----|
      | Aspire project without Azure Functions | No Functions secret manager involved |
      | Standalone Azure Functions (not Aspire) | Uses connection string by default |
      | Functions with explicit connection string | `AzureWebJobsStorage` is a full connection string, not identity-based |
      | `AzureWebJobsSecretStorageType` already set | Configuration is already present |
      
    • global-rules.md 1.2 KB
      # Global Rules
      
      > **MANDATORY** — These rules apply to ALL skills. Violations are unacceptable.
      
      ## Rule 1: Destructive Actions Require User Confirmation
      
      ⛔ **ALWAYS use `ask_user`** before ANY destructive action.
      
      ### What is Destructive?
      
      | Category | Examples |
      |----------|----------|
      | **Delete** | `az group delete`, `azd down`, `rm -rf`, delete resource |
      | **Overwrite** | Replace existing files, overwrite config, reset settings |
      | **Irreversible** | Purge Key Vault, delete storage account, drop database |
      | **Cost Impact** | Provision expensive resources, scale up significantly |
      | **Security** | Expose secrets, change access policies, modify RBAC |
      
      ### How to Confirm
      
      ```
      ask_user(
        question: "This will permanently delete resource group 'rg-myapp'. Continue?",
        choices: ["Yes, delete it", "No, cancel"]
      )
      ```
      
      ### No Exceptions
      
      - Do NOT assume user wants to delete/overwrite
      - Do NOT proceed based on "the user asked to deploy" (deploy ≠ delete old)
      - Do NOT batch destructive actions without individual confirmation
      
      ---
      
      ## Rule 2: Never Assume Subscription or Location
      
      ⛔ **ALWAYS use `ask_user`** to confirm:
      - Azure subscription (show actual name and ID)
      - Azure region/location
      
    • policy-validation.md 1.5 KB
      # Azure Policy Validation
      
      ## How to Validate Policies
      
      ### 1. Get Subscription ID
      
      Retrieve your current Azure subscription ID:
      
      ```bash
      az account show --query id -o tsv
      ```
      
      ### 2. Validate Policies
      
      Call the Azure MCP Policy tool to retrieve policies for your subscription:
      
      ```
      mcp_azure_mcp_policy(command: "list", parameters: { subscription_id: "<subscription-id>" })
      ```
      
      Replace `<subscription-id>` with the actual subscription ID obtained from step 1.
      
      ## Review Policy Compliance
      
      When validating Azure policies for your subscription:
      
      - **Check for policy violations** — Identify any resources or configurations that don't comply with assigned policies
      - **Verify organizational compliance** — Ensure the planned deployment meets all organizational policy requirements
      - **Address policy conflicts** — Resolve any policy issues before proceeding to deployment
      
      ## Common Policy Issues
      
      | Issue | Resolution |
      |-------|------------|
      | Non-compliant resource SKUs | Update resource SKUs to comply with allowed values |
      | Missing required tags | Add required tags to resources in your infrastructure code |
      | Disallowed resource types | Replace with allowed alternatives or request policy exception |
      | Location restrictions | Deploy to allowed regions only |
      | Network security violations | Update NSG rules, firewall settings, or virtual network configurations |
      
      ## Next Steps
      
      Only proceed to deployment after all policy violations are resolved and compliance is confirmed.
      
    • region-availability.md 3 KB
      # Azure Region Availability Reference
      
      > **AUTHORITATIVE SOURCE** — Consult this file BEFORE recommending any region.
      >
      > Official reference: https://azure.microsoft.com/en-us/explore/global-infrastructure/products-by-region/table
      
      ## How to Use
      
      1. Check if your architecture includes any **limited availability** services below
      2. If yes → refer to the table or use the MCP tool to list supported regions with sufficient quota for that service, and only offer regions that support ALL services
      3. If all services are "available everywhere" → offer common regions
      
      ## MCP Tools Used
      
      | Tool | Purpose |
      |------|---------|
      | `mcp_azure_mcp_quota` | Check Azure region availability and quota by setting `command` to `quota_usage_check` or `quota_region_availability_list` |
      
      ---
      
      ## Services with LIMITED Region Availability
      
      ### Azure Static Web Apps (SWA)
      
      ⚠️ **NOT available in many common regions**
      
      | ✅ Available | ❌ NOT Available (will FAIL) |
      |-------------|------------------------------|
      | `westus2` | `eastus` |
      | `centralus` | `northeurope` |
      | `eastus2` | `southeastasia` |
      | `westeurope` | `uksouth` |
      | `eastasia` | `canadacentral` |
      | | `australiaeast` |
      | | `westus3` |
      
      ---
      
      ### Azure OpenAI
      
      ⚠️ **Very limited — varies by model**
      
      | Region | GPT-4o | GPT-4 | GPT-3.5 | Embeddings |
      |--------|:------:|:-----:|:-------:|:----------:|
      | `eastus` | ✅ | ✅ | ✅ | ✅ |
      | `eastus2` | ✅ | ✅ | ✅ | ✅ |
      | `westus` | ⚠️ | ⚠️ | ✅ | ✅ |
      | `westus3` | ✅ | ⚠️ | ✅ | ✅ |
      | `southcentralus` | ✅ | ✅ | ✅ | ✅ |
      | `swedencentral` | ✅ | ✅ | ✅ | ✅ |
      | `westeurope` | ⚠️ | ✅ | ✅ | ✅ |
      
      > Check https://learn.microsoft.com/azure/ai-services/openai/concepts/models for current model availability.
      
      ---
      
      ### Azure Kubernetes Service (AKS)
      
      It has limited quota in some regions, to get available regions with enough quota, use `mcp_azure_mcp_quota` tool.
      
      ---
      
      ### Azure Database for PostgreSQL
      
      It has limited quota in some regions, to get available regions with enough quota, use `mcp_azure_mcp_quota` tool.
      
      ---
      
      ## Services Available in Most Regions
      
      These services are available in all major Azure regions — no special consideration needed:
      
      - **Container Apps**
      - **Azure Functions**
      - **App Service**
      - **Azure SQL Database**
      - **Cosmos DB**
      - **Key Vault**
      - **Storage Account**
      - **Service Bus**
      - **Event Grid**
      - **Application Insights / Log Analytics**
      
      ---
      
      ## Common Architecture Patterns
      
      | Pattern | Recommended Regions |
      |---------|---------------------|
      | SWA only | `westus2`, `centralus`, `eastus2`, `westeurope`, `eastasia` |
      | SWA + backend services | `westus2`, `centralus`, `eastus2`, `westeurope`, `eastasia` |
      | Container Apps (no SWA) | `eastus`, `eastus2`, `westus2`, `centralus`, `westeurope` |
      | With Azure OpenAI (GPT-4o/4/3.5 + embeddings) | `eastus`, `eastus2`, `swedencentral` |
      | SWA + Azure OpenAI (GPT-4o/4/3.5 + embeddings) | `eastus2` (only region with full SWA + model overlap) |
      
      ---
      
      **Last updated:** 2026-03-02
      
      
    • role-verification.md 3.9 KB
      # Role Assignment Verification
      
      Verify that all RBAC role assignments in the generated infrastructure are correct and sufficient before deployment. Incorrect or missing roles are a common cause of runtime failures.
      
      ## When to Verify
      
      After build verification (step 4) and **before** recording proof (step 6). Role issues surface as cryptic auth errors during deployment — catching them here saves debugging time.
      
      ## Verification Checklist
      
      Review every resource-to-identity relationship in the generated Bicep/Terraform:
      
      | Check | How |
      |-------|-----|
      | **Every service identity has roles** | Each app with a managed identity must have at least one role assignment |
      | **Roles match data operations** | Use service-specific **data-plane** roles for data access (see mapping table below); use generic Reader/Contributor/Owner only for management-plane operations |
      | **Scope is least privilege** | Roles scoped to specific resources, not resource groups or subscriptions |
      | **No missing roles** | Cross-check app code operations against assigned roles (see table below) |
      | **Local dev identity has roles** | If testing locally, the user's identity needs equivalent roles via `az login` |
      
      ## Common Service-to-Role Mapping
      
      | Service Operation | Required Role | Common Mistake |
      |-------------------|---------------|----------------|
      | Read blobs | Storage Blob Data Reader | Using generic Reader (no data access) |
      | Read + write blobs | Storage Blob Data Contributor | Missing write permission |
      | Generate SAS via user delegation | Storage Blob Delegator + Data Reader/Contributor | Forgetting Delegator role |
      | Read Key Vault secrets | Key Vault Secrets User | Using Key Vault Reader (no secret access) |
      | Read + write Cosmos DB | Cosmos DB Built-in Data Contributor | Using generic Contributor |
      | Send Service Bus messages | Azure Service Bus Data Sender | Using generic Contributor |
      | Read queues | Storage Queue Data Reader | Using Blob role for queues |
      
      ## How to Verify (Static Code Review)
      
      Review the generated Bicep/Terraform files directly — do **not** query live Azure state here. For each role assignment resource in your infrastructure code:
      
      1. Identify the **principal** (which managed identity)
      2. Identify the **role** (which role definition)
      3. Identify the **scope** (which target resource)
      4. Cross-check against the app code to confirm the role grants the required data-plane access
      
      > 💡 **Tip:** Search your Bicep for `Microsoft.Authorization/roleAssignments` or your Terraform for `azurerm_role_assignment` to find all role assignments.
      
      > ⚠️ **Live role verification** (querying Azure for actually provisioned roles) is handled by **azure-deploy** step 8 as a post-deployment check. This step is a static code review only.
      
      ## Decision Tree
      
      ```
      For each app identity in the generated infrastructure:
      ├── Has role assignments?
      │   ├── No → Add required role assignments to Bicep/Terraform
      │   └── Yes → Check each role:
      │       ├── Role matches code operations? → ✅ OK
      │       ├── Role too broad? → Narrow to least privilege
      │       └── Role insufficient? → Upgrade or add missing role
      │
      For local testing:
      ├── User identity has equivalent roles?
      │   ├── No → Grant roles via CLI or inform user
      │   └── Yes → ✅ Ready for functional verification
      ```
      
      > ⚠️ **Warning:** Generic roles like `Contributor` or `Reader` do **not** include data-plane access. For example, `Contributor` on a Storage Account cannot read blobs — you need `Storage Blob Data Contributor`. This is the most common RBAC mistake.
      
      ## Record in Plan
      
      After role verification, update `.azure/deployment-plan.md`:
      
      ```markdown
      ## Role Assignment Verification
      - Status: Verified / Issues Found
      - Identities checked: <list of app identities>
      - Roles confirmed: <list of role assignments>
      - Issues: <any missing or incorrect roles fixed>
      ```
      
  • SKILL.md 3.5 KB
    ---
    name: azure-validate
    description: "Pre-deployment validation for Azure readiness. Run deep checks on configuration, infrastructure (Bicep or Terraform), RBAC role assignments, managed identity permissions, and prerequisites before deploying. WHEN: validate my app, check deployment readiness, run preflight checks, verify configuration, check if ready to deploy, validate azure.yaml, validate Bicep, test before deploying, troubleshoot deployment errors, validate Azure Functions, validate function app, validate serverless deployment, verify RBAC roles, check role assignments, review managed identity permissions, what-if analysis, validate Container Apps deployment."
    license: MIT
    metadata:
      author: Microsoft
      version: "1.2.2"
    ---
    
    # Azure Validate
    
    > **AUTHORITATIVE GUIDANCE** — Follow these instructions exactly unless they contradict security policies given to you.
    
    > **⛔ STOP — PREREQUISITE CHECK REQUIRED**
    >
    > Before proceeding, verify this prerequisite is met:
    >
    > **azure-prepare** was invoked and completed → `.azure/deployment-plan.md` exists with status `Approved` or later
    >
    > If the plan is missing, **STOP IMMEDIATELY** and invoke **azure-prepare** first.
    >
    > The complete workflow ensures success:
    >
    > `azure-prepare` → `azure-validate` → `azure-deploy`
    
    ## Triggers
    
    - Check if app is ready to deploy
    - Validate azure.yaml or Bicep
    - Run preflight checks
    - Troubleshoot deployment errors
    
    ## Rules
    
    1. Run after azure-prepare, before azure-deploy
    2. All checks must pass—do not deploy with failures
    3. ⛔ **Destructive actions require `ask_user`** — [global-rules](references/global-rules.md)
    
    ## Steps
    
    Run the workflow script and follow its instructions. It walks you through each validation step one at a time, recording progress in `.azure/validate-status.json`. Use [references/scripts/workflow.ps1](references/scripts/workflow.ps1) on Windows or [references/scripts/workflow.sh](references/scripts/workflow.sh) on macOS/Linux.
    
    Start by calling the script **without** the completed-step argument:
    
    ```bash
    pwsh references/scripts/workflow.ps1 -WorkspacePath <workspace-path>
    # macOS/Linux: bash references/scripts/workflow.sh --workspace-path <workspace-path>
    ```
    
    Each run prints the next action and the value to pass next. Perform the action, then re-run with that value (`-CompletedStep <value>` for pwsh, `--completed-step <value>` for bash). Repeat until it reports the azure-validate workflow is complete.
    
    The steps reference recipe details in [references/recipes/README.md](references/recipes/README.md) and role checks in [references/role-verification.md](references/role-verification.md).
    
    > **⛔ VALIDATION AUTHORITY**
    >
    > This skill is the officially verified way to set plan status to `Validated`. You MUST follow the script's instructions to completion before setting status to `Validated`.
    > Do NOT set status to `Validated` without doing so.
    
    ---
    
    > **⚠️ NEXT STEP — DEPENDS ON USER INTENT**
    >
    > After ALL validations pass, check whether the user asked to deploy:
    > - **If the user explicitly requested deployment**, you **MUST** invoke **azure-deploy** to execute it. Do NOT run `azd up`, `azd deploy`, or any deployment commands directly — let azure-deploy handle execution.
    > - **If the user only asked to validate or prepare** (not deploy), STOP after recording proof and setting status to `Validated`. Report the validation results and do NOT invoke azure-deploy.
    >
    > If any validation failed, fix the issues and re-run azure-validate before proceeding.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related