diff --git a/.gitea/workflows/docker-build.yml b/.gitea/workflows/docker-build.yml index 70b1d96..1c32e5d 100644 --- a/.gitea/workflows/docker-build.yml +++ b/.gitea/workflows/docker-build.yml @@ -4,6 +4,8 @@ on: push: branches: - dev + - main + - develop - auth-api - employee-api - transit-api @@ -13,6 +15,8 @@ on: pull_request: branches: - dev + - main + - develop env: REGISTRY: gitea.testinstance.nl @@ -31,6 +35,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Detect changed paths id: filter @@ -64,7 +70,7 @@ jobs: build-auth-api: needs: detect-changes - if: ${{ (gitea.ref_name == 'auth-api' || gitea.ref_name == 'dev') && needs.detect-changes.outputs.auth_api == 'true' }} + if: ${{ (gitea.ref_name == 'auth-api' || gitea.ref_name == 'dev' || gitea.ref_name == 'main' || gitea.ref_name == 'develop') && needs.detect-changes.outputs.auth_api == 'true' }} runs-on: ubuntu-latest steps: - name: Checkout code @@ -88,6 +94,9 @@ jobs: echo "tags<> "$GITHUB_OUTPUT" echo "${image_base}:${{ gitea.ref_name }}" >> "$GITHUB_OUTPUT" echo "${image_base}:${{ gitea.sha }}" >> "$GITHUB_OUTPUT" + if [[ "${{ gitea.ref_name }}" == "dev" || "${{ gitea.ref_name }}" == "main" || "${{ gitea.ref_name }}" == "develop" ]]; then + echo "${image_base}:latest" >> "$GITHUB_OUTPUT" + fi echo "EOF" >> "$GITHUB_OUTPUT" - name: Build and push auth_api @@ -99,7 +108,7 @@ jobs: build-employee-api: needs: detect-changes - if: ${{ (gitea.ref_name == 'employee-api' || gitea.ref_name == 'dev') && needs.detect-changes.outputs.employee_api == 'true' }} + if: ${{ (gitea.ref_name == 'employee-api' || gitea.ref_name == 'dev' || gitea.ref_name == 'main' || gitea.ref_name == 'develop') && needs.detect-changes.outputs.employee_api == 'true' }} runs-on: ubuntu-latest steps: - name: Checkout code @@ -123,6 +132,9 @@ jobs: echo "tags<> "$GITHUB_OUTPUT" echo "${image_base}:${{ gitea.ref_name }}" >> "$GITHUB_OUTPUT" echo "${image_base}:${{ gitea.sha }}" >> "$GITHUB_OUTPUT" + if [[ "${{ gitea.ref_name }}" == "dev" || "${{ gitea.ref_name }}" == "main" || "${{ gitea.ref_name }}" == "develop" ]]; then + echo "${image_base}:latest" >> "$GITHUB_OUTPUT" + fi echo "EOF" >> "$GITHUB_OUTPUT" - name: Build and push employee_api @@ -134,7 +146,7 @@ jobs: build-transit-api: needs: detect-changes - if: ${{ (gitea.ref_name == 'transit-api' || gitea.ref_name == 'dev') && needs.detect-changes.outputs.transit_api == 'true' }} + if: ${{ (gitea.ref_name == 'transit-api' || gitea.ref_name == 'dev' || gitea.ref_name == 'main' || gitea.ref_name == 'develop') && needs.detect-changes.outputs.transit_api == 'true' }} runs-on: ubuntu-latest steps: - name: Checkout code @@ -158,6 +170,9 @@ jobs: echo "tags<> "$GITHUB_OUTPUT" echo "${image_base}:${{ gitea.ref_name }}" >> "$GITHUB_OUTPUT" echo "${image_base}:${{ gitea.sha }}" >> "$GITHUB_OUTPUT" + if [[ "${{ gitea.ref_name }}" == "dev" || "${{ gitea.ref_name }}" == "main" || "${{ gitea.ref_name }}" == "develop" ]]; then + echo "${image_base}:latest" >> "$GITHUB_OUTPUT" + fi echo "EOF" >> "$GITHUB_OUTPUT" - name: Build and push transit_api @@ -169,7 +184,7 @@ jobs: build-error-log-api: needs: detect-changes - if: ${{ (gitea.ref_name == 'error-log-api' || gitea.ref_name == 'dev') && needs.detect-changes.outputs.error_log_api == 'true' }} + if: ${{ (gitea.ref_name == 'error-log-api' || gitea.ref_name == 'dev' || gitea.ref_name == 'main' || gitea.ref_name == 'develop') && needs.detect-changes.outputs.error_log_api == 'true' }} runs-on: ubuntu-latest steps: - name: Checkout code @@ -193,6 +208,9 @@ jobs: echo "tags<> "$GITHUB_OUTPUT" echo "${image_base}:${{ gitea.ref_name }}" >> "$GITHUB_OUTPUT" echo "${image_base}:${{ gitea.sha }}" >> "$GITHUB_OUTPUT" + if [[ "${{ gitea.ref_name }}" == "dev" || "${{ gitea.ref_name }}" == "main" || "${{ gitea.ref_name }}" == "develop" ]]; then + echo "${image_base}:latest" >> "$GITHUB_OUTPUT" + fi echo "EOF" >> "$GITHUB_OUTPUT" - name: Build and push error_log_api @@ -204,7 +222,7 @@ jobs: build-admin-ui: needs: detect-changes - if: ${{ (gitea.ref_name == 'admin-ui' || gitea.ref_name == 'dev') && needs.detect-changes.outputs.admin_ui == 'true' }} + if: ${{ (gitea.ref_name == 'admin-ui' || gitea.ref_name == 'dev' || gitea.ref_name == 'main' || gitea.ref_name == 'develop') && needs.detect-changes.outputs.admin_ui == 'true' }} runs-on: ubuntu-latest steps: - name: Checkout code @@ -228,6 +246,9 @@ jobs: echo "tags<> "$GITHUB_OUTPUT" echo "${image_base}:${{ gitea.ref_name }}" >> "$GITHUB_OUTPUT" echo "${image_base}:${{ gitea.sha }}" >> "$GITHUB_OUTPUT" + if [[ "${{ gitea.ref_name }}" == "dev" || "${{ gitea.ref_name }}" == "main" || "${{ gitea.ref_name }}" == "develop" ]]; then + echo "${image_base}:latest" >> "$GITHUB_OUTPUT" + fi echo "EOF" >> "$GITHUB_OUTPUT" - name: Build and push admin_ui @@ -239,7 +260,7 @@ jobs: build-monitoring-ui: needs: detect-changes - if: ${{ (gitea.ref_name == 'monitoring-ui' || gitea.ref_name == 'dev') && needs.detect-changes.outputs.monitoring_ui == 'true' }} + if: ${{ (gitea.ref_name == 'monitoring-ui' || gitea.ref_name == 'dev' || gitea.ref_name == 'main' || gitea.ref_name == 'develop') && needs.detect-changes.outputs.monitoring_ui == 'true' }} runs-on: ubuntu-latest steps: - name: Checkout code @@ -263,6 +284,9 @@ jobs: echo "tags<> "$GITHUB_OUTPUT" echo "${image_base}:${{ gitea.ref_name }}" >> "$GITHUB_OUTPUT" echo "${image_base}:${{ gitea.sha }}" >> "$GITHUB_OUTPUT" + if [[ "${{ gitea.ref_name }}" == "dev" || "${{ gitea.ref_name }}" == "main" || "${{ gitea.ref_name }}" == "develop" ]]; then + echo "${image_base}:latest" >> "$GITHUB_OUTPUT" + fi echo "EOF" >> "$GITHUB_OUTPUT" - name: Build and push monitoring_ui @@ -283,7 +307,7 @@ jobs: build-admin-ui, build-monitoring-ui, ] - if: ${{ gitea.event_name == 'push' && gitea.ref_name == 'dev' }} + if: ${{ always() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') && gitea.event_name == 'push' && (gitea.ref_name == 'dev' || gitea.ref_name == 'main') }} runs-on: ubuntu-latest steps: diff --git a/.github/agents/.version b/.github/agents/.version deleted file mode 100644 index ed0d9e9..0000000 --- a/.github/agents/.version +++ /dev/null @@ -1 +0,0 @@ -0.13.1 \ No newline at end of file diff --git a/.github/agents/azure-debug-generate/.metadata.json b/.github/agents/azure-debug-generate/.metadata.json deleted file mode 100644 index abd818a..0000000 --- a/.github/agents/azure-debug-generate/.metadata.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "azure-debug-generate", - "version": "0.0.1" -} diff --git a/.github/agents/azure-debug-generate/instructions.md b/.github/agents/azure-debug-generate/instructions.md deleted file mode 100644 index 84976c1..0000000 --- a/.github/agents/azure-debug-generate/instructions.md +++ /dev/null @@ -1,92 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Azure Debug Generate - -> **AUTHORITATIVE GUIDANCE — MANDATORY COMPLIANCE** -> -> This document is the **official, canonical source** for generating VS Code debug -> configuration from an approved plan. You **MUST** follow these instructions -> exactly as written. When in doubt, defer to this document. Do not improvise, -> infer, or substitute steps. - ---- - -## Global Rules (NO EXCEPTIONS) - -1. **Plan is the source of truth** — Read `.azure/vscode-debug-plan.md` and generate exactly what it specifies. Only generate artifacts for rows where `Generate` is checked (`[x]`). -2. **Update plan progressively** — Mark steps complete as you go; update **Last Updated** timestamp on every status change -3. ❌ **Destructive actions require `ask_user`** — Always confirm before overwriting, deleting, or modifying existing files -4. **Preserve existing config** — Never silently overwrite project configuration files or `docker-compose.yml`. Merge or ask first. -5. **Scope — VS Code debug setup only** — These instructions are for generating local debug configurations in VS Code. Cloud architecture, IaC generation, provisioning, and deployment are handled by the **azure-deploy** agent through the complete **azure-app-onboard** pipeline. -6. **Warn on limited support** — When a project type, runtime, or emulator declared in the plan has no matching reference file, emit a `⚠️ LIMITED SUPPORT:` warning — [limited-support.md](references/limited-support.md). - ---- - -## Autopilot mode - -**Active when** the invoking chat query begins with `[AUTOPILOT MODE]`, **or** `.azure/vscode-debug-plan.md` contains `executionMode: auto` or equivalent. When active, run fully unattended: -- **Do NOT call `ask_user`.** This is the tail of an autopilot chain over a freshly generated project, so overwriting/merging the scaffolded config files is expected and safe — proceed without prompting (still preserve and merge existing configs rather than blindly overwriting). -- Validation (Phase 3) still runs in full — autopilot never skips validation or the `Implemented` status write. Setting status to `Implemented` is what signals the workflow is complete (the extension restores its auto-approve setting at that point). - ---- - -## Phase 1: Pre-Flight - -Verify the plan and environment before generating any files. - -| # | Action | Reference | -|---|--------|-----------| -| 1 | **Verify plan** — Confirm `.azure/vscode-debug-plan.md` exists with status `Approved`. Set status to `Executing` and update **Last Updated**. | `.azure/vscode-debug-plan.md` | -| 2 | **Load references** — For each service in the plan's Services table (where Generate is checked), load the corresponding project-type and runtime reference files. If no reference file exists, emit a limited-support warning. | [limited-support.md](references/limited-support.md) | -| 3 | **Run pre-flight checks** — Stale data directories and port conflicts. | [preflight.md](references/preflight.md) | - ---- - -## Phase 2: Generate - -The plan drives implementation. Read each section of `.azure/vscode-debug-plan.md` and generate the corresponding artifacts. Use the reference files for implementation details — the plan specifies WHAT to generate, the references specify HOW. - -Follow the generation steps in [generate.md](references/generate.md) in order. - ---- - -## Phase 3: Validate - -> ⚠️ **CRITICAL:** You MUST complete every validation step before proceeding. Do NOT mark the task as complete, do NOT set status to `Implemented`, and do NOT deliver a closing message until validation is finished and the checklist is updated with real results. - -| # | Action | Reference | -|---|--------|-----------| -| 1 | **Validate each launch configuration** — Follow every step in validation.md for each non-compound config. | [validation.md](references/validation.md) | -| 2 | **Validate the compound configuration** — Faithfully run each compound's orchestration per validation.md § Step 9, never inferring the result from the individual configs. | [validation.md](references/validation.md) § Step 9 | -| 3 | **Tear down validation processes** — Stop **every** process and emulator validation spawned (`docker compose down`) and verify all app HTTP, debug, and emulator ports are free again, so a subsequent user F5 starts from a clean slate. | [validation.md](references/validation.md) § Final Teardown | -| 4 | **Update Debug Configuration Checklist** — Create or update the `## Debug Configuration Checklist` section in `.azure/vscode-debug-plan.md` with real ✅ or ❌ results for each configuration. | [validation.md](references/validation.md) § Plan Integration | -| 5 | **Set status** — Only after every checklist stub has been replaced with a real result, set plan status to `Implemented` and update **Last Updated**. | `.azure/vscode-debug-plan.md` | - -> ⛔ **VALIDATION IS NOT OPTIONAL.** Do NOT set status to `Implemented` until every stub in the Debug Configuration Checklist has been replaced with a real ✅ or ❌ result. A checklist with any remaining stubs or missing entries means validation is incomplete — go back and finish it. This is the single most common failure mode. - ---- - -## Outputs - -| Artifact | Location | -|----------|----------| -| **Plan** (updated) | `.azure/vscode-debug-plan.md` | -| Docker Compose | `docker-compose.yml` | -| VS Code Debug Config | `.vscode/launch.json` — see [project-types/](references/project-types/) and [runtimes/](references/runtimes/) | -| VS Code Build Config | `.vscode/tasks.json` — see [project-types/](references/project-types/) and [runtimes/](references/runtimes/) | -| VS Code Extensions | `.vscode/extensions.json` — see [generate.md](references/generate.md) § assembly protocol | -| VS Code Settings | `.vscode/settings.json` — see [generate.md](references/generate.md) § assembly protocol | -| Connection Strings | `local.settings.json` or `.env` | -| Convenience Scripts | Runtime-specific script runner (see [runtimes/](references/runtimes/)) | -| API Test Collections | `api-test-collections/{service-id}//invoke.{sh,ps1}` | - ---- - -## Post-Generation - -After validation completes with status `Implemented`, **stop**. Do not present next steps or a closing message — the `azure-debug-generate` agent handles post-generation guidance and interactive next steps. - -## Troubleshooting - -For Edge or Chrome related startup issues, consult [chromium.md](references/project-types/frontend-spa/debug-adapters/chromium.md). diff --git a/.github/agents/azure-debug-generate/references/api-test-collections.md b/.github/agents/azure-debug-generate/references/api-test-collections.md deleted file mode 100644 index 32c4ba1..0000000 --- a/.github/agents/azure-debug-generate/references/api-test-collections.md +++ /dev/null @@ -1,224 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# API Test Collection Patterns - -> Reference for generating `api-test-collections/{service-id}/` scripts, where `{service-id}` is the canonical service ID derived from the plan's **Service Label** column (see [generate.md](generate.md) § Service ID Derivation). Only generate test collections for services whose **Generate** column is checked in the plan. Scripts should be language-agnostic commands that exercise the running app and test its integration with any live emulators. - ---- - -## HTTP - - **HTTP patterns** use `{baseUrl}` — the project type supplies the base URL (e.g., `http://localhost:7071/api` for Functions). All other patterns target the emulator directly and are reusable across project types. - -### GET request - -```sh -curl -i "{baseUrl}/{FunctionName}" -``` - -### POST with JSON body - -```sh -curl -i -X POST "{baseUrl}/{FunctionName}" \ - -H "Content-Type: application/json" \ - -d @sample-data.json -``` - -> **`{baseUrl}` by project type:** -> -> | Project Type | Base URL | -> |-------------|---------| -> | Azure Functions | `http://localhost:7071/api` | -> | Container App | `http://localhost:{port}` (from Dockerfile `EXPOSE`) | -> | App Service | `http://localhost:{port}` (from framework dev server) | - ---- - -## Storage (Azurite — Blob / Queue / Table) - -> Requires Azurite running. Uses `--connection-string "UseDevelopmentStorage=true"` for all commands. - -### Blob trigger — upload a file - -```sh -az storage blob upload \ - --connection-string "UseDevelopmentStorage=true" \ - --container-name {container-name} \ - --name "sample-file.json" \ - --file sample-file.json \ - --overwrite -``` - -### Queue trigger — send a message - -```sh -az storage message put \ - --connection-string "UseDevelopmentStorage=true" \ - --queue-name {queue-name} \ - --content '{"id": "test-001", "data": "sample"}' -``` - -### Table trigger — insert an entity - -```sh -az storage entity insert \ - --connection-string "UseDevelopmentStorage=true" \ - --table-name {table-name} \ - --entity PartitionKey=pk RowKey=rk001 Value=test -``` - ---- - -## Cosmos DB - -> Requires Cosmos DB Emulator running on `https://localhost:8081`. TLS verification must be disabled for local calls. - -### Insert a document - -```sh -curl -k -X POST "https://localhost:8081/dbs/{database}/colls/{collection}/docs" \ - -H "Authorization: type=master&ver=1.0&sig=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==" \ - -H "Content-Type: application/json" \ - -H "x-ms-documentdb-partitionkey: [\"test\"]" \ - -H "x-ms-version: 2018-12-31" \ - -d '{"id": "test-001", "partitionKey": "test", "data": "sample"}' -``` - -> `-k` disables TLS verification for the emulator's self-signed cert. Never use in production. - ---- - -## Service Bus - -> Requires Service Bus Emulator running. Uses curl against the Service Bus Emulator's HTTP endpoint. - -### Send a message to a queue - -```sh -curl -i -X POST "http://localhost:5672/messages" \ - -H "Content-Type: application/json" \ - -H "BrokerProperties: {\"Label\": \"test\"}" \ - -d '{"id": "test-001", "data": "sample"}' -``` - -> The Service Bus Emulator's HTTP endpoint and port may vary. Check the emulator documentation and docker-compose configuration for the correct URL. For SDK-based testing, use the Azure Service Bus SDK with the emulator connection string from `emulators/` config. - -### Send a message to a topic - -```sh -curl -i -X POST "http://localhost:5672/messages" \ - -H "Content-Type: application/json" \ - -H "BrokerProperties: {\"Label\": \"test\"}" \ - -d '{"id": "test-001", "data": "sample"}' -``` - -> Adjust the URL path for topic-specific endpoints per the emulator's API surface. - ---- - -## Event Hubs - -> Requires Event Hubs Emulator running. - -### Send an event - -```sh -curl -i -X POST "http://localhost:5672/messages" \ - -H "Content-Type: application/json" \ - -d '{"id": "test-001", "data": "sample"}' -``` - -> The Event Hubs Emulator's HTTP endpoint and port may vary. Check the emulator documentation and docker-compose configuration for the correct URL. For SDK-based testing, use the Azure Event Hubs SDK with the emulator connection string from `emulators/` config. - ---- - -## Timer (Azure Functions only) - -Timer triggers cannot be fired by an external event — the Functions host fires them on schedule. Use the Functions admin API to trigger them on demand: - -```sh -curl -i -X POST "http://localhost:7071/admin/functions/{FunctionName}" \ - -H "Content-Type: application/json" \ - -d '{}' -``` - -> This calls the Functions admin endpoint which is only available locally. The `{}` body is required; the timer trigger ignores it. - ---- - -## Generation Rules - -When generating API test collections during Phase 2: - -1. Resolve endpoints from the implemented route registrations, not only `.azure/project-plan.md`. The project plan intentionally omits derived authentication routes, so include implemented registration, login, and current-user endpoints when `API Login` is enabled. -2. Create one top-level subdirectory per service: `api-test-collections/{service-id}/`, where `{service-id}` is derived from the plan's **Service Label** column (see [generate.md](generate.md) § Service ID Derivation). Only generate for services whose **Generate** column is checked in the plan. -3. Within each service directory, generate one subdirectory per trigger/endpoint found during inventory. -4. Name the trigger directory after the trigger: `{trigger-type}-{function-or-endpoint-name}` (e.g., `http-GetOrder`, `blob-ProcessUpload`). -5. Create an `invoke` script (`.sh` on macOS/Linux, `.ps1` on Windows) with the appropriate pattern from this file, substituting discovered values (function name, container name, etc.). -6. Create a `sample-data.json` or `sample-message.json` next to the invoke script when the test requires a body. -7. On macOS/Linux, make the script executable (`chmod +x`). - -> **Do not generate timer test scripts** unless the user explicitly requests it — they're rarely needed for local debugging. - ---- - -## Plan Section Formatting Rules - -When writing the **API Test Collections** section of the plan, the heading format may vary by trigger type. In all cases, the subfolder names under `api-test-collections/{service-id}/` (e.g., `http-register`, `http-createOrder`) should also be referenced in each section's markdown heading when they differ so users can easily see which routes map to each invokable script. - ---- - -### HTTP triggers / web API endpoints - -``` -### {METHOD} {route} [{🔒}] `{folder-name}` -``` - -- **`{METHOD} {route}`** — HTTP verb and full route path (e.g., `GET /api/health`) -- **`🔒`** — Include this emoji when the endpoint requires authentication (any auth scheme: Bearer JWT, API key, etc.). Omit entirely for anonymous/public endpoints. -- **`` `{folder-name}` ``** — The exact folder name under `api-test-collections/{service-id}/` (e.g., `` `http-health` ``) - -**Examples:** - -```markdown -### GET /api/health `http-health` - -### POST /api/auth/register `http-register` - -### GET /api/auth/me 🔒 `http-getMe` - -### POST /api/orders 🔒 `http-createOrder` -``` - -**Auth key** — add this once at the top of the API Test Collections section, just after the folder tree, when any 🔒 routes are present: - -```markdown -> 🔒 = requires authentication (replace `` with a JWT from the login endpoint before running) -``` - ---- - -### Non-HTTP triggers (blob, queue, Service Bus, Event Hubs, etc.) - -``` -### {TriggerType}: {function-or-resource-name} `{folder-name}` -``` - -- **`{TriggerType}`** — Human-readable trigger category: `Blob`, `Queue`, `Service Bus`, `Event Hubs`, `Table`, `Cosmos DB`, etc. -- **`{function-or-resource-name}`** — The function name or the specific resource being targeted (container name, queue name, topic name, etc.) -- **`` `{folder-name}` ``** — The exact folder name under `api-test-collections/{service-id}/` (e.g., `` `blob-ProcessUpload` ``) - -**Examples:** - -```markdown -### Blob: uploads container `blob-processUpload` - -### Queue: order-requests `queue-sendOrder` - -### Service Bus: invoices topic `servicebus-sendInvoice` - -### Event Hubs: telemetry `eventhubs-sendTelemetry` -``` - -> The 🔒 indicator does not apply to non-HTTP triggers — they are invoked by pushing data into the resource directly, not via an authenticated HTTP call. diff --git a/.github/agents/azure-debug-generate/references/emulators/_template.md b/.github/agents/azure-debug-generate/references/emulators/_template.md deleted file mode 100644 index 1de6edd..0000000 --- a/.github/agents/azure-debug-generate/references/emulators/_template.md +++ /dev/null @@ -1,68 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# {Emulator Name} - -> **Template** — Copy this file to `emulators/{name}.md` when adding a new emulator. - ---- - -## Docker Image - - - -``` -{org}/{image}:{tag} -``` - -## docker-compose Service Block - - - -```yaml -services: - {service-name}: - image: {org}/{image}:{tag} - ports: - - "{host-port}:{container-port}" - volumes: - - ./.{service-name}:/data - restart: unless-stopped -``` - -## Connection String - - - -``` -{connection-string} -``` - -## Required App Environment Variables - - - -| Variable | Value | -|----------|-------| -| `{VAR_NAME}` | `{value}` | - -## Healthcheck (Database Emulators Only) - - - - - -## Container Runtime Support - - - - - -| Docker | Podman | -|--------|--------| -| {✅ certified / 🔲 planned} | {✅ certified / 🔲 planned} | - -## Notes - - - diff --git a/.github/agents/azure-debug-generate/references/emulators/azurite.md b/.github/agents/azure-debug-generate/references/emulators/azurite.md deleted file mode 100644 index c8fe5e1..0000000 --- a/.github/agents/azure-debug-generate/references/emulators/azurite.md +++ /dev/null @@ -1,50 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Azurite (Azure Blob / Queue / Table Storage) - -## Docker Image - -``` -mcr.microsoft.com/azure-storage/azurite -``` - -## docker-compose Service Block - -```yaml -services: - azurite: - image: mcr.microsoft.com/azure-storage/azurite - # Overriding the default command requires re-specifying --blobHost/--queueHost/--tableHost 0.0.0.0 - # so Azurite listens on all interfaces (the image's default). Without them, Azurite falls back to - # 127.0.0.1 inside the container and becomes unreachable from the host via port mapping. - # --skipApiVersionCheck allows newer Azure SDK API versions to work with older Azurite releases. - command: azurite --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost 0.0.0.0 --skipApiVersionCheck - ports: - - "10000:10000" - - "10001:10001" - - "10002:10002" - volumes: - - ./.azurite:/data - restart: unless-stopped -``` - -## Connection String - -``` -UseDevelopmentStorage=true -``` - -## Required App Environment Variables - -| Variable | Value | -|----------|-------| -| `AzureWebJobsStorage` (Functions) | `UseDevelopmentStorage=true` | -| `AZURE_STORAGE_CONNECTION_STRING` (SDK) | `UseDevelopmentStorage=true` | - -## Notes - -- Ports: 10000 (Blob), 10001 (Queue), 10002 (Table) -- **Consolidation:** If multiple storage bindings are detected (blob + queue + table), use a **single** Azurite service — not one per binding type. -- The Event Hubs Emulator requires Azurite for checkpointing. If both are needed, the `azurite` service is shared. -- **Container runtime:** Certified for both **Docker** and **Podman** — the service block above is unchanged for either engine. Under Podman on Windows/macOS the ports are published from the Podman machine to the host exactly as with Docker; no `:Z`/`:z` volume label is needed for the `./.azurite` bind mount. diff --git a/.github/agents/azure-debug-generate/references/emulators/postgres.md b/.github/agents/azure-debug-generate/references/emulators/postgres.md deleted file mode 100644 index e27d52d..0000000 --- a/.github/agents/azure-debug-generate/references/emulators/postgres.md +++ /dev/null @@ -1,128 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# PostgreSQL - -> PostgreSQL has no Azure-provided emulator. Use the standard `postgres` Docker image for local development. If the project targets **Azure Cosmos DB for PostgreSQL**, note in the plan that no local emulator is available. - -## Docker Image - -``` -postgres:16 -``` - -## docker-compose Service Block - -Declare the local credentials **once** in a workspace-root `.env`, then reference them everywhere -else. Compose interpolates `${...}` from `.env` (or the shell environment), so the same values feed -the database service and every application service. - -`.env` is required — without it Compose interpolates `${POSTGRES_USER}` to an empty string and the -database silently fails to authenticate: - -``` -POSTGRES_USER=postgres -POSTGRES_PASSWORD=postgres -POSTGRES_DB=localdev -``` - -> ⛔ **`.gitignore` must list `.env` before you create it.** A credential that reaches a commit is -> compromised and has to be rotated — deleting it in a later commit leaves the value in history and -> in every existing clone and fork. Private repositories are no exception. If the project has no -> `.gitignore`, or has one that omits `.env`, fix that before writing the file. - -> **Never inline a concrete `user:password@host` URL** in a generated file — build it from the -> variables above. Beyond hard-coding a credential, such literals are rewritten by secret-redaction -> filters, and a masked value starting with `*` is a fatal YAML parse error: YAML reads a leading -> `*` as an alias reference. - -```yaml -services: - postgres: - image: postgres:16 - ports: - - "5432:5432" - environment: - POSTGRES_USER: ${POSTGRES_USER} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} - POSTGRES_DB: ${POSTGRES_DB} - volumes: - - postgres_data:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] - interval: 5s - timeout: 5s - retries: 5 - start_period: 30s - restart: unless-stopped - -# Declare the named volume at the top level of the compose file (merge into an -# existing top-level `volumes:` key if the file already has one). -volumes: - postgres_data: -``` - -> ⛔ **Use a named volume for the Postgres data dir — not a `./.postgres` bind mount.** On its first -> start Postgres's `initdb` sets the data directory's permissions to `0700`, which requires `chown`/`chmod` -> on the mount. Against a **host bind mount on Windows/macOS** — especially **rootless Podman**, where the -> container user can't change ownership of a Windows-side path — that fails with -> `could not change permissions of directory "/var/lib/postgresql/data": Operation not permitted`, and the -> database never initializes. A **named volume** is managed inside the engine's VM (ext4), so `initdb` -> succeeds on Docker Desktop and Podman alike. This also sidesteps the bind-mount performance and -> permission quirks Postgres hits on Docker Desktop for Windows/macOS. (Azurite and most other emulators -> tolerate a workspace bind mount fine — this named-volume rule is specific to database emulators like -> Postgres that `chown` their data directory.) - -## Connection String - -The host depends on where the client runs: - -| Client location | Host | -|---|---| -| Host machine (VS Code debug target, `npm` task) | `localhost` | -| Another docker-compose service | `postgres` (the service name) | - -``` -postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB} -``` - -## Required App Environment Variables - -Declare these in the workspace-root **`.env`**, alongside the `POSTGRES_*` values: - -| Variable | Value | -|----------|-------| -| `DATABASE_URL` | `postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB}` | -| `POSTGRES_CONNECTION_STRING` | `postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB}` | - -> Use whichever variable name the project's ORM or SDK expects. Both forms above are shown as reference. -> When the value is set on a docker-compose service, replace the `localhost` host with `postgres`. - -> **`.env` must declare every key `.env.example` declares.** `.env.example` is documentation — -> nothing loads it, so a key that appears only there is undefined at run time. - -> **A runtime settings file does not cover host-run tasks.** `local.settings.json` is read by the -> Azure Functions host and a compose `environment:` block by its own container; neither reaches a -> VS Code task that runs a tool directly on the host, such as `npm run db:migrate`. That client -> fails with "Unable to acquire a connection", which reads like an unready database but is a -> missing variable — it never opened a socket. Put the value in `.env` so both paths resolve it. - -## Healthcheck - -The healthcheck is included in the docker-compose service block above. It uses `pg_isready` to verify PostgreSQL is accepting connections. The migration service (see [migrations.md](../migrations.md)) depends on `condition: service_healthy` to wait for readiness before running migrations. - -```yaml -healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] - interval: 5s - timeout: 5s - retries: 5 - start_period: 30s -``` - -## Notes - -- Port 5432 is the standard PostgreSQL port. -- Default credentials (`postgres`/`postgres`) are intentionally simple for local dev and are declared in the workspace-root `.env`. Never use in production. -- Data is persisted to the **named volume `postgres_data`** (managed by the container engine, not a workspace folder). Reset it with `docker compose down -v` / `podman compose down -v`, or `docker volume rm _postgres_data`. Because it is not a workspace directory, it needs no `.vscode/settings.json` `files.exclude` entry and is not part of the workspace stale-**directory** preflight check. -- **Container runtime:** Certified for both **Docker** and **Podman** — the service block, healthcheck, and named `postgres_data` volume are unchanged for either engine. The named volume (not a bind mount) is what lets `initdb` run under rootless Podman on Windows/macOS. The `condition: service_healthy` gate that migrations depend on is honored by both `docker compose` and `podman compose`. diff --git a/.github/agents/azure-debug-generate/references/generate.md b/.github/agents/azure-debug-generate/references/generate.md deleted file mode 100644 index 1454c9d..0000000 --- a/.github/agents/azure-debug-generate/references/generate.md +++ /dev/null @@ -1,292 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Artifact Generation - -Cross-cutting rules, step sequence, and assembly protocols for generating local development configuration files from an approved plan. - ---- - -## Reading the Plan - -The plan's tables drive all generation: - -| Plan Section | What It Drives | -|-------------|----------------| -| **Services** table | Which services get launch.json/tasks.json entries. | -| **Emulators** table | Which emulator compose services to generate. | -| **Orchestrator** table | The container runtime (Docker/Podman) and the **Compose Command** (`docker compose` / `podman compose`) to write into every emulator/migration task. | -| **Migrations** table | Which migration compose services to generate. | -| **API Test Collections** table | Which API test scripts to generate. | -| **Convenience Scripts** table | Which convenience scripts to generate. | - -> **Container runtime is a plan value — never hard-code `docker compose`.** Read the **Compose Command** cell from the plan's Orchestrator table and use it verbatim for every generated Compose invocation (Start Emulators task, migration task, convenience scripts, validation, teardown). It is `docker compose` by default and `podman compose` when the plan selected Podman. The generated `docker-compose.yml` itself is **identical for either engine** — only the command that drives it changes. In the examples throughout these references, `docker compose` stands in for the plan's Compose Command; substitute `podman compose` when the plan says Podman. - -### Targeted Resolution - -The plan provides high-level intent. For implementation details, perform **targeted resolution scans** of the workspace: - -- **Migration details** — Scan for migration directory path, existing migration scripts, connection env var names. See [migrations.md](migrations.md). -- **API endpoints** — Parse function definitions or route handlers to get specific endpoint names, methods, routes, auth levels. -- **Connection string keys** — Check `local.settings.json`, `.env`, or app config for existing key names. -- **Existing config** — Check for existing `.vscode/launch.json`, `.vscode/tasks.json`, `docker-compose.yml` to determine merge vs create. -- **Framework details** — For frontend SPAs, detect the specific framework (Vite, Next.js, Angular, CRA) and dev server port from config files. -- **TypeScript source maps** — For TypeScript services, verify `tsconfig.json` has `"sourceMap": true` in `compilerOptions`. Without it, breakpoints in `.ts` files appear as unverified. See `runtimes/node.md` § Debugger Properties. - ---- - -## Generation Steps - -For each service in the plan's Services table (where Generate is checked), generate the following artifacts in order. The plan specifies WHAT to generate; the reference files linked below specify HOW. - -| # | Action | Reference | -|---|--------|-----------| -| 1 | **Generate docker-compose** — For each emulator in the plan's Emulators table, load the emulator reference and assemble the docker-compose service block. If migrations are checked, add healthcheck and migration service. | [emulators/](emulators/), [migrations.md](migrations.md) | -| 2 | **Generate VS Code debug config** — Assemble `launch.json` and `tasks.json` entries from the project-type and runtime references. For multi-service workspaces, generate compound configuration. | generate.md § Source Ownership, [project-types/](project-types/), [runtimes/](runtimes/), [multi-service.md](multi-service.md) | -| 3 | **Generate VS Code workspace config** — Assemble `.vscode/extensions.json` and `.vscode/settings.json` from project-type and runtime references. Add emulator data directory exclusions. | generate.md § VS Code Extension Recommendations, generate.md § VS Code Workspace Settings | -| 4 | **Configure connection strings** — Update `local.settings.json`, `.env`, or app config with emulator connection strings. Never overwrite existing values. | `project-types/{type}.md` § Connection Strings, `emulators/{name}.md` § Required App Environment Variables | -| 5 | **Generate convenience scripts** — For each checked script in the plan's Convenience Scripts table, add to the project's native script runner. | `runtimes/{rt}.md` § Convenience Scripts | -| 6 | **Generate migrations** — If the plan's Migrations table has checked rows, do targeted resolution for migration details, then generate the docker-compose migration service. | [migrations.md](migrations.md) | -| 7 | **Generate API test collections** — If the plan's API Test Collections table has checked rows, do targeted resolution for endpoints/triggers, then generate test scripts. | [api-test-collections.md](api-test-collections.md) | - -> **Podman emulator certification.** When the plan's container runtime is **Podman**, each emulator's reference file declares its Podman status under `## Container Runtime Support`. Azurite and PostgreSQL are certified. For any emulator not marked Podman-certified, emit a `⚠️ LIMITED SUPPORT: Emulator "{name}" is not yet certified for Podman` warning (per [limited-support.md](limited-support.md)) and generate a best-effort compose service anyway — do **not** silently swap the runtime back to Docker. - ---- - -## Dependency Availability - -> ⚠️ **Do not assume CLI tools or packages are installed in the target project.** - -Before writing any script or task command that invokes a CLI tool (e.g., `rimraf`, `concurrently`, `cross-env`): - -1. **Check** — Verify the tool is already a project dependency. -2. **Add dependency** — Add it as a project dev dependency. This ensures it is version-locked and works consistently across all machines. -3. **Ask if uncertain** — Use `ask_user` if the tool is expensive, opinionated, or has multiple alternatives. - ---- - -## Local Credentials - -> ⚠️ **Never inline a literal credential into a task or a compose file.** Declare local credentials -> once in a workspace-root `.env` and reference them through Compose `${VAR}` interpolation -> everywhere else. - -| Correct | Do **not** generate | -|---|---| -| Compose `${VAR}` interpolation from `.env` | A literal credential inlined into a task or compose file | - -A concrete credential literal is rewritten by secret-redaction filters, and a masked value that -begins with `*` is a fatal YAML parse error — YAML reads a leading `*` as an alias reference, so the -whole compose file stops parsing. - -Interpolation only resolves if the variable is actually defined: Compose reads `${...}` from `.env` -or the shell environment, never from a service's own `environment:` block. Whenever a generated -file references a variable — a compose service, a host task, or a connection string — `.env` must -declare every variable it needs, or the value silently becomes an empty string. - -> ⛔ **`.gitignore` must list `.env` before you create it.** A credential that reaches a commit is -> compromised and has to be rotated — deleting it in a later commit leaves it in history, in every -> clone, and in every fork. Private repositories are no exception. If the project has no -> `.gitignore`, or has one that omits `.env`, fix that first. - -See `emulators/{name}.md` § Required App Environment Variables for the per-emulator variable set. - ---- - -## VS Code Debug & Task Configuration - -Assemble `.vscode/launch.json` and `.vscode/tasks.json` by combining properties from the detected **project type** and **runtime** references. Use the source ownership table below to determine which file provides each property. - -### Source Ownership - -| Concern | Server-side project source | Browser SPA source | -|---------|---------------------------|-------------------| -| Debugger type (`node`, `coreclr`) | `runtimes/{rt}.md` § Debugger Properties | `project-types/frontend-spa/debug-adapters/{adapter}.md` | -| Debug port | `runtimes/{rt}.md` § Debugger Properties | N/A (uses dev server URL) | -| Request mode (`attach` / `launch`) | `project-types/{type}.md` § Runtime Wiring | `project-types/frontend-spa/frontend-spa.md` § Runtime Wiring | -| Top-level startup task (type, command, problem matcher) | `project-types/{type}.md` § VS Code Task Configuration | `project-types/frontend-spa/frontend-spa.md` § VS Code Task Configuration | -| Build chain tasks (install, clean, watch) | `runtimes/{rt}.md` § Build Chain | N/A (dev server handles compilation) | -| Runtime-specific launch properties (`outFiles`, `processName`) | `runtimes/{rt}.md` § Debugger Properties | N/A | -| Compound configuration | [multi-service.md](multi-service.md) § Compound Debug Configuration | [multi-service.md](multi-service.md) § Compound Debug Configuration | -| Working directory (`cwd`) rules | generate.md § Working Directory (`cwd`) Rules | generate.md § Working Directory (`cwd`) Rules | -| Task `runOptions` rules | generate.md § Task `runOptions` Rules | generate.md § Task `runOptions` Rules | -| Emulator startup task (`Start Emulators`) | `runtimes/{rt}.md` § Build Chain | N/A (backend service owns emulators) | - -> `project-types/{type}.md` § VS Code Task Configuration provides **concrete task JSON per runtime** — use those blocks directly. `runtimes/{rt}.md` § Build Chain provides the dependency tasks that the startup task's `dependsOn` references. - -### Service ID Derivation - -Derive a canonical service ID from the plan's **Service Label** column: lowercase, kebab-case (e.g., "Functions API" → `functions-api`, "Web App" → `web-app`). This ID is used for: - -- Task labels (e.g., `functions-api: func host start`) -- Launch config naming (use the plan's **Launch Config Name** column directly) -- Compound config member references - -If two services resolve to the same ID, append the project type: `payments-api-functions`. - -> ⛔ Every generated task label should conform to `{service-id}: {task name}` (e.g., `functions-api: func host start`, `functions-api: dotnet build`). Wherever a task label is referenced — generation blocks, `dependsOn` chains, `preLaunchTask` values, validation Ready-Signal tables, and validation checklists — it **MUST** use this `{service-id}:` form. Instruction files and any examples that show a label without the `{service-id}:` prefix added are illustrating the latter part of the label; resolve it to the full form before writing or matching. - -### Task Chain Shape (Server-side only) - -> Browser-based projects (e.g., Frontend SPA) skip the build chain — the dev server task is the only task. See `project-types/{type}.md` § VS Code Task Configuration. - -``` -"{service-id}: {top-level-task}" ← project-type-specific (see project-types/{type}.md) - ├── dependsOn: "{service-id}: {watch-task}" ← from runtimes/{rt}.md - │ └── dependsOn: "{service-id}: {clean-task}" - │ └── dependsOn: "{service-id}: {install-task}" - └── dependsOn: "Start Emulators" ← only when emulators are required -``` - -> Adjust task labels and commands for alternative package managers (`yarn`, `pnpm`, `gradle`). The key invariant is the chain shape: **install → clean → build/watch → top-level task** (with `Start Emulators` as a sibling dependency of the top-level task, NOT nested under install). Some runtimes skip steps — use only what applies. - -### Project Type Path Resolution - -Most project types have a single reference file at `project-types/{type}.md`. Some use a subdirectory: - -| Project Type | Reference Path | -|--------------|---------------| -| `functions` | `project-types/functions.md` | -| `frontend-spa` | `project-types/frontend-spa/frontend-spa.md` | - -When instructions reference `project-types/{type}.md`, resolve via this table. If the type is not listed, look for `project-types/{type}.md` first, then `project-types/{type}/{type}.md`. - -### Working Directory (`cwd`) Rules - -> ⚠️ **CRITICAL for multi-service repos.** Without correct `cwd`, commands like `npm install` or `func host start` will run from the workspace root and fail. - -Use the **Service Root** column from the plan's Services table to determine the `cwd` for each task. - -| Task Scope | `cwd` Setting | Example | -|------------|--------------|---------| -| **Per-service tasks** (install, clean, watch, build, top-level) | `"options": { "cwd": "${workspaceFolder}/{service-root}" }` | `"cwd": "${workspaceFolder}/api"` | -| **Shared tasks** (Start Emulators) | Workspace root (omit `cwd` — it defaults to workspace root) | — | -| **Single-service repos** | Omit `cwd` — workspace root is the service root | — | - -### Task `runOptions` Rules - -**Every** task in `tasks.json` must include `runOptions` with `instanceLimit: 1` and `instancePolicy: "silent"` — install, clean, watch, build, top-level, emulator, **and the sequenced compound task** from [multi-service.md](multi-service.md) § Compound Debug Configuration. There is no task type this is optional for; if you are writing a `"label"`, you are writing `runOptions` beside it. - -This prevents duplicate task instances and silently skips re-invocations when a task is already running (e.g., from compound + individual `preLaunchTask` chains). The compound task is the one that needs it most, not least: it is reachable both directly and as a `preLaunchTask`, so it is the likeliest to be invoked twice. - -### Start Emulators Task - -When the plan includes emulators, generate a shared `Start Emulators` task. This task is a **sibling dependency** of each service's top-level startup task (e.g., `func host start`). Do NOT place `Start Emulators` as a dependency of build chain tasks like `npm install` or `npm watch` — it belongs in the startup task's `dependsOn` array alongside the build chain prerequisite. - -Use the plan's **Compose Command** for the task `command` — `docker compose up -d` by default, or `podman compose up -d` when the plan's Orchestrator selected Podman. - -```json -{ - "type": "shell", - "label": "Start Emulators", - "command": "docker compose up -d", - "problemMatcher": [], - "runOptions": { "instanceLimit": 1, "instancePolicy": "silent" } -} -``` - -### instanceLimit and instancePolicy - -Set **`instanceLimit: 1`** and **`instancePolicy: "silent"`** on every task. You never want parallel instances of the same build or startup task, and `"silent"` ensures that duplicate invocations are skipped without prompting the user. - -### Problem Matchers - -**Background tasks (`isBackground: true`) MUST have a real `problemMatcher`.** An empty matcher (`"problemMatcher": []`) on a background task causes a blocking VS Code dialog ("This task is tracked by a problem matcher"). Look up the correct matcher from the relevant reference file: - -- **Runtime tasks** (build, watch) → `runtimes/{rt}.md` § VS Code Problem Matchers -- **Project-type tasks** (top-level startup) → `project-types/{type}.md` § VS Code Task Configuration -- **Frontend dev servers** → `project-types/frontend-spa/frontend-spa.md` § Framework Lookup Table - -For non-background tasks: - -| Task Type | Matcher | -|-----------|---------| -| Short-lived commands (`npm install`, `npm clean`, `docker compose up -d`) | `"problemMatcher": []` | -| Dependency-only tasks (only `dependsOn`, no `command`) | Omit `problemMatcher` entirely | - -Short-lived commands use an empty matcher to suppress matching on tasks that don't produce compiler-style output. - -### Example - -```json -{ - "type": "shell", - "label": "npm install", - "command": "npm install", - "runOptions": { "instanceLimit": 1, "instancePolicy": "silent" }, - "problemMatcher": [] -} -``` - ---- - -## VS Code Extension Recommendations (`.vscode/extensions.json`) - -Aggregate extension recommendations from the detected **runtime** and **project type** into `.vscode/extensions.json`. Each source lists its required extensions in a `## VS Code Extension Recommendations` section with an `Extension ID | Why Required` table. - -### Assembly Protocol - -| Step | Action | Details | -|------|--------|---------| -| 1. Collect | Gather from runtime | Read extension IDs from `runtimes/{rt}.md § VS Code Extension Recommendations` | -| 2. Collect | Gather from project type | Read extension IDs from `project-types/{type}.md § VS Code Extension Recommendations` | -| 3. Deduplicate | Remove duplicates | Each extension ID appears once in the final list | -| 4. Write | Output `.vscode/extensions.json` | Contribute to the file — do not replace existing entries | - -### Output Format - -```json -{ - "recommendations": [ - "{runtime-extension-1}", - "{project-type-extension-1}" - ] -} -``` - ---- - -## VS Code Workspace Settings (`.vscode/settings.json`) - -Aggregate workspace settings from the detected **runtime**, **project type**, and **emulator configuration** into `.vscode/settings.json`. Each source lists its contributed settings in a `## VS Code Workspace Settings` section with a `Setting | Value | Why` table. - -### Assembly Protocol - -| Step | Action | Details | -|------|--------|---------| -| 1. Collect | Gather from runtime | Read settings from `runtimes/{rt}.md § VS Code Workspace Settings` | -| 2. Collect | Gather from project type | Read settings from `project-types/{type}.md § VS Code Workspace Settings` | -| 3. Collect | Gather emulator exclusions | Derive data directory exclusions from `docker-compose.yml` `volumes:` mounts | -| 4. Collect | Gather required debug settings | Add the mandatory keys from generate.md § Required Debug Settings | -| 5. Write | Output `.vscode/settings.json` | Merge into the file — contribute without replacing existing entries or user customizations | - -### Required Debug Settings - -Always merge these keys into the workspace `.vscode/settings.json` on **every** run which is workspace scope only. Preserve any existing entries; if a key is already present, leave its value untouched. - -| Setting | Value | Why | -|---------|-------|-----| -| `debug.onTaskErrors` | `"debugAnyway"` | Suppresses VS Code's blocking "task errors" modal on F5, which can appear as a project launching blocker when it's often not. | - -```json -{ - "debug.onTaskErrors": "debugAnyway" -} -``` - -### Emulator Data Directory Exclusions - -When emulators are configured with **workspace bind mounts** (e.g. Azurite's `./.azurite:/data`), add their data directories to both `files.exclude` and `search.exclude` in **`.vscode/settings.json`** to reduce workspace noise: - -```json -{ - "files.exclude": { - "**/.azurite": true - }, - "search.exclude": { - "**/.azurite": true - } -} -``` - -> Derive directory names from the actual **bind-mount** `volumes:` entries in `docker-compose.yml` (a `./.name:/path` host mount) — do not hardcode. **Named volumes** (e.g. Postgres's `postgres_data`) live inside the container engine, not the workspace, so they need no exclusion. Each emulator's data pattern is defined in `emulators/{name}.md`. diff --git a/.github/agents/azure-debug-generate/references/limited-support.md b/.github/agents/azure-debug-generate/references/limited-support.md deleted file mode 100644 index ff1504a..0000000 --- a/.github/agents/azure-debug-generate/references/limited-support.md +++ /dev/null @@ -1,74 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Limited Support Warnings - -> Emit a standardized warning when the skill detects a feature that is not yet fully supported. The warning format is consistent across all feature categories so the user always knows what to expect. - ---- - -## ⛔ Detection Algorithm — MANDATORY - -For every project type, runtime, and emulator declared in the plan's tables, follow this procedure **exactly**: - -1. **Check for a matching reference file** — List the files and subdirectories in the category folder (ignoring `_template.md`). If any filename or subdirectory name loosely matches the declared value (ignoring case, spaces, dashes, and underscores), the feature has a reference file. -2. **Check the status inside the reference file** — Open the matched reference file and look for a status indicator. If the file is marked `🔲 Planned` (in a frontmatter note, status field, or top-level callout), the feature has **limited support** despite having a reference file. You **MUST** emit a warning. -3. **If a match exists AND the file is not marked Planned** → the feature is fully supported. Proceed normally. -4. **If no match exists** → the feature has limited support. You **MUST** emit a warning. Do NOT substitute a different, supported feature. - -| Category | Category Folder | Path Notes | -|----------|-----------------|------------| -| Project type | `references/project-types/` | Some types use subdirectories (e.g., `frontend-spa/frontend-spa.md`). Match against both filenames and subdirectory names. | -| Runtime | `references/runtimes/` | | -| Emulator | `references/emulators/` | | - -> ⚠️ **Do NOT skip this check.** Every declared feature MUST be verified against the category folder before generating artifacts. - ---- - -## Warning Format - -``` -⚠️ LIMITED SUPPORT: {Category} "{value}" is not yet fully supported. -``` - -Where: -- `{Category}` — a short label for the feature area (e.g., `Project type`, `Runtime`, `Emulator`) -- `{value}` — the specific feature declared in the plan (e.g., `python`, `Container App`, `Cosmos DB`) - ---- - -## ⛔ Emission Protocol — MANDATORY - -When a limited-support feature is detected, you **MUST** follow this exact sequence: - -### Step 1: Emit in assistant message - -Write the canonical warning in your **regular assistant message text**. This is mandatory — the warning must be visible in the chat output, not hidden inside a tool call. - -``` -⚠️ LIMITED SUPPORT: Emulator "Durable Task Scheduler" is not yet fully supported. -``` - -### Step 2: Confirm with user - -For the **first** limited-support feature encountered in the session, call `ask_user` to confirm whether to proceed: - -``` -ask_user( - question: "⚠️ LIMITED SUPPORT: {Category} \"{value}\" is not yet fully supported. Would you still like me to put forth a best-effort attempt?", - choices: [ - "Yes, proceed with best effort", - "No, stop here" - ] -) -``` - -If the user agrees, treat that as blanket consent for the rest of the session. Any additional limited-support features discovered later should still be emitted as a warning — but do **not** call `ask_user` again. - -> Emit each `⚠️ LIMITED SUPPORT:` warning exactly once per `(Category, value)` pair in assistant messages. ---- - -## No Silent Substitution - -**Do NOT silently substitute a supported alternative** when a feature has limited support (e.g. switching a Container App to Azure Functions, or Python to Node.js). Always generate for the project type, runtime, and emulators declared in the plan, even when support is limited. diff --git a/.github/agents/azure-debug-generate/references/migrations.md b/.github/agents/azure-debug-generate/references/migrations.md deleted file mode 100644 index 142717f..0000000 --- a/.github/agents/azure-debug-generate/references/migrations.md +++ /dev/null @@ -1,87 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Database Migrations — Generation - -Generate compose migration services from the plan's Migrations table. The plan records WHAT migration tool is in use and which service needs it. This reference covers HOW to generate the compose configuration. - -> The migration service is ordinary compose content and is **identical for Docker or Podman**. Any task or command that drives it (`docker compose up db-migrate`) uses the plan's Orchestrator **Compose Command** — substitute `podman compose` when the plan selected Podman. - ---- - -## Targeted Resolution - -The plan's Migrations table provides: `Generate | Service | Migration Tool`. Before generating the docker-compose migration service, perform targeted resolution to fill in the details: - -| Detail | How to Resolve | -|--------|---------------| -| **Migration directory** | Scan for tool-specific directories: `prisma/migrations/`, `migrations/`, `Migrations/`, `alembic/` | -| **Migration command** | Check the project's script runner (e.g., `package.json` scripts) for an existing migration command. If found, use it. If not, construct from the tool name. | -| **Target database service** | Match against the plan's Emulators table — the database emulator's compose service name | -| **Connection env var** | Check `local.settings.json`, `.env`, or the migration tool's config file for the variable name | -| **Compose-network connection string** | Same shape as the local connection string but with the compose service name as host instead of `localhost` | -| **Existing script** | Check whether a migration script already exists in the project's script runner | - -### Migration Script Lookup - -| Detection Evidence | Instruction | -|--------------------|-------------| -| Existing migration script in project (e.g., `npm run db:migrate`) | Use it as-is in the docker-compose service | -| Migration tool detected but no script | Create a script in the project's native script runner that wraps the tool's CLI command (e.g., `"db:migrate": "npx prisma migrate deploy"` in `package.json`) | -| Raw SQL files only, no migration tool | Recommend and install a lightweight migration tool as a dev dependency (e.g., `node-pg-migrate` for Node.js). Ask the user before installing. | - ---- - -## Docker Compose Patterns - -Two patterns are needed: a **healthcheck** on the database service and a one-shot **migration service**. - -### Healthcheck Pattern - -When migrations are present, the target database service **must** have a healthcheck so the migration service can use `depends_on` with `condition: service_healthy`. The healthcheck definition belongs in the emulator's docker-compose config — see the emulator reference files in [emulators/](emulators/). - -### Migration Service Pattern - -```yaml -services: - db-migrate: - image: ${RUNTIME_IMAGE} - working_dir: /app - depends_on: - ${DATABASE_SERVICE}: - condition: service_healthy - volumes: - - ./:/app:ro - ${EXTRA_VOLUME_MOUNTS} - environment: - ${CONNECTION_ENV_VAR}: ${CONNECTION_STRING_FOR_COMPOSE_NETWORK} - entrypoint: ${MIGRATION_SCRIPT} - restart: "no" -``` - -**Filling in the template — use resolved details:** - -| Placeholder | How to determine | -|-------------|-----------------| -| `RUNTIME_IMAGE` | A Docker image that provides the language runtime. See the table below. | -| `DATABASE_SERVICE` | The compose service name for the target database (from Emulators table) | -| `CONNECTION_ENV_VAR` | The environment variable the migration tool expects (from targeted resolution) | -| `CONNECTION_STRING_FOR_COMPOSE_NETWORK` | Same shape as local connection string but with compose service name as host | -| `EXTRA_VOLUME_MOUNTS` | Additional mounts needed for the ecosystem. See the table below. | -| `MIGRATION_SCRIPT` | The project's migration script command (from targeted resolution) | - -**Runtime images and extra volume mounts:** - -| Ecosystem | Image | Extra Volume Mounts | Notes | -|-----------|-------|-------------------|-------| -| Node.js / TypeScript | `node:{major}-slim` | `./node_modules:/app/node_modules:ro` | Mount node_modules separately for native modules | -| .NET | `mcr.microsoft.com/dotnet/sdk:{version}` | — | Best-effort — emit limited support warning | -| Python | `python:{version}-slim` | `./.venv:/app/.venv:ro` (if applicable) | Best-effort — emit limited support warning | -| Java | `eclipse-temurin:{version}` | — | Best-effort — emit limited support warning | -| Go | `golang:{version}` | — | Best-effort — emit limited support warning | - -> **Key properties:** -> - `depends_on` with `condition: service_healthy` — waits for the database to accept connections -> - `volumes` with `:ro` — mounts project files read-only for safety -> - `restart: "no"` — runs once per `docker compose up`, does not restart after exit -> - Mount ecosystem-specific dependency directories when the migration tool is installed as a project dependency diff --git a/.github/agents/azure-debug-generate/references/multi-service.md b/.github/agents/azure-debug-generate/references/multi-service.md deleted file mode 100644 index 4ea97d8..0000000 --- a/.github/agents/azure-debug-generate/references/multi-service.md +++ /dev/null @@ -1,119 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Multi-Service Orchestration — Generation - -> Applies when the plan's Services table has **2+ rows** (excluding the compound config row). Governs compound debug configuration, working directory rules, startup ordering, and partial configuration handling. - ---- - -## Port Assignment - -When two or more services share the same runtime, each needs a unique debug port. Look up the `Base debug port` from `runtimes/{rt}.md` and assign ports sequentially: first service gets the base port, second gets base + 1, third gets base + 2, etc. - -> Browser-based project types (e.g., Frontend SPA) do not use debug ports — they connect via the dev server URL instead. - ---- - -## Partial Configuration Handling - -Check each service root for existing VS Code debug config before generating anything. A service is considered already configured if it has an existing debug configuration entry in `.vscode/launch.json` matching its service ID or Launch Config Name. - -| State | Action | -|-------|--------| -| **Fully configured service** | Skip all artifact generation for that service; carry its existing config into the compound configuration unchanged | -| **Partially configured service** | Generate only what is missing (e.g. tasks but no debug config → generate debug config only) | -| **Unconfigured service** | Generate all artifacts as normal | - -Adding a second service to an existing single-service repo is safe — the original service's config is preserved and the new service is added alongside it. - ---- - -## Compound Debug Configuration - -> ⛔ **MANDATORY:** When 2+ service roots are detected (including Frontend SPA projects), a compound debug configuration **must** be generated. A frontend SPA counts as a service root — it does not need emulators, but it does need a debug config entry and inclusion in the compound. - -> ⚠️ **Working directory:** Multi-service task chains require correct `cwd` on every per-service task. See [generate.md § Working Directory (`cwd`) Rules](generate.md) — without it, commands like `npm install` or `func host start` run from the workspace root and fail. - -Use the plan's Services table to assemble the compound config. The compound config row in the plan specifies the Launch Config Name (e.g., "Debug All Services"). - -### Startup Ordering - -VS Code compound launch configurations always start their listed configurations **in parallel**. There is no `dependsOrder` property for compounds — only individual tasks support sequenced dependencies. This means you cannot directly tell a compound to "start the backend before the frontend." - -When a frontend service proxies to a local backend (e.g., a dev server proxy for API calls), the backend must be ready before the frontend starts. Otherwise the frontend proxy may produce `ECONNREFUSED` errors on startup. Since compounds cannot enforce this ordering, the workaround is to push the sequencing into a **compound task** that uses `dependsOrder: "sequence"`, and set that task as the compound's `preLaunchTask`. - -> The plan may include a note like "ℹ️ **Proxy detected:**" indicating this dependency. Check the frontend's config files (e.g., `vite.config.ts` `server.proxy`) to confirm. - -#### Pattern - -**1. Generate a sequenced compound task** that starts services in order. Give it any descriptive label (referred to below as `{sequenced-compound-task}`): - -```json -{ - "label": "{sequenced-compound-task}", - "dependsOn": [ - "{backend-service-id}: {backend-top-level-task}", - "{frontend-service-id}: {frontend-top-level-task}" - ], - "dependsOrder": "sequence", - "runOptions": { "instanceLimit": 1, "instancePolicy": "silent" } -} -``` - -The backend is listed first. Its `problemMatcher` (from `project-types/{type}.md`) signals "ready" before the frontend task starts. - -**2. Set the compound config's `preLaunchTask`** to the sequenced task: - -```json -{ - "name": "{Launch Config Name from plan's compound row}", - "configurations": ["{Backend Launch Config Name}", "{Frontend Launch Config Name}"], - "preLaunchTask": "{sequenced-compound-task}", - "stopAll": true -} -``` - -**3. Individual configs keep their own `preLaunchTask`** so they work standalone: - -```json -{ - "name": "{Backend Launch Config Name}", - "preLaunchTask": "{backend-service-id}: {backend-top-level-task}" -} -``` - -```json -{ - "name": "{Frontend Launch Config Name}", - "preLaunchTask": "{frontend-service-id}: {frontend-top-level-task}" -} -``` - -**4. Set `instanceLimit: 1` and `instancePolicy: "silent"` on background tasks** to prevent duplicate instances. - -When the compound runs, the sequenced compound task starts both services via `dependsOrder: "sequence"`. Then each individual configuration's `preLaunchTask` fires again — but those services are already running. With `instanceLimit: 1` and `instancePolicy: "silent"`, the duplicate invocation is silently skipped and the existing instance keeps running. - -#### Why This Pattern Is Necessary - -| Concern | How it's solved | -|---------|----------------| -| Compounds can't sequence configurations | The sequenced compound **task** (`dependsOrder: "sequence"`) handles ordering instead | -| Backend must be ready before frontend starts | Backend is listed first in `dependsOn`; its problem matcher signals readiness | -| Debuggers must not attach before services are running | The compound's `preLaunchTask` ensures all services are started before any debugger attaches | -| Individual configs must still work standalone | Each config has its own `preLaunchTask` pointing to its service's top-level task | -| Duplicate task invocations from compound + individual preLaunchTasks | `instanceLimit: 1` + `instancePolicy: "silent"` silently skips the duplicate — the first instance keeps running | - ---- - -### Deduplicated Startup Graph - -Any generated compound configuration MUST satisfy **all** of the following. Each rule is checkable against `tasks.json` / `launch.json`: - -| # | Rule | How to check | -|---|------|--------------| -| 1 | **Each service's top-level task appears exactly once** across the compound's effective task graph (the transitive `dependsOn` closure of the sequenced compound task). No service's start task is reachable via two different paths. | Expand the `dependsOn` closure of the sequenced compound task; every service's top-level task label occurs exactly once. | -| 2 | **The sequenced compound task is the single owner of startup ordering.** No per-service task re-declares a `dependsOn` on another service's start task; ordering lives only in the sequenced compound task. | No service start task lists another service's start task in its own `dependsOn`. | -| 3 | **Every background task sets `instanceLimit: 1` and `instancePolicy: "silent"`.** | Every long-running task in the chain carries both properties. | - -Because the individual configs keep their own `preLaunchTask` (so they work standalone), the compound WILL invoke each service's start task a second time. Rule 3 makes that second invocation a silent no-op instead of a duplicate process, while Rules 1 and 2 ensure the *first* pass never double-starts a service either. diff --git a/.github/agents/azure-debug-generate/references/preflight.md b/.github/agents/azure-debug-generate/references/preflight.md deleted file mode 100644 index 84b4ca7..0000000 --- a/.github/agents/azure-debug-generate/references/preflight.md +++ /dev/null @@ -1,88 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Pre-Flight Checks - -Verify the plan exists and environment is ready before proceeding onwards to generating files. - -## Container Runtime Readiness Check - -Read the container runtime from the plan's Orchestrator table (**Docker**, **Podman**, or **Podman (Docker-compatible)**) and confirm the engine is actually running before you generate or validate anything. A CLI on PATH is not the same as a running engine. - -- **Docker** — confirm the daemon is reachable (`docker info`). If it errors, tell the user to start Docker Desktop / the Docker service and wait, then retry. -- **Podman (Docker-compatible)** — the plan runs the Podman engine through its Docker-compatible socket, and the generated tasks use `docker compose`. Confirm the socket answers with `docker info` (it will report the Podman server). If `docker info` errors, the Docker-compatible socket isn't up: tell the user to enable Docker compatibility in Podman Desktop and ensure the **Podman machine** is started (same machine check as below), then retry. -- **Podman** — confirm the engine is reachable (`podman info`). On **Windows and macOS** this needs a running **Podman machine**: - - ```bash - podman machine list --format '{{.Name}} {{.Running}}' - ``` - - - If a machine exists but is **stopped**, ask the user before starting it (starting a VM is a stateful, resource-consuming action): - - ``` - ask_user( - question: "Your Podman machine \"podman-machine-default\" is stopped. Start it so the emulators can run?", - choices: [ - "Yes, start the Podman machine", - "No, I'll start it myself" - ] - ) - ``` - - On approval, run `podman machine start` and wait for it to report ready. - - If **no machine exists**, do NOT silently `podman machine init` — tell the user to run `podman machine init && podman machine start` (a one-time, multi-minute setup) and re-run once it is ready. - -> ⛔ **NEVER switch the plan's container runtime on your own — not at readiness, not during generation, not during validation.** The runtime in the Orchestrator table was chosen (and, when the user asked for a specific engine, chosen *by them*). If the selected engine fails at **any** point — the engine won't start, a container won't come up, ports don't forward to the host, a volume/permission error, a health check never passes — **STOP and surface the blocker to the user with `ask_user`**, describing the failure and the options (fix the engine, or explicitly switch to the other engine). Do **not** silently rewrite the Orchestrator row, the `Start Emulators` task, or the convenience scripts to a different engine and continue. Silently falling back — e.g. from a user-requested Podman to Docker because Podman had a networking or bind-mount issue — is a **failure of this agent**, even if the resulting app works: the user asked for Podman and must decide, not discover after the fact that Docker ran everything. -> -> When you do surface a runtime blocker, prefer offering the **fix** first (many Podman-on-Windows issues are config, not dead ends — e.g. use the WSL machine provider for host port forwarding; database emulators must use a named volume, not a workspace bind mount, so `initdb` can `chown` its data dir). Only switch engines if the user explicitly chooses to. - -## Stale Data Directory Check - -Before generating any files, check for leftover emulator state from a previous run — both **workspace data directories** (bind-mounted emulators such as Azurite's `.azurite/`, `.cosmos/`, `.servicebus/`) and **named volumes** (database emulators such as Postgres's `postgres_data`; see [emulators/postgres.md](emulators/postgres.md)). Stale state can cause container startup failures — for example, PostgreSQL's `initdb` will refuse to initialize if the data directory (the `postgres_data` volume) already contains files from an incompatible or partially-initialized cluster. - -- **Bind-mounted data directories:** list them with `ls`/`Get-ChildItem` in the workspace. -- **Named volumes:** list them with `docker compose ls` / `docker volume ls` (or the `podman` equivalents) — a project-scoped `*_postgres_data` volume from a prior run is the database equivalent of a stale directory. - -If any stale state is found: - -1. **List all found directories and named volumes** with their sizes. -2. **Ask the user how to proceed** using `ask_user`: - -``` -ask_user( - question: "Leftover emulator state was found from a previous run:\n\n- .azurite/ (12 MB, workspace directory)\n- postgres_data (45 MB, named volume)\n\nThese can cause container startup failures. How would you like to handle this?", - choices: [ - "Delete them and start fresh (recommended)", - "Keep them — I want to preserve the existing data" - ] -) -``` - -3. **If the user chooses to delete** — Remove bind-mounted directories with platform-appropriate removal (`rm -rf` on macOS/Linux, `Remove-Item -Recurse -Force` on Windows), and remove named volumes with `docker compose down -v` / `podman compose down -v` (or `docker volume rm _postgres_data`), before proceeding with generation. -4. **If the user wants to keep them** — Proceed, but warn that containers may fail to start. If they do fail, offer to clean up at that point. -5. **Never delete data directories or volumes silently** — Always confirm with the user first. - ---- - -## Port Conflict Check - -Before generating any files, scan all ports required by the planned emulators (e.g. `lsof -i -P -n`). For each occupied port, identify the process name and PID. - -If any conflicts are found: - -1. **List all conflicts clearly** — port number, process name, PID. -2. **Ask the user how to proceed** using `ask_user`: - -``` -ask_user( - question: "The following ports are already in use on your machine:\n\n- Port 5432 → postgres (PID 1234)\n\nThese ports are needed by the planned emulators. How would you like to handle this?", - choices: [ - "Help me remap the conflicting ports to alternatives", - "I'll handle it myself — proceed with the plan as-is" - ] -) -``` - -3. **If the user wants help remapping** — Propose alternative port numbers, update all references in the plan and project files (docker-compose service ports, connection strings, convenience scripts, VS Code debug config), then resume generation. -4. **If the user will handle it themselves** — Proceed with generation using the original ports. -5. **Never remap ports or modify config silently** — Always confirm with the user before making changes. diff --git a/.github/agents/azure-debug-generate/references/project-types/_template.md b/.github/agents/azure-debug-generate/references/project-types/_template.md deleted file mode 100644 index 9a30a70..0000000 --- a/.github/agents/azure-debug-generate/references/project-types/_template.md +++ /dev/null @@ -1,169 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# {Type} — Project Type - -> **Template** — Copy this file to `project-types/{type}.md` when adding a new server-side project type. For browser-based project types, use `project-types/frontend-spa/` as a reference — the structure differs (Framework Lookup Table replaces Startup Command, debug adapters replace Debugger Properties). - ---- - -## Detection Signals - - - -| Signal | Notes | -|--------|-------| -| `{file}` | {description} | - ---- - -## Prerequisites - - - -| Tool | Detection Command | Required For | Install Link | -|------|-------------------|-------------|-------------| -| `{tool}` | `{command}` | {purpose} | `{install-url}` | - ---- - -## Runtime Support Matrix - - - -| Runtime | Status | Reference | -|---------|--------|-----------| -| node-ts | | | -| node-js | | | -| dotnet | | | -| python | | | -| java | | | -| go | | | - ---- - -## Dependency Discovery - - - - -| Dependency Signal | Azure Service | Emulator | -|------------------|---------------|---------| -| `{signal}` | {service} | [{name}](../emulators/{name}.md) | - ---- - -## Startup Command - - - -``` -{command} -``` - ---- - -## Runtime Wiring - - - -> See **§ VS Code Task Configuration** below for the concrete task JSON for each runtime. - -| Runtime | Startup task label | Task type | Problem matcher | Request Mode | Status | Reference | -|---------|--------------------|-----------|-----------------|--------------|--------|-----------| -| node-ts | {label} | {shell\|func\|...} | {matcher} | {attach\|launch} | | | -| node-js | {label} | {shell\|func\|...} | {matcher} | {attach\|launch} | | | -| dotnet | {label} | {shell\|func\|...} | {matcher} | {attach\|launch} | | | -| python | {label} | {shell\|func\|...} | {matcher} | {attach\|launch} | | | -| java | {label} | {shell\|func\|...} | {matcher} | {attach\|launch} | | | -| go | {label} | {shell\|func\|...} | {matcher} | {attach\|launch} | | | - -### VS Code Debug Configuration - - - -### VS Code Task Configuration - - - -### Connection Strings - - - -| Emulator | Key | Value | File | -|----------|-----|-------|------| -| {emulator} | `{VAR_NAME}` | `{value}` | `{file}` | - ---- - -## API Test Collections - - - -See [api-test-collections.md](../api-test-collections.md) for all test script patterns. - ---- - -## VS Code Extension Recommendations (`.vscode/extensions.json`) - - - -| Extension ID | Why Required | -|--------------|-------------| -| `{extension-id}` | {reason} | - ---- - -## VS Code Workspace Settings (`.vscode/settings.json`) - - - -| Setting | Value | Why | -|---------|-------|-----| -| `{setting.key}` | `{value}` | {reason} | - ---- - -## Validation Signals - - - -### Ready Signal - -| Top-Level Task | Ready Signal (stdout) | -|----------------|----------------------| -| {task label} | `"{pattern}"` | - -### HTTP Verification - -| Curl Target | Expected Status | Notes | -|-------------|-----------------|-------| -| `http://localhost:{port}/{path}` | `{status}` | {notes} | - ---- - -## Checklist — {Type} Project Validation - - - -After generating `launch.json`, `tasks.json`, and `extensions.json`, verify the following were produced correctly: - -1. ✅ Startup task exists in `tasks.json` with the correct type and problem matcher -2. ✅ `launch.json` `preLaunchTask` points to the startup task -3. ✅ `.vscode/extensions.json` includes project-type extensions listed above -4. ✅ `dependsOn` chain includes runtime build/watch task and `Start Emulators` (when emulators are required) - -> Runtime-specific checks (e.g., build task, debugger type) are defined in `runtimes/{rt}.md`. diff --git a/.github/agents/azure-debug-generate/references/project-types/frontend-spa/debug-adapters/_template.md b/.github/agents/azure-debug-generate/references/project-types/frontend-spa/debug-adapters/_template.md deleted file mode 100644 index b2df659..0000000 --- a/.github/agents/azure-debug-generate/references/project-types/frontend-spa/debug-adapters/_template.md +++ /dev/null @@ -1,40 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# {Adapter} — Browser Debug Adapter - -> **Template** — Copy this file to `{adapter}.md` when adding a new browser debug adapter. - -## VS Code Debugger Type - -| Browser | VS Code Debugger Type | Required Extension | -|---------|----------------------|-------------------| -| {browser} | `{type}` | {extension or "None — built-in"} | - ---- - -## Launch Configuration - -```json -{ - "name": "{id} (debug)", - "type": "{type}", - "request": "launch", - "url": "http://localhost:{port from Framework Lookup Table}", - "preLaunchTask": "{id} dev" -} -``` - -| Field | Source | -|-------|--------| -| `type` | VS Code Debugger Type from table above | -| `url` | Default Port from [frontend-spa.md § Framework Lookup Table](../frontend-spa.md) | -| `preLaunchTask` | `{id} dev` — the dev server task from [frontend-spa.md § VS Code Task Configuration](../frontend-spa.md) | - - - ---- - -## Notes - - diff --git a/.github/agents/azure-debug-generate/references/project-types/frontend-spa/debug-adapters/blazorwasm.md b/.github/agents/azure-debug-generate/references/project-types/frontend-spa/debug-adapters/blazorwasm.md deleted file mode 100644 index fb8c374..0000000 --- a/.github/agents/azure-debug-generate/references/project-types/frontend-spa/debug-adapters/blazorwasm.md +++ /dev/null @@ -1,43 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Blazor WASM — Browser Debug Adapter - -> 🔲 **Planned** — This adapter is not yet implemented. Emit a `⚠️ LIMITED SUPPORT:` warning per [limited-support.md](../../../limited-support.md). - -## VS Code Debugger Type - -| Browser / Runtime | VS Code Debugger Type | Required Extension | -|-------------------|----------------------|-------------------| -| Blazor WASM (.NET) | `blazorwasm` | `ms-dotnettools.csharp` | - ---- - -## Launch Configuration - -```json -{ - "name": "{id} (debug)", - "type": "blazorwasm", - "request": "launch", - "url": "http://localhost:{port from Framework Lookup Table}", - "browser": "chrome", - "cwd": "${workspaceFolder}/{service-root}", - "preLaunchTask": "{id} dev" -} -``` - -| Field | Source | -|-------|--------| -| `type` | Always `blazorwasm` | -| `url` | Default Port from [frontend-spa.md § Framework Lookup Table](../frontend-spa.md) | -| `browser` | Which browser to launch — defaults to `chrome` | -| `cwd` | .NET project root | -| `preLaunchTask` | `{id} dev` — the dev server task from [frontend-spa.md § VS Code Task Configuration](../frontend-spa.md) | - ---- - -## Notes - -- The `blazorwasm` adapter is provided by the C# extension, not built into VS Code. -- The launch config shape differs from the CDP-based `chrome`/`msedge` adapter — it requires `browser` and `cwd` fields, and does not use `webRoot`. diff --git a/.github/agents/azure-debug-generate/references/project-types/frontend-spa/debug-adapters/chromium.md b/.github/agents/azure-debug-generate/references/project-types/frontend-spa/debug-adapters/chromium.md deleted file mode 100644 index e5654bd..0000000 --- a/.github/agents/azure-debug-generate/references/project-types/frontend-spa/debug-adapters/chromium.md +++ /dev/null @@ -1,76 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Chromium — Browser Debug Adapter - -> Covers all **Chromium-based browsers** (Chrome, Edge, etc.). The launch configuration is identical — only the `type` field differs. - -## VS Code Debugger Type - -| Browser | VS Code Debugger Type | Required Extension | -|---------|----------------------|-------------------| -| Chrome | `chrome` | None — built-in | -| Edge | `msedge` | None — built-in | - -Use the browser recorded in the plan's Prerequisites section (see [prerequisites.md](../../../../../shared-references/prerequisites.md) § Browser detection) to pick the `type`. - ---- - -## Launch Configuration - -```json -{ - "name": "{id} (debug)", - "type": "{chrome or msedge}", - "request": "launch", - "url": "http://localhost:{port from Framework Lookup Table}", - "webRoot": "${workspaceFolder}/{service-root}", - "preLaunchTask": "{id} dev" -} -``` - -| Field | Source | -|-------|--------| -| `type` | Browser Debugger Type from table above — matches the browser recorded in the plan's Prerequisites (`chrome` for Chrome, `msedge` for Edge) | -| `url` | Default Port from [frontend-spa.md § Framework Lookup Table](../frontend-spa.md) | -| `webRoot` | **Framework root** — see `webRoot` Resolution below | -| `preLaunchTask` | `{id} dev` — the dev server task from [frontend-spa.md § VS Code Task Configuration](../frontend-spa.md) | - ---- - -## `webRoot` Resolution - -> **Universal rule:** `webRoot` must ALWAYS be the **framework root directory** (where the framework's config file lives), NEVER a subdirectory within it. The dev server determines its URL path structure relative to this root. Setting `webRoot` to a subdirectory like `src/` causes path doubling and breaks breakpoint resolution. - -This applies to **all frameworks** — any framework where the config file lives in a parent directory of `src/` will exhibit the same bug. - -| Framework | Config file that defines the root | `webRoot` value | -|-----------|----------------------------------|-----------------| -| Vite | `vite.config.*` | Directory containing `vite.config.*` | -| CRA | `package.json` (with `react-scripts`) | Directory containing `package.json` | -| Angular | `angular.json` | Workspace root (or project root in monorepo) | -| Next.js | `next.config.*` | Directory containing `next.config.*` | -| Blazor WASM | `*.csproj` | Directory containing `*.csproj` | - -> ⛔ **Before writing `webRoot`**, verify: does `{service-root}` contain the framework config file (e.g., `vite.config.ts`, `angular.json`)? If the plan's Service Root points to a `src/` subdirectory that does NOT contain the config file, walk up to the parent that does. - ---- - -## Notes - -- The `"request": "launch"` mode opens a new browser window with CDP debugging enabled — breakpoints work immediately. -- `webRoot` maps the browser's served files back to workspace source files for correct breakpoint resolution. -- Chrome and Edge share the same CDP protocol, so all behavior is identical between them. - ---- - -## Troubleshooting — first-launch browser failure - -> ⚠️ A browser debug config can fail on the **first** launch (**F5**) — the browser window doesn't open, or the debugger never attaches. This is almost always **browser process / first-run state, NOT a defect in the generated `launch.json`**. The two common causes are (1) a browser instance is already running, and (2) a one-time development HTTPS certificate prompt interrupts startup (e.g. after a TLS/dev-cert setup by the app or a local emulator). - -**Don't assume a first-launch failure is a bad config.** The generated config may be correct — jumping straight to rewriting it (e.g. swapping `launch`→`attach` or hand-authoring a dedicated Edge process task) may treat the wrong cause. Guide the user through the following fixes below **first**: - -- **Close every window of the debug browser** (all Edge windows, or all Chrome windows), then press **F5** again. An already-running browser is the most common cause of a failed debug launch. -- If a **development certificate / HTTPS prompt** appeared during the first run, accept it, then close all browser windows and retry so the trusted certificate takes effect. -- If it still fails, explore if you can switch the config `type` between `msedge` and `chrome` (whichever other Chromium browser is installed), or use `editor-browser` to debug inside VS Code. -- **Only if normal launch keeps failing after the above**, fall back to launching the browser explicitly with a dedicated remote-debugging port and its own `--user-data-dir`, then switch the config to `attach`. This is heavier but a viable last resort. diff --git a/.github/agents/azure-debug-generate/references/project-types/frontend-spa/frontend-spa.md b/.github/agents/azure-debug-generate/references/project-types/frontend-spa/frontend-spa.md deleted file mode 100644 index 4238410..0000000 --- a/.github/agents/azure-debug-generate/references/project-types/frontend-spa/frontend-spa.md +++ /dev/null @@ -1,181 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Project Type: Frontend SPA - -Reference guide for local development setup of frontend single-page application projects. - -> All SPA frameworks share the same VS Code debug configuration shape: browser debugger type (e.g., `chrome`, `msedge`), `launch` request mode, dev server as a prerequisite task. The per-framework differences — runtime, startup command, default port, and background problem matcher — are resolved from the Framework Lookup Table below. - ---- - -## Detection Signals - -Match against these signals to classify a workspace root as a frontend SPA. If the workspace clearly is an SPA but doesn't match a specific signal below, still classify it as Frontend SPA — use the Framework Lookup Table's fallback row for configuration. - -| Signal | Notes | -|--------|-------| -| `vite.config.*` or `vite` in devDependencies | Vite-based SPA | -| `next.config.*` or `next` in dependencies | Next.js app | -| `angular.json` | Angular app | -| `react-scripts` in dependencies | Create React App | -| `*.razor` files + `Microsoft.AspNetCore.Components.WebAssembly` in `*.csproj` | Blazor WASM | - ---- - -## Prerequisites - -No VS Code extension is required — browser debugging uses VS Code's built-in adapter. A Chromium-based browser (Chrome or Edge) must be installed, though; that is captured as a Debug prerequisite via [prerequisites.md](../../../../shared-references/prerequisites.md) § Browser detection. Runtime prerequisites (e.g., Node.js) are listed in `runtimes/{rt}.md § Prerequisites`. - ---- - -## Runtime Support Matrix - -Runtime support for Frontend SPAs is tracked per-framework in the **Framework Lookup Table** below. Each row's `Status` column indicates implementation readiness. - ---- - -## Dependency Discovery - -Frontend SPAs communicate with backend services via HTTP during local development — they do not connect to Azure emulators directly. In monorepo setups, Azure service dependencies (storage, databases, etc.) are handled by the backend project type. In standalone SPA projects, the backend may already be running as a deployed service or a separate local process — no emulator setup is needed for the SPA itself. - ---- - -## Backend Proxy Dependencies - -Frontend dev servers often proxy API requests to a local backend during development. In multi-service setups, if a proxy config is detected pointing to another local service, that backend service's startup task should be a dependency of the frontend's dev server task to prevent `ECONNREFUSED` errors during initial page load. - -| Framework | Proxy Config Location | -|-----------|----------------------| -| Vite | `server.proxy` in `vite.config.*` | -| Create React App | `"proxy"` field in `package.json` | -| Angular | `proxy.conf.json` | -| Next.js | `rewrites()` in `next.config.*` | - ---- - -## Startup Command - -The startup command varies per framework. See the **Framework Lookup Table** below for the specific command for each detected framework (e.g., `npm run dev` for Vite, `npm start` for Angular). - ---- - -## Framework Lookup Table - -Use this table to resolve per-framework values when generating the VS Code configuration. The `Status` column is the source of truth for implementation readiness — if a framework is not ✅ Implemented, emit a `⚠️ LIMITED SUPPORT:` warning per [limited-support.md](../../limited-support.md). - -> **To add a new framework:** add a row with its runtime, detection signal, startup command, default port, problem matcher patterns, and status. - -| Framework | Runtime | Detection | Startup Command | Default Port | Ready Pattern (begins) | Ready Pattern (ends) | Status | -|-----------|---------|----------|-----------------|--------------|----------------------|---------------------|--------| -| Vite | node | `vite.config.*` or `vite` in devDependencies | `npm run dev` | 5173 | `VITE` | `Local:` | ✅ Implemented | -| Next.js | node | `next.config.*` or `next` in dependencies | `npm run dev` | 3000 | `\s*ready` | `started server on` | ✅ Implemented | -| Angular | node | `angular.json` | `npm start` | 4200 | `Compiling` | `Compiled successfully` | ✅ Implemented | -| Create React App | node | `react-scripts` in dependencies | `npm start` | 3000 | `Starting the development server` | `Compiled` | ✅ Implemented | -| Blazor WASM | dotnet | `*.razor` + `WebAssembly` SDK in `*.csproj` | `dotnet watch run` | 5000 | `Now listening on` | `Application started` | 🔲 Planned | -| other | — | No match | — | — | — | — | [limited-support.md](../../limited-support.md) | - -> ⚠️ **ANSI escape codes:** Dev servers often wrap output in color codes (e.g., `\x1b[32m...\x1b[0m`). Prefer plain-text anchors that appear outside styled regions (e.g., `Local:` instead of `ready in \d+`) to avoid regex mismatches. - ---- - -## Runtime Wiring - - - -| Startup command | Startup task label | Task type | Problem matcher | Request Mode | -|----------------|-------------------|-----------|-----------------|--------------| -| From Framework Lookup Table | `{id} dev` | `shell` | From Framework Lookup Table | `launch` | - -### VS Code Debug Configuration - -The request mode is always `launch` (VS Code opens the browser). The browser (Chrome or Edge) is the one recorded in the plan's Prerequisites section during the browser detection pass (see [prerequisites.md](../../../../shared-references/prerequisites.md) § Browser detection) — use its debug adapter `type` (`chrome` for Chrome, `msedge` for Edge). The user can change the browser in the plan before approval. - -Look up the launch configuration template from the corresponding adapter file in [`debug-adapters/`](debug-adapters/): - -| Browser / Adapter | Adapter File | Status | -|-------------------|-------------|--------| -| Chromium (Chrome, Edge, etc.) | [debug-adapters/chromium.md](debug-adapters/chromium.md) | ✅ Implemented | -| Blazor WASM (.NET) | [debug-adapters/blazorwasm.md](debug-adapters/blazorwasm.md) | 🔲 Planned | -| ∞ | [debug-adapters/_template.md](debug-adapters/_template.md) | — | - -> **To add a new debug adapter:** copy `debug-adapters/_template.md` to `debug-adapters/{adapter}.md` and add a row to this table. - -### VS Code Task Configuration - -The top-level task is the framework's dev server. No runtime build chain — the dev server handles compilation internally. The task label follows the pattern `"{id} dev"`. - -```json -{ - "type": "shell", - "label": "{id} dev", - "command": "{command from Framework Lookup Table}", - "options": { "cwd": "${workspaceFolder}/{service-root}" }, - "isBackground": true, - "runOptions": { "instanceLimit": 1, "instancePolicy": "silent" }, - "problemMatcher": { - "owner": "{framework name, lowercased}", - "pattern": { "regexp": "^$" }, - "background": { - "activeOnStart": true, - "beginsPattern": "{Ready Pattern (begins) from Framework Lookup Table}", - "endsPattern": "{Ready Pattern (ends) from Framework Lookup Table}" - } - } -} -``` - -### Connection Strings - -Not applicable — Frontend SPAs do not connect to Azure emulators directly. In monorepo setups, connection strings are owned by the backend project type. - ---- - -## API Test Collections - -Not applicable — Frontend SPAs do not expose API endpoints. In monorepo setups, API test collections are owned by the backend project type. See [api-test-collections.md](../../api-test-collections.md) for backend patterns. - ---- - -## VS Code Extension Recommendations (`.vscode/extensions.json`) - -No project-type-specific extensions. Browser debugging uses VS Code's built-in capabilities. - -> Framework-specific extensions (if any) would be listed here. Runtime extensions are listed in `runtimes/{rt}.md`. - ---- - -## VS Code Workspace Settings (`.vscode/settings.json`) - -No project-type-specific workspace settings. - ---- - -## Validation Signals - -Used by [validation.md](../../validation.md) during Phase 3 to verify the generated debug configuration works. - -### Ready Signal - -Use the `Ready Pattern (begins)` and `Ready Pattern (ends)` columns from the **Framework Lookup Table** above for the detected framework. The ready signal is observed on stdout of the dev server task. - -### HTTP Verification - -| Curl Target | Expected Status | Notes | -|-------------|-----------------|-------| -| `http://localhost:{dev-server-port}` | `200` or `301` | Use the resolved dev server port from the Framework Lookup Table. Validates the dev server started — do NOT launch a browser. Framework-specific redirects (e.g., `301` for Next.js) are acceptable. | - ---- - -## Checklist — Frontend SPA Project Validation - -After generating `launch.json` and `tasks.json`, verify the following were produced correctly: - -1. ✅ Dev server task exists in `tasks.json` with a custom `background` problem matcher using the correct begin/end patterns from the Framework Lookup Table -2. ✅ `launch.json` uses the browser debug adapter matching the browser recorded in the plan's Prerequisites (`chrome` or `msedge`) with `"request": "launch"` -3. ✅ `launch.json` `url` matches the framework's default port from the Framework Lookup Table -4. ✅ `launch.json` `preLaunchTask` points to the dev server task - -> Runtime-specific checks (e.g., build task, debugger type) are defined in `runtimes/{rt}.md`. diff --git a/.github/agents/azure-debug-generate/references/project-types/functions.md b/.github/agents/azure-debug-generate/references/project-types/functions.md deleted file mode 100644 index 3e10d21..0000000 --- a/.github/agents/azure-debug-generate/references/project-types/functions.md +++ /dev/null @@ -1,281 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Project Type: Azure Functions - -Reference guide for local development setup of Azure Functions projects. - ---- - -## Detection Signals - -| Signal | Notes | -|--------|-------| -| `host.json` present | Primary signal — required | -| Azure Functions SDK in dependencies | Confirms it's a Functions project — identified during the plan phase | - ---- - -## Prerequisites - -| Tool | Detection Command | Required For | Install Link | -|------|-------------------|-------------|-------------| -| Azure Functions Core Tools | `func --version` | Run Functions host locally | [aka.ms/azure-functions-core-tools](https://aka.ms/azure-functions-core-tools) | - ---- - -## Runtime Support Matrix - -| Runtime | Status | Reference | -|---------|--------|-----------| -| node-ts | ✅ Implemented | [runtimes/node.md](../runtimes/node.md) | -| node-js | ✅ Implemented | [runtimes/node.md](../runtimes/node.md) | -| dotnet (Functions isolated) | ✅ Implemented | [runtimes/dotnet.md](../runtimes/dotnet.md) | -| python | 🔲 Planned | [limited-support.md](../limited-support.md) | -| java | 🔲 Planned | [limited-support.md](../limited-support.md) | - -> **Limited-support runtimes:** When a runtime with limited support is detected, emit a `⚠️ LIMITED SUPPORT:` warning per [limited-support.md](../limited-support.md) and ask the user whether to proceed. If the user agrees, proceed with best-effort generation for all artifacts (emulators, debug config, tasks). Do not silently skip debug/launch configuration — let the user decide. - ---- - -## Dependency Discovery - -Scan every `function.json` for its `"type"` binding field, **or** scan Python/Java source files for trigger decorator/attribute names. Each binding maps to an emulator. - -### Binding → Emulator Mapping - -| Binding Type(s) | Azure Service | Default Ports | Connection String | Status | Reference | -|----------------|---------------|---------------|-------------------|--------|-----------| -| `blobTrigger`, `blob` | Blob Storage | 10000 | `UseDevelopmentStorage=true` | ✅ Implemented | [emulators/azurite.md](../emulators/azurite.md) | -| `queueTrigger`, `queue` | Queue Storage | 10001 | `UseDevelopmentStorage=true` | ✅ Implemented | [emulators/azurite.md](../emulators/azurite.md) | -| `table` | Table Storage | 10002 | `UseDevelopmentStorage=true` | ✅ Implemented | [emulators/azurite.md](../emulators/azurite.md) | -| `cosmosDBTrigger`, `cosmosDB` | Cosmos DB | 8081, 10250–10254 | — | 🔲 Planned | [limited-support.md](../limited-support.md) | -| `serviceBusTrigger`, `serviceBus` | Service Bus | 5672 | — | 🔲 Planned | [limited-support.md](../limited-support.md) | -| `eventHubTrigger`, `eventHub` | Event Hubs | 9093 | — | 🔲 Planned | [limited-support.md](../limited-support.md) | -| `sql`, `sqlTrigger` | Azure SQL | 1433 | — | 🔲 Planned | [limited-support.md](../limited-support.md) | -| `httpTrigger` | (built-in) | — | — | ✅ Implemented | — | -| `timerTrigger` | (built-in) | — | — | ✅ Implemented | — | - -> **Azurite consolidation:** If multiple storage bindings (blob + queue + table) are detected, create a **single** Azurite service — not one per binding type. - -### Services Without Azure Emulators - -| Binding Type | Azure Service | Recommendation | -|-------------|---------------|----------------| -| `signalR` | Azure SignalR | Use a dev-tier Azure SignalR instance | -| PostgreSQL (SDK, not a binding) | Azure Database for PostgreSQL | [emulators/postgres.md](../emulators/postgres.md) | - ---- - -## Startup Command - -> For VS Code `"type": "func"` tasks, omit the `func` executable prefix — the Azure Functions extension supplies it. The equivalent CLI commands are shown below for reference. - -**Node.js (TypeScript & JavaScript):** - -``` -languageWorkers__node__arguments="--inspect=9229" func host start -``` - -> ⚠️ The Azure Functions Core Tools do **not** automatically enable the Node.js debugger. You **must** supply `--inspect=9229` to the Node worker so it opens a debug port for VS Code to attach to. Without it, the `attach` configuration connects to nothing. In the VS Code `func` task, set this via `options.env` (`languageWorkers__node__arguments`) rather than a command-line flag — the env-var form avoids shell/JSON quoting issues and uses the same `languageWorkers____arguments` convention across runtimes. - -**dotnet:** - -``` -func host start -``` - -> For .NET, the Functions host spawns a worker process that VS Code attaches to via `coreclr` — no additional debug flags are needed on the command line. - ---- - -## Runtime Wiring - - - -> See **§ VS Code Task Configuration** below for the concrete task JSON for each runtime. - -| Runtime | Startup task label | Task type | Problem matcher | Request mode | Status | Reference | -|---------|--------------------|-----------|-----------------|--------------|--------|-----------| -| node-ts | `{service-id}: func host start` | `func` | `$func-node-watch` | `attach` | ✅ Implemented | [runtimes/node.md](../runtimes/node.md) | -| node-js | `{service-id}: func host start` | `func` | `$func-node-watch` | `attach` | ✅ Implemented | [runtimes/node.md](../runtimes/node.md) | -| dotnet | `{service-id}: func host start` | `func` | `$func-dotnet-watch` | `attach` | ✅ Implemented | [runtimes/dotnet.md](../runtimes/dotnet.md) | -| python | `{service-id}: func host start` | `func` | `$func-python-watch` | `attach` | 🔲 Planned | [limited-support.md](../limited-support.md) | -| java | `{service-id}: func host start` | `func` | `$func-java-watch` | `attach` | 🔲 Planned | [limited-support.md](../limited-support.md) | - -> `{service-id}` is the kebab-case ID derived from the plan's Service Label column — see [generate.md § Service ID Derivation](../generate.md). - -> **dotnet `processName` warning.** The .NET `coreclr` (request: `attach`) configuration requires the literal `processName` in `launch.json` — `.exe` suffix on Windows, no extension on macOS/Linux. Without it, F5 fails with `"No process with the specified name is currently running"`. Do NOT use `${command:pickProcess}`. See [runtimes/dotnet.md § processName Determination](../runtimes/dotnet.md). - -The startup step `dependsOn`: -1. The runtime-specific build/watch task label from `runtimes/{rt}.md` § Build Chain (e.g., `{service-id}: npm watch` for node-ts, `{service-id}: dotnet build` for dotnet) -2. `"Start Emulators"` (only when emulators are required — omit when the plan has no checked emulators) - -### VS Code Task Configuration - -The top-level task uses the VS Code `func` task type provided by the Azure Functions extension (`ms-azuretools.vscode-azurefunctions`). The launch configuration's `preLaunchTask` points to this task. - -> **Task label scoping:** All task labels MUST be prefixed with the service ID (e.g., `functions-api: func host start`). This prevents label collisions in multi-service workspaces. See [generate.md § Service ID Derivation](../generate.md). - -#### Debug Argument Injection - -The Functions host runs your code in a separate **language-worker** process, so the worker must start with the runtime's debug flag. Inject it through the task's `options.env` using the `languageWorkers____arguments` convention, then point a matching `attach` config at the resulting port. Only the env key, the flag value, and the attach `type`/`port` change per runtime — the rest of the `func host start` task stays identical, so new runtimes slot straight into this table. - -| Runtime | `options.env` setting | Value to inject | Debug port | `attach` type | Status | -|---------|-----------------------|-----------------|------------|---------------|--------| -| node-ts | `languageWorkers__node__arguments` | `--inspect=9229` | 9229 | `node` | ✅ Implemented | -| node-js | `languageWorkers__node__arguments` | `--inspect=9229` | 9229 | `node` | ✅ Implemented | - - -**node-ts** (has watch task): - -```json -{ - "type": "func", - "label": "{service-id}: func host start", - "command": "host start", - "options": { - "cwd": "${workspaceFolder}/{path-to-functions-project}", - "env": { "languageWorkers__node__arguments": "--inspect=9229" } - }, - "problemMatcher": "$func-node-watch", - "isBackground": true, - "runOptions": { "instanceLimit": 1, "instancePolicy": "silent" }, - "dependsOn": ["{service-id}: npm watch", "Start Emulators"] -} -``` - -> Remove `"Start Emulators"` from `dependsOn` when the plan has no checked emulators. - -**node-js** (no compile/watch step): - -```json -{ - "type": "func", - "label": "{service-id}: func host start", - "command": "host start", - "options": { - "cwd": "${workspaceFolder}/{path-to-functions-project}", - "env": { "languageWorkers__node__arguments": "--inspect=9229" } - }, - "problemMatcher": "$func-node-watch", - "isBackground": true, - "runOptions": { "instanceLimit": 1, "instancePolicy": "silent" }, - "dependsOn": ["{service-id}: npm install", "Start Emulators"] -} -``` - -> Remove `"Start Emulators"` from `dependsOn` when the plan has no checked emulators. - -> `dependsOn`: first entry is the runtime-specific prerequisite — watch task for TypeScript, install task for JavaScript. The exact task labels come from `runtimes/{rt}.md` § Build Chain, prefixed with the service ID. - -**dotnet** (compiled — requires build before host start): - -```json -{ - "type": "func", - "label": "{service-id}: func host start", - "command": "host start", - "options": { "cwd": "${workspaceFolder}/{path-to-functions-project}" }, - "problemMatcher": "$func-dotnet-watch", - "isBackground": true, - "runOptions": { "instanceLimit": 1, "instancePolicy": "silent" }, - "dependsOn": ["{service-id}: dotnet build", "Start Emulators"] -} -``` - -> Remove `"Start Emulators"` from `dependsOn` when the plan has no checked emulators. - -> **dotnet `processName`:** The `coreclr` attach configuration requires a literal `processName` in `launch.json`. See [runtimes/dotnet.md § processName Determination](../runtimes/dotnet.md) for how to derive it from the `.csproj`, including cross-platform rules (`.exe` suffix on Windows only). - -#### .NET Isolated Worker Version Constraints - -Functions Worker **2.x** is required for .NET 10: -- `Microsoft.Azure.Functions.Worker >= 2.50.0` -- `Microsoft.Azure.Functions.Worker.Sdk >= 2.0.5` - -When detecting a .NET Functions project, verify these minimum versions. Worker 2.x uses the canonical `func host start` + `coreclr` (request: `attach`) flow where the Functions host spawns the worker process. - -### Connection Strings - -| Emulator | Key | Value | Status | Reference | -|----------|-----|-------|--------|-----------| -| Azurite (storage) | `AzureWebJobsStorage` | `UseDevelopmentStorage=true` | ✅ Implemented | [emulators/azurite.md](../emulators/azurite.md) | -| Cosmos DB | {detected from bindings} | — | 🔲 Planned | [limited-support.md](../limited-support.md) | -| Service Bus | {detected from bindings} | — | 🔲 Planned | [limited-support.md](../limited-support.md) | -| Event Hubs | {detected from bindings} | — | 🔲 Planned | [limited-support.md](../limited-support.md) | -| SQL Edge | {detected from bindings} | — | 🔲 Planned | [limited-support.md](../limited-support.md) | -| PostgreSQL | {detected from code} | See [emulators/postgres.md](../emulators/postgres.md) | ✅ Implemented | [emulators/postgres.md](../emulators/postgres.md) | - -> **Key discovery:** Except for `AzureWebJobsStorage` (a well-known Azure Functions convention), connection string key names are not fixed. Perform targeted resolution — check `local.settings.json`, `.env`, binding configurations, and SDK usage to detect the actual key names. Use the detected names — do not invent defaults. - -> **Never overwrite** existing values in `local.settings.json` — only add missing keys. - ---- - -## API Test Collections - -See [api-test-collections.md](../api-test-collections.md) for all test script patterns. For this project type, generate tests for: - -- HTTP triggers → HTTP patterns with `baseUrl: http://localhost:7071/api` -- Blob triggers → Storage § Blob trigger pattern -- Queue triggers → Storage § Queue trigger pattern -- Timer triggers → Timer § admin API pattern (only if explicitly requested) -- Cosmos DB triggers → Cosmos DB pattern -- Service Bus triggers → Service Bus pattern -- Event Hub triggers → Event Hubs pattern - ---- - -## VS Code Extension Recommendations (`.vscode/extensions.json`) - -Contribute the following to `.vscode/extensions.json`. - -| Extension ID | Why Required | -|--------------|-------------| -| `ms-azuretools.vscode-azurefunctions` | Contributes the `"type": "func"` task type | - ---- - -## VS Code Workspace Settings (`.vscode/settings.json`) - -Contribute the following to `.vscode/settings.json`. - -| Setting | Value | Why | -|---------|-------|-----| -| `azureFunctions.showProjectWarning` | `false` | Suppresses the "failed to detect project" prompt that fires when the extension scans the workspace — our generated config already handles project setup | -| `azureFunctions.validateEmulators` | `false` | Suppresses emulator validation warnings from the extension — emulators are managed via user's orchestrator configuration | - ---- - -## Validation Signals - -Used by [validation.md](../validation.md) during Phase 3 to verify the generated debug configuration works. - -### Ready Signal - -| Top-Level Task | Ready Signal (stdout) | -|----------------|----------------------| -| `{service-id}: func host start` | `"Host lock lease acquired"` or `"Functions host started"` | - -> The `Top-Level Task` column uses the canonical `{service-id}:`-prefixed label — see [generate.md § Service ID Derivation](../generate.md). Resolve `{service-id}` to the same value used during generation before matching against `tasks.json`. - -### HTTP Verification - -| Curl Target | Expected Status | Notes | -|-------------|-----------------|-------| -| First discovered anonymous `httpTrigger` route (e.g., `http://localhost:7071/api/{function-name}`) | `200` | Port `7071` is the Functions host HTTP port; debug port `9229` is for the debugger only. Use the first anonymous HTTP trigger found during targeted resolution. If only function-key/admin routes exist, skip HTTP verification with a warning rather than assuming a route. | - ---- - -## Checklist — Functions Project Validation - -After generating `launch.json`, `tasks.json`, and `extensions.json`, verify the following were produced correctly: - -1. ✅ `{service-id}: func host start` task exists in `tasks.json` with `"type": "func"` -2. ✅ `launch.json` `preLaunchTask` points to `{service-id}: func host start` -3. ✅ `.vscode/extensions.json` includes `ms-azuretools.vscode-azurefunctions` -4. ✅ `local.settings.json` contains all required connection string keys (e.g., `AzureWebJobsStorage`) -5. ✅ `dependsOn` chain includes the runtime build/watch task and `Start Emulators` (when emulators are required) - -> Runtime-specific checks (e.g., `dotnet build` task, `processName` derivation) are defined in `runtimes/{rt}.md`. diff --git a/.github/agents/azure-debug-generate/references/runtimes/_template.md b/.github/agents/azure-debug-generate/references/runtimes/_template.md deleted file mode 100644 index 16a856a..0000000 --- a/.github/agents/azure-debug-generate/references/runtimes/_template.md +++ /dev/null @@ -1,138 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# {Runtime} — Debug & Build Configuration - -> **Template** — Copy this file to `runtimes/{rt}.md` when adding a new runtime. - ---- - -## Prerequisites - - - - -| Tool | Detection Command | Required For | Install Link | -|------|-------------------|-------------|-------------| -| `{tool}` | `{tool} --version` | {purpose} | `{install-url}` | - ---- - -## Debugger Properties - - - -| Property | Value | Notes | -|----------|-------|-------| -| Debug protocol | `{protocol}` | The wire protocol the runtime exposes (e.g., `Node Inspector`, `CoreCLR DAP`, `debugpy DAP`, `JDWP`, `Delve DAP`). | -| VS Code debugger type | `{type}` | The VS Code debugger adapter identifier (e.g., `node`, `coreclr`, `debugpy`, `java`, `go`). | -| Base debug port | `{port}` | Default debug port for this runtime; overridden per-service in monorepos | - -### VS Code Problem Matchers - - - -| Task | Watch Problem Matcher | Build Problem Matcher | -|------|----------------------|----------------------| -| {task} | `{matcher}` | `{matcher}` | - ---- - -## Build Chain - - - -Chain shape (startup task comes from the project type): - -``` -"{service-id}: {startup task}" ← from project-types/{type}.md Runtime Wiring - ├── dependsOn: "{service-id}: {build/watch step}" ← this file - └── dependsOn: "Start Emulators" ← only when emulators are required -``` - -> **Task label scoping:** All task labels MUST be prefixed with the service ID derived from the plan's Service Label column. See [generate.md § Service ID Derivation](../generate.md). - -### Build Commands - -| Step | Command | Purpose | Background? | -|------|---------|---------|------------| -| Start Emulators | `docker compose up -d` | Start all emulator services (idempotent — no-op if already running) | No | - -See [generate.md](../generate.md) § Task `runOptions` Rules for how these build steps are rendered into VS Code task configuration. - ---- - -## Convenience Scripts - - - -The plan's Convenience Scripts table specifies WHICH scripts to generate. This section covers HOW to register them for this runtime. - -**Script runner:** `{file}` (e.g., `package.json` scripts, `scripts/` directory, `Makefile`, `pyproject.toml`) -**Run command pattern:** `{command}` (e.g., `npm run {script}`, `./scripts/{script}.sh`) - -> When the runtime's script runner is not inherently cross-platform (e.g., standalone scripts vs `package.json`), generate platform-appropriate scripts or document cross-platform alternatives. - -### Script Format - - - -### Common Script Implementations - -Use these implementations when building scripts from the plan: - -| Script Purpose | Typical Command | Notes | -|---------------|-----------------|-------| -| Start emulators | `docker compose up -d` | Idempotent — safe to re-run | -| Stop emulators | `docker compose down` | Stops and removes containers | -| Clean emulator data | Stop containers with volumes, then remove bind-mount data directories | Run `docker compose down -v` (`-v` also drops **named volumes** like Postgres's `postgres_data`), then remove `{data-dirs}` = `./.{name}` **bind-mount** directories derived from `docker-compose.yml` `volumes:` mounts. Use platform-appropriate removal. | -| Run migrations | `{migration tool CLI command}` | See [migrations.md](../migrations.md) for how to determine the command | - ---- - -## VS Code Extension Recommendations (`.vscode/extensions.json`) - - - -| Extension ID | Why Required | -|--------------|-------------| -| `{extension-id}` | {reason} | - ---- - -## VS Code Workspace Settings (`.vscode/settings.json`) - - - -| Setting | Value | Why | -|---------|-------|-----| -| `{setting.key}` | `{value}` | {reason} | - ---- - -## Checklist — {Runtime} Validation - -> ⛔ **MANDATORY — runs during Phase 3 validation after all artifacts are generated.** You MUST verify every item below. Do NOT skip, assume, or approximate results. - - - -After generating VS Code configuration, verify the following were produced correctly: - -### Post-Generation Checks - -1. ✅ Build task exists in `tasks.json` with the correct problem matcher -2. ✅ `launch.json` uses the correct debugger type and request mode -3. ✅ `.vscode/extensions.json` includes runtime extensions listed above - -### Live Validation Checks - - - -> Project-type-specific checks (e.g., startup task, connection strings) are defined in `project-types/{type}.md`. diff --git a/.github/agents/azure-debug-generate/references/runtimes/dotnet.md b/.github/agents/azure-debug-generate/references/runtimes/dotnet.md deleted file mode 100644 index 65b8153..0000000 --- a/.github/agents/azure-debug-generate/references/runtimes/dotnet.md +++ /dev/null @@ -1,194 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# .NET / C# — Debug & Build Configuration - -> Covers **.NET** projects. Project-type-specific hosting (Functions, App Service, etc.) is defined in `project-types/{type}.md` — this file covers the .NET runtime layer only. - -## Prerequisites - -| Tool | Detection Command | Required For | Install Link | -|------|-------------------|-------------|-------------| -| .NET SDK | `dotnet --version` | Build and run .NET projects | [dotnet.microsoft.com](https://dotnet.microsoft.com/download) | -| C# extension | VS Code extension `ms-dotnettools.csharp` installed | Contributes the `coreclr` debugger type required for F5 debugging | [Marketplace](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp) | - ---- - -## Debugger Properties - - - -| Property | Value | Notes | -|----------|-------|-------| -| Debug protocol | `CoreCLR DAP` | .NET Core / .NET 5+ debug adapter protocol | -| VS Code debugger type | `coreclr` | Contributed by the C# extension (`ms-dotnettools.csharp`) | -| Request mode | `attach` | VS Code attaches to a running .NET process by `processName` | -| Base debug port | — | CoreCLR attach uses `processName`, not a port | - -### VS Code Problem Matchers - -| Task | Watch Problem Matcher | Build Problem Matcher | -|------|----------------------|----------------------| -| `dotnet build` | — | `$msCompile` | - ---- - -## processName Determination - -The `coreclr` debugger (request: `attach`) matches `processName` **literally** against the OS process list. Getting this wrong means F5 silently fails. - -### Cross-Platform Rules - -| Platform | Process name | Example | -|----------|-------------|---------| -| Windows | `{AssemblyName}.exe` | `Scrapbook.Api.exe` | -| macOS / Linux | `{AssemblyName}` (no extension) | `Scrapbook.Api` | - -> ⛔ **Windows requires the `.exe` suffix.** Writing `"Scrapbook.Api"` instead of `"Scrapbook.Api.exe"` produces: -> -> ``` -> No process with the specified name is currently running. -> ``` - -### Deriving `AssemblyName` - -The process name is derived from the project's `.csproj`: - -1. If `` is defined, use that value -2. Otherwise, the `.csproj` filename without extension (e.g., `Functions.csproj` → `Functions`) -3. The `.csproj` **MUST** have `Exe` — otherwise no executable is produced and there's no target to debug - -**Verify before writing `launch.json`:** after `dotnet build`, confirm the executable exists at `{projectDir}/bin/Debug/{tfm}/{AssemblyName}[.exe]`. - -> ❌ **Do NOT use `${command:pickProcess}` or `${command:azureFunctions.pickProcess}`** — both pop a blocking dialog every F5. Use literal `processName`. - ---- - -## Build Chain - -Tasks owned by this runtime: build. The startup task and its dependency wiring are provided by the project type's Runtime Wiring table — not this file. - -> **Task label scoping:** All task labels MUST be prefixed with the service ID (e.g., `functions-api: dotnet build`). See [generate.md § Service ID Derivation](../generate.md). - -Chain shape (startup task comes from the project type): - -``` -"{service-id}: {startup task}" ← from project-types/{type}.md Runtime Wiring - ├── dependsOn: "{service-id}: dotnet build" ← this file - └── dependsOn: "Start Emulators" ← only when emulators are required -``` - -### Build Commands - -| Step | Task Label | Command | Purpose | Background? | -|------|-----------|---------|---------|------------| -| build | `{service-id}: dotnet build` | `dotnet build {path-to-csproj} --configuration Debug` | Restore + compile | No | - -```json -{ - "label": "{service-id}: dotnet build", - "type": "process", - "command": "dotnet", - "args": [ - "build", - "${workspaceFolder}/{path-to-csproj}", - "--configuration", - "Debug" - ], - "problemMatcher": "$msCompile", - "group": "build", - "runOptions": { "instanceLimit": 1, "instancePolicy": "silent" } -} -``` - -> .NET does not have a watch-based incremental compile step in the debug chain (unlike TypeScript's `tsc --watch`). Each F5 re-runs `dotnet build`. - -See [generate.md](../generate.md) § Task `runOptions` Rules for how these build steps are rendered into VS Code task configuration. - ---- - -## Convenience Scripts - -The plan's Convenience Scripts table specifies WHICH scripts to generate. This section covers HOW to register them for .NET projects. - -Because .NET projects do not have a built-in script runner equivalent to `npm run`, convenience scripts live as standalone scripts under `scripts/`. - -**Script runner:** Scripts in `scripts/` directory -**Run command pattern:** Platform-dependent (see below) - -### Script Format - -Generate scripts appropriate for the user's platform: - -| Platform | File extension | Shebang / header | Make executable | -|----------|---------------|-------------------|-----------------| -| macOS / Linux | `.sh` | `#!/bin/bash` | `chmod +x` | -| Windows | `.ps1` | — | — (PowerShell scripts run directly) | - -> When the platform is ambiguous, generate `.sh` scripts — they also work on Windows via Git Bash or WSL. - -### Common Script Implementations - -Use these implementations when building scripts from the plan: - -| Script Purpose | Typical Command | Notes | -|---------------|-----------------|-------| -| Start emulators | `docker compose up -d` | Idempotent — safe to re-run | -| Stop emulators | `docker compose down` | Stops and removes containers | -| Clean emulator data | Stop containers with volumes, then remove bind-mount data directories | Run `docker compose down -v` (`-v` also drops **named volumes** like Postgres's `postgres_data`), then remove `{data-dirs}` = `./.{name}` **bind-mount** directories derived from `docker-compose.yml` `volumes:` mounts. Use platform-appropriate removal (e.g., `rm -rf` on macOS/Linux, `Remove-Item -Recurse` on Windows) | -| Run migrations | `{migration tool CLI command}` | See [migrations.md](../migrations.md) for how to determine the command | - ---- - -## VS Code Extension Recommendations (`.vscode/extensions.json`) - - - -| Extension ID | Why Required | -|--------------|-------------| -| `ms-dotnettools.csharp` | Contributes the `"type": "coreclr"` debugger for .NET attach/launch | -| `ms-dotnettools.csdevkit` | C# Dev Kit — adds Solution Explorer, test discovery, and enhanced debugging experience | - -> Project-type-specific extensions (e.g., `ms-azuretools.vscode-azurefunctions` for Functions) are listed in `project-types/{type}.md`. - ---- - -## VS Code Workspace Settings (`.vscode/settings.json`) - - - -| Setting | Value | Why | -|---------|-------|-----| -| `files.exclude: **/bin` | `true` | Hide .NET build output from explorer | -| `files.exclude: **/obj` | `true` | Hide .NET intermediate build files from explorer | - -> These exclusions reduce workspace noise. They are deep-merged with any existing `files.exclude` entries — see [generate.md § VS Code Workspace Settings](../generate.md). - ---- - -## Checklist — .NET Runtime Validation - -> ⛔ **MANDATORY — runs during Phase 3 validation after all artifacts are generated.** You MUST verify every item below. Do NOT skip, assume, or approximate results. - -After generating VS Code configuration, verify the following were produced correctly: - -### Post-Generation Checks - -1. ✅ `dotnet build` task exists in `tasks.json` with `$msCompile` problem matcher -2. ✅ `processName` in `launch.json` matches the `AssemblyName` derived from `.csproj` (platform-appropriate — see § processName Determination) -3. ✅ `launch.json` uses `"type": "coreclr"` with `"request": "attach"` -4. ✅ `.vscode/extensions.json` includes `ms-dotnettools.csharp` and `ms-dotnettools.csdevkit` - -### Live Validation Checks - -These checks run during Phase 3 validation ([validation.md](../validation.md) Step 7), after the ready signal is observed: - -1. ✅ Verify the target process exists for attachment — the `processName` in `launch.json` must match a running OS process: - - **Windows PowerShell:** `Get-Process -Name "" -ErrorAction SilentlyContinue` → must return a process - - **macOS/Linux:** `pgrep -x ""` → must return a PID -2. ✅ On Windows, confirm `processName` includes the `.exe` suffix (e.g., `"Scrapbook.Api.exe"`, NOT `"Scrapbook.Api"`) -3. ✅ If process check fails, fix `launch.json` before marking the config ✅ - -> If the process is not found, the F5 attach WILL fail with: `"No process with the specified name is currently running"` diff --git a/.github/agents/azure-debug-generate/references/runtimes/node.md b/.github/agents/azure-debug-generate/references/runtimes/node.md deleted file mode 100644 index 1d32949..0000000 --- a/.github/agents/azure-debug-generate/references/runtimes/node.md +++ /dev/null @@ -1,190 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Node.js — Debug & Build Configuration - -> Covers both **JavaScript** and **TypeScript** projects. The debugger properties are identical; only the build chain differs. - -## Prerequisites - -| Tool | Detection Command | Required For | Install Link | -|------|-------------------|-------------|-------------| -| Node.js | `node --version` | All Node projects | [nodejs.org](https://nodejs.org/) | -| npm | `npm --version` | Dependency management | (bundled with Node) | - ---- - -## Debugger Properties - -| Property | Value | Notes | -|----------|-------|-------| -| Debug protocol | `Node Inspector` | V8 inspector protocol over WebSocket | -| VS Code debugger type | `node` | Maps Node Inspector to VS Code's built-in Node debugger | -| Base debug port | `9229` | Default Node.js inspector port | -| Auto-restart | `true` | Re-attach after the host restarts on file changes | - -### `outFiles` (node-ts only — REQUIRED) - -For any TypeScript Node.js project, the `launch.json` attach configuration **must** include `outFiles` pointing to the compiled JavaScript output. Without it, VS Code cannot locate `.js.map` files and breakpoints set in `.ts` source files will not trigger. - -**Derivation:** Read the project's `tsconfig.json` to determine the output directory: -1. Check `compilerOptions.outDir` — this is the compiled output root (e.g., `"outDir": "./dist"`) -2. Construct the glob: `${workspaceFolder}/{service-root}/{outDir}/**/*.js` -3. If `outDir` is not set, fall back to the project root: `${workspaceFolder}/{service-root}/**/*.js` - -> `{service-root}` is the path from the workspace root to the project directory. In a single-project workspace this is empty (just `${workspaceFolder}/dist/**/*.js`). In a monorepo it includes the nested path (e.g., `${workspaceFolder}/services/functions/dist/**/*.js`). - -**Example:** -```json -{ - "name": "My API (debug)", - "type": "node", - "request": "attach", - "port": 9229, - "restart": true, - "outFiles": ["${workspaceFolder}/services/functions/dist/**/*.js"], - "preLaunchTask": "{service-id}: func host start" -} -``` - -> `preLaunchTask` uses the canonical `{service-id}:`-prefixed task label — see [generate.md § Service ID Derivation](../generate.md). The example above corresponds to a `functions-api` service, so the resolved value is `functions-api: func host start`. - -> **Verify:** `tsconfig.json` must have `"sourceMap": true` in `compilerOptions` for any TypeScript project that will be debugged. Without source maps, VS Code breakpoints in `.ts` files cannot bind to the compiled `.js` output and will appear as gray (unverified) dots — even when the debugger is successfully attached. The build/watch task must run **before** the startup task so compiled output exists when the debugger attaches. - -### VS Code Problem Matchers - -| Variant | Watch Problem Matcher | Build Problem Matcher | -|---------|----------------------|----------------------| -| node-ts | `$tsc-watch` | `$tsc` | -| node-js | — | — | - -> **Monorepo / multi-service:** When multiple Node services are present, each is assigned a sequential debug port starting from the base port defined in the project type's Runtime Wiring table. See [multi-service.md](../multi-service.md) for port assignment rules. - ---- - -## Variant Detection - -| Signal | Variant | Notes | -|--------|---------|-------| -| `tsconfig.json` present | **node-ts** | TypeScript — requires compile step | -| `package.json` without `tsconfig.json` | **node-js** | Plain JavaScript — no compile step | - ---- - -## Build Chain - -Tasks owned by this runtime: install, clean, build, watch. The startup task and its dependency wiring are provided by the project type's Runtime Wiring table — not this file. - -> **Task label scoping:** All task labels MUST be prefixed with the service ID (e.g., `functions-api: npm watch`). See [generate.md § Service ID Derivation](../generate.md). - -### Build Commands - -#### node-ts (TypeScript) - -``` -"{service-id}: {startup task}" ← from project-types/{type}.md Runtime Wiring - ├── dependsOn: "{service-id}: npm watch" - │ └── dependsOn: "{service-id}: npm clean" - │ └── dependsOn: "{service-id}: npm install" - └── dependsOn: "Start Emulators" ← only when emulators are required -``` - -| Step | Task Label | Command | Purpose | Background? | -|------|-----------|---------|---------|------------| -| install | `{service-id}: npm install` | `npm install` | Installs dependencies | No | -| clean | `{service-id}: npm clean` | `npm run clean` | Cleans build output | No | -| watch | `{service-id}: npm watch` | `npm run watch` | Runs `tsc --watch` for incremental builds | ✅ Yes | -| build | `{service-id}: npm build` | `npm run build` | One-shot build (used outside debug flow) | No | - -#### node-js (JavaScript) - -``` -"{service-id}: {startup task}" ← from project-types/{type}.md Runtime Wiring - ├── dependsOn: "{service-id}: npm install" - └── dependsOn: "Start Emulators" ← only when emulators are required -``` - -| Step | Task Label | Command | Purpose | Background? | -|------|-----------|---------|---------|------------| -| install | `{service-id}: npm install` | `npm install` | Installs dependencies | No | - -> No compile, clean, or watch step — JavaScript runs directly. - -> **Monorepo / alternative package managers:** Adjust commands if the project uses `yarn`, `pnpm`, or a monorepo layout. The key invariant is the chain shape: **install → [clean → build/watch →] startup task** (compile steps only for TypeScript). - -See [generate.md](../generate.md) § Task `runOptions` Rules for how these build steps are rendered into VS Code task configuration. - - ---- - -## Convenience Scripts - -The plan's Convenience Scripts table specifies WHICH scripts to generate. This section covers HOW to register them for Node.js projects. - -**Script runner:** `package.json` `"scripts"` block -**Run command pattern:** `npm run {script-name}` - -### Script Format - -Each script is a shell command added to `package.json` `"scripts"`. Example entry: - -```json -{ - "{script-name}": "{shell command}" -} -``` - -### Common Script Implementations - -Use these implementations when building scripts from the plan: - -| Script Purpose | Typical Command | Notes | -|---------------|-----------------|-------| -| Start emulators | `docker compose up -d` | Idempotent — safe to re-run | -| Stop emulators | `docker compose down` | Stops and removes containers | -| Clean emulator data | `docker compose down -v && rimraf {data-dirs}` | `-v` (`--volumes`) also removes **named volumes** (e.g. Postgres's `postgres_data`). `{data-dirs}` = space-separated `./.{name}` **bind-mount** directories derived from `docker-compose.yml` `volumes:` mounts (e.g., `.azurite`). Use `rimraf` for cross-platform compatibility. Requires `rimraf` in `devDependencies` — see [generate.md § Dependency Availability](../generate.md). | -| Run migrations | `{migration tool CLI command}` | See [migrations.md](../migrations.md) for how to determine the command | - ---- - -## VS Code Extension Recommendations (`.vscode/extensions.json`) - -No recommended extensions. - ---- - -## VS Code Workspace Settings (`.vscode/settings.json`) - - - -| Setting | Value | Why | -|---------|-------|-----| -| `files.exclude: **/node_modules` | `true` | Hide dependency tree from explorer — large and noisy | - -> This exclusion reduces workspace noise. It is deep-merged with any existing `files.exclude` entries — see [generate.md § VS Code Workspace Settings](../generate.md). - ---- - -## Checklist — Node.js Runtime Validation - -> ⛔ **MANDATORY — runs during Phase 3 validation after all artifacts are generated.** You MUST verify every item below. Do NOT skip, assume, or approximate results. - -After generating VS Code configuration, verify the following were produced correctly: - -### Post-Generation Checks - -1. ✅ `launch.json` uses `"type": "node"` with the correct debug port -2. ✅ For TypeScript: `launch.json` includes `"outFiles"` derived from `tsconfig.json` `outDir` (e.g., `["${workspaceFolder}/{service-root}/{outDir}/**/*.js"]`) -3. ✅ For TypeScript: `tsconfig.json` has `"sourceMap": true` — without it, breakpoints in `.ts` files appear as gray (unverified) dots -4. ✅ For TypeScript: watch task exists in `tasks.json` with `$tsc-watch` problem matcher -5. ✅ For TypeScript: build chain follows install → clean → watch dependency order - -### Live Validation Checks - -These checks run during Phase 3 validation ([validation.md](../validation.md) Step 7), after the ready signal is observed: - -1. ✅ For TypeScript: verify `tsconfig.json` has `"sourceMap": true` in `compilerOptions`. If missing, add `"sourceMap": true` and re-run the build task before marking the config ✅ - -> The Node Inspector debug port (`9229`) is handled automatically by the Functions host or `--inspect` flag. - -> Project-type-specific checks (e.g., `{service-id}: func host start` task, connection strings) are defined in `project-types/{type}.md`. diff --git a/.github/agents/azure-debug-generate/references/validation.md b/.github/agents/azure-debug-generate/references/validation.md deleted file mode 100644 index b7d8819..0000000 --- a/.github/agents/azure-debug-generate/references/validation.md +++ /dev/null @@ -1,139 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Validation - -Verify that the generated VS Code debug configuration actually works. This phase runs after all artifacts are generated (Phase 2) and before the closing message. - -> ⛔ **MANDATORY.** You MUST execute every step in this file for each launch configuration. Do NOT skip, assume, or approximate results. Do NOT proceed to the closing message until every checklist entry has a real ✅ or ❌ result. - -> **Compose command comes from the plan.** Every `docker compose …` invocation below is a stand-in for the plan's Orchestrator **Compose Command** — use `docker compose` by default, or `podman compose` when the plan selected Podman. The commands (`up -d`, `ps`, `logs`, `down`) are identical across both engines. - -> ⛔ **Do NOT switch the container runtime to make validation pass.** If an emulator fails to start, a port doesn't reach the host, a volume/permission error occurs, or a health check never passes on the plan's selected engine, **STOP and surface it to the user** (per [preflight.md § Container Runtime Readiness Check](preflight.md)) — offer the fix first, and only switch engines if the user explicitly chooses to. Silently rewriting the Orchestrator/tasks to the other engine and re-validating is a failure even if the app then works. - ---- - -## Validation Algorithm - -Steps 1–8 apply to each **non-compound** launch configuration in `.vscode/launch.json`; Step 9 then validates each **compound** configuration. - -For each **non-compound** launch configuration in `.vscode/launch.json`: - -### Step 1: Resolve the Task Chain - -- Read the config's `preLaunchTask` value -- Trace the full `dependsOn` chain in `.vscode/tasks.json` to resolve the dependency order - -### Step 2: Verify Script Dependencies - -- For each task in the resolved chain, verify that its command can actually execute: - - **Package scripts** (e.g., `npm run clean`, `dotnet build`) — Confirm a matching script entry or build target exists in the project - - **CLI tool invocations** (e.g., `rimraf`, `concurrently`) — Confirm the tool is installed as a project dependency - - If a dependency is missing, add it as a project dev dependency before proceeding (see [generate.md § Dependency Availability](generate.md)) - -### Step 3: Start Services - -- Run prerequisite tasks first (install, clean, emulators), then start the `preLaunchTask` itself as a background process - -### Step 4: Verify Emulators - -- If a `docker-compose.yml` was generated, verify all services started correctly after `docker compose up -d` (or `podman compose up -d`): - - **Long-running services** (database emulators, Azurite) → should be running and healthy - - **One-shot services** (e.g., `db-migrate`) → should have exited with code 0 - - Use `docker compose ps` and `docker compose logs ` (or the `podman compose` equivalents) to check - - If any service failed, diagnose the issue, fix the configuration, and re-run until all services are healthy or exited cleanly - - Only mark the config ❌ after exhausting reasonable fix attempts - -### Step 5: Confirm Ready Signal - -- Watch stdout from the **top-level task** for the ready signal. Look up the expected pattern from `project-types/{type}.md` § Validation Signals § Ready Signal. - -### Step 6: Confirm HTTP Reachability - -- After the ready signal, confirm with `curl` using the **application HTTP port** (not the debug port). Look up the expected URL and status from `project-types/{type}.md` § Validation Signals § HTTP Verification. - -> Use the curl template: `curl -s -o /dev/null -w "%{http_code}" ` - -> **HTTP verification not applicable:** If the project type's HTTP Verification table says "N/A" or no anonymous/public endpoint is available (e.g., all routes require auth keys), skip HTTP verification. The config can still pass (✅) based on the ready signal alone — note "HTTP verification skipped: {reason}" in the checklist entry. - -### Step 7: Per-Debugger-Type Checks - -- Run additional checks based on the `type` field in the launch configuration. See the [Per-Runtime Validation Checks](#per-runtime-validation-checks) section below. If no additional checks are listed for the debugger type, skip this step. - -### Step 8: Cleanup - -- Tear down **every** process this config started — not just the last one. Kill the top-level `preLaunchTask` process **and every task in its resolved `dependsOn` chain** (dev servers, `func host`, watchers, and any emulators started for this config), then confirm the app HTTP port and the debug port are released (e.g. `lsof -i :` returns nothing) before moving to the next config. A lingering process here causes the next config — or the compound — to hit "port already in use". - -### Step 9: Validate the Compound Configuration - -For each **compound** launch configuration, faithfully run its orchestration — do NOT infer the result from the individual configs. - -> ⛔ **Do NOT skip running the compound.** Configs that each pass standalone can still fail when launched together: overlapping or duplicated `dependsOn` chains can start a service more than once, or a second invocation can grab an already-bound port. The user must never see this. You MUST run the compound's orchestration and observe the real result. - -- **Confirm the startup graph is deduplicated.** Before running, verify the compound satisfies every rule in [multi-service.md § Deduplicated Startup Graph](multi-service.md) — the compound's effective task graph must start each service exactly once. If any rule is violated, fix the generated config before running; do not validate a graph that can duplicate a service. - -- **Run the compound orchestration.** Execute the compound's `preLaunchTask` (the sequenced compound task chain) exactly as VS Code would — run its `dependsOn` members in `dependsOrder: "sequence"`. This is the same terminal-driven orchestration used for the individual configs. - -- **Assert each service starts exactly once.** Watch the task output: each service's top-level task must produce exactly one running instance. When an individual config's `preLaunchTask` fires again for an already-running service, that duplicate invocation MUST be a silent no-op (via `instancePolicy: "silent"`) — never a second process. If any service starts a second instance or grabs an already-bound port, that is considered a fail and should be fixed before proceeding. - -- **Assert readiness and HTTP reachability per service.** For each member service, confirm its ready signal (as in Step 5) and HTTP reachability (as in Step 6), reusing the same `project-types/{type}.md § Validation Signals` lookups the per-config algorithm uses. Every service must reach its ready signal; every service with an HTTP endpoint must return the expected status via `curl`. - -- **Record the result.** Mark the compound ✅ only after **each service started exactly once AND reached its ready signal AND (where applicable) passed HTTP reachability**. Otherwise mark it ❌ with the duplicate/failure evidence. - -> **Known limitation:** As with individual configs, the agent validates task orchestration and readiness through the terminal — it does not attach VS Code debuggers to the compound's member configs. Keep the assertion focused on **started exactly once + ready + reachable**. - -- **Tear down the compound.** Stop every process the compound started (all member configs and their full task chains) and confirm their ports are released, exactly as in Step 8. This feeds into the final teardown sweep below. - ---- - -## Validation Signal Lookup - -Ready signals and HTTP verification targets are defined in each project-type reference file under `§ Validation Signals`. Load the project-type file for the service being validated and read its signal tables. - -| Information | Where to find it | -|-------------|-----------------| -| Ready signal (stdout pattern) | `project-types/{type}.md` § Validation Signals § Ready Signal | -| HTTP verification (curl target, expected status) | `project-types/{type}.md` § Validation Signals § HTTP Verification | -| Debugger-specific checks (processName, etc.) | `runtimes/{rt}.md` § Checklist — Live Validation Checks | -| Runtime-specific details (debug port, outFiles) | `runtimes/{rt}.md` § Debugger Properties | - -> **Path resolution:** Some project types use subdirectories — see [generate.md § Project Type Path Resolution](generate.md) for the lookup table. - ---- - -## Per-Runtime Validation Checks - -Additional runtime-specific checks beyond the generic algorithm. These run after the ready signal and HTTP verification. - -> ⛔ You **MUST** load and execute the runtime's live validation checks. Do NOT skip this step or assume the checks pass. - -- Load `runtimes/{rt}.md` § Checklist and execute every item under **Live Validation Checks**. Each runtime's checklist contains debugger-specific verifications (e.g., source map verification for Node.js, process attachment verification for .NET). - ---- - -## Final Teardown — Free All Ports Before Handing Back - -After all individual configs **and** every compound have been validated, and **before** the Plan Integration / status write, sweep and stop everything validation spun up: - -1. **Stop every lingering background process** you started during validation — every dev server, `func host`, watcher, and task/language process, across all configs and the compound. Nothing you launched may still be running. -2. **Stop every emulator you started.** If validation ran `docker compose up` (or `podman compose up`), run the matching `docker compose down` / `podman compose down` (or stop the specific services you started). Do not leave Azurite, database emulators, or any compose service running. -3. **Verify the ports are free again.** Confirm that every port the generated configs will use is released — application HTTP ports, debug ports, and emulator ports. Use `lsof -i :` (and the plan's `docker compose ps` / `podman compose ps` for emulators); each must show nothing bound. If any port is still held, find and stop the owning process before finishing. - -> A subsequent user **F5** must start from a completely clean slate. Do NOT proceed to the closing message or set status to `Implemented` while any validation-spawned process or emulator is still running, or while any of these ports is still bound. - ---- - -## Plan Integration - -After validating all configurations, **create or update** the `## Debug Configuration Checklist` section in `.azure/vscode-debug-plan.md`. If the section does not exist, add it at the end of the plan before closing. - -``` -## Debug Configuration Checklist - -Debug Configuration Checklist: -✅ — -✅ — -✅ — each service started once + ready + curl result -``` - -One line per config (non-compound and compound). For a **non-compound** config, ✅ requires the ready signal observed AND curl confirmed (or curl skipped with a valid reason). For a **compound** config, ✅ requires the real compound test from Step 9 — each member service started **exactly once** AND reached its ready signal AND (where applicable) passed HTTP reachability — never an inferred pass from the individual results. diff --git a/.github/agents/azure-debug-plan/.metadata.json b/.github/agents/azure-debug-plan/.metadata.json deleted file mode 100644 index ee89de6..0000000 --- a/.github/agents/azure-debug-plan/.metadata.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "azure-debug-plan", - "version": "0.0.1" -} diff --git a/.github/agents/azure-debug-plan/instructions.md b/.github/agents/azure-debug-plan/instructions.md deleted file mode 100644 index 907f4fb..0000000 --- a/.github/agents/azure-debug-plan/instructions.md +++ /dev/null @@ -1,68 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Azure Debug Plan - -> **AUTHORITATIVE GUIDANCE — MANDATORY COMPLIANCE** -> -> This document is the **official, canonical source** for classifying a workspace -> and generating a VS Code debug setup plan. You **MUST** follow these instructions -> exactly as written. When in doubt, defer to this document. Do not improvise, -> infer, or substitute steps. - ---- - -## Global Rules (NO EXCEPTIONS) - -1. **Update plan progressively** — Mark steps complete as you go; update **Last Updated** timestamp on every status change -2. ❌ **Destructive actions require `ask_user`** — Always confirm before overwriting, deleting, or modifying existing files -3. **Preserve existing config** — Never silently overwrite project configuration files or `docker-compose.yml`. Merge or ask first. -4. **Scope — VS Code debug setup only** — This instruction set classifies the workspace and generates a plan. Cloud architecture, IaC generation, provisioning, and deployment are handled by the **azure-deploy** agent through the complete **azure-app-onboard** pipeline. ---- - -## Autopilot mode (overrides the approval STOP) -**Active when** the invoking chat query begins with `[AUTOPILOT MODE]`, **or** `.azure/project-plan.md` records `executionMode: auto` or equivalent. When active, run fully unattended: -- **Phase 0–1 still run in full**. When generating the plan `.azure/vscode-debug-plan.md`, also record `executionMode: auto` or similar. Follow any specific instructions per the template. -- Skip the step that instructs to run `openLocalPlanView` and do NOT wait for approval. Set status straight to `Approved`, then invoke `azure-debug-generate` as you normally would, but with the chat arg prefixed with `[AUTOPILOT MODE] `. -- **Reuse the project-plan prerequisites.** When `.azure/project-plan.md` already lists Prerequisites, carry them over wholesale instead of re-deriving them — see [inventory.md § Step 1](references/inventory.md) for the detail. -- Never call `ask_user` for non-destructive steps. Scan completeness is unchanged — autopilot suppresses **the preview and approval gates only**. - -## Workflow - -> **Two phases — Classify → Plan — then STOP.** -> -> Do NOT generate any configuration files (docker-compose, launch.json, tasks.json, etc.). -> All `.azure/` artifacts must be created inside the **workspace root**, not a session-state folder. - ---- - -## Phase 0: Classify - -Scan the workspace for service roots. Produce a `services[]` list. - -| Action | Reference | -|--------|-----------| -| Check for `.azure/project-plan.md` — if found, read it for advisory context. | — | -| Scan all subdirectories; detect project type + runtime per service root | [classify.md](references/classify.md) | -| If 2+ service roots: assign service IDs, deduplicate emulators, plan compound debug config | [multi-service.md](references/multi-service.md) | - ---- - -## Phase 1: Plan - -Scan dependencies, detect configuration, and generate the plan directly. The user reviews and edits the plan markdown before approving. - -| # | Action | Reference | -|---|--------|-----------| -| 1 | **Detect prerequisites** — Check required tools and VS Code extensions | [inventory.md](references/inventory.md) § Step 1 | -| 2 | **Scan Azure dependencies** — For each service, scan bindings (Functions) or SDK packages (other types) to identify Azure service dependencies | [inventory.md](references/inventory.md) § Step 2 | -| 3 | **Map dependencies to emulators** — Map each detected Azure dependency to its local emulator. Deduplicate across services. | [inventory.md](references/inventory.md) § Step 2 | -| 4 | **Detect container runtime & Compose provider** — Scan for an existing `docker-compose.yml`/`compose.yaml` and detect the available container runtime per [prerequisites.md § Container runtime detection](../../shared-references/prerequisites.md). Record the chosen runtime (**Docker**, **Podman**, or **Podman (Docker-compatible)**) and its Compose command in the plan's Orchestrator table. **Prefer the Podman engine when it's ready** — native `podman compose`, or `docker compose` when the Docker CLI is backed by Podman; fall back to Docker, and default to Docker Compose only when neither is confirmed. | [prerequisites.md](../../shared-references/prerequisites.md) | -| 5 | **Detect migrations** — Scan for migration files, dependencies, and scripts | [migrations.md](references/migrations.md) | -| 6 | **Inventory API test opportunities** — List HTTP endpoints and triggers per service | [inventory.md](references/inventory.md) § Step 3 | -| 7 | **Write plan** — Generate `.azure/vscode-debug-plan.md` from scan results. Fill all sections completely. | [plan-template.md](references/plan-template.md) | -| 8 | **Present plan** — Show plan to user and ask for approval. Highlight any ❓ prerequisites to double-check. The user can edit the plan directly before approving. On approval, update status to `Approved`. | `.azure/vscode-debug-plan.md` | - ---- - -> **❌ STOP HERE** — Do NOT proceed to artifact generation. Do NOT generate docker-compose.yml, launch.json, tasks.json, or any other configuration files. Present the plan to the user and wait for approval. After approval, the custom agent wrapper will invoke `azure-debug-generate` to handle generation. diff --git a/.github/agents/azure-debug-plan/references/classify.md b/.github/agents/azure-debug-plan/references/classify.md deleted file mode 100644 index 8cd1dd7..0000000 --- a/.github/agents/azure-debug-plan/references/classify.md +++ /dev/null @@ -1,33 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Classify Workspace - -Determine the project type(s) and runtime(s) for each service root in the workspace. Classification produces an array of service contexts — even single-service workspaces produce a one-item list so the rest of the flow is uniform. - -> **Always scan the full directory tree** — not just the workspace root. Service roots nested in subdirectories (e.g. `./api/`, `./web/`) must be found regardless of project layout. -> Ignore: `node_modules/`, `.git/`, `dist/`, `build/`, `bin/`, `obj/`. - -### ⚠️ Exclude Non-Service Directories - -Only include directories that represent **runnable services** (APIs, web apps, functions, workers, etc.). Exclude directories that are **shared libraries, utility packages, or common modules** — these are consumed by services but are not independently launchable and should never appear as a service. - -Common exclusion signals: -- Directory name or `package.json` name contains `shared`, `common`, `utils`, `lib`, or `helpers` -- No entry point (no `main`, `start` script, `host.json`, `server.*`, or framework config) -- Used as a dependency by other service roots (e.g. via workspace references or `file:` dependencies) -- Project type is `library` — a package that exports modules but is not runnable on its own - ---- - -## Step 1: Detect Project Types - -Scan every subdirectory and classify each service root by project type. See [project-types.md](project-types.md) for the detection table and per-type nuances. - -## Step 2: Detect Runtimes - -For each service root, determine the language/runtime and version. See [runtimes.md](runtimes.md) for the detection table and per-runtime nuances. - -## Output - -Classification produces a `services[]` list of `{ root, projectType, runtime, ... }` entries. Even single-service workspaces produce a one-item list. This information will be used in follow-up sections. diff --git a/.github/agents/azure-debug-plan/references/inventory.md b/.github/agents/azure-debug-plan/references/inventory.md deleted file mode 100644 index 3d8ce5f..0000000 --- a/.github/agents/azure-debug-plan/references/inventory.md +++ /dev/null @@ -1,53 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Inventory - -Scan the workspace to populate the plan. For multi-service workspaces, loop over each service in `services[]` from classify.md; deduplicate emulators across services per [multi-service.md](multi-service.md). - ---- - -## Step 1: Prerequisites - -### Autopilot: Reuse the Project Plan Prerequisites - -**Autopilot mode only.** In autopilot, if `.azure/project-plan.md` exists and lists Prerequisites (its `### Run` and `### Debug` groups), carry those prerequisites over wholesale into the debug plan instead of re-deriving them — the planning stage already inventoried them, and autopilot has no approval gate to catch a dropped prerequisite. Add any prerequisites the workspace scan reveals that project-plan is missing, and don't silently override a project-plan entry. Only when project-plan has no Prerequisites do you fall back to deriving them from the workspace scan below. - -In interactive (non-autopilot) mode, derive prerequisites from the workspace scan as usual; the user reviews and edits the plan before approving. - -### Deriving Prerequisites from the Workspace Scan - -Identify required tools and then inventory them by following [prerequisites.md](../../shared-references/prerequisites.md). - -The required tools are derived from a scan of the currently opened workspace project — check only the tools and extensions relevant to the detected project types, runtimes, and Azure bindings. Both tool sets defined in prerequisites.md apply here — the **Run** tools (Node.js, .NET SDK, Python, Functions Core Tools, ...) and the **Debug** tools (Docker, Docker Compose, VS Code extensions, ...) — since debugging exercises the full local stack. - -For every detected project type, include its VS Code debug-integration extension from the Debug Tools table as its own Debug row (e.g. an Azure Functions project always includes `ms-azuretools.vscode-azurefunctions`) — these extensions are required for the debug experience and are separate from the Run-group CLI/runtime tools, so never omit them. - ---- - -## Step 2: Azure Dependencies - -For each service, identify Azure service dependencies by scanning bindings or SDK packages. - -- **Functions projects:** Scan bindings per [project-types.md](project-types.md) § functions -- **Other project types:** Scan dependency files (e.g. `package.json`, `requirements.txt`, `*.csproj`) for packages that indicate an Azure service dependency - -The table below shows common SDK-to-service mappings — this is **not exhaustive**. Any package that implies connectivity to an Azure service should be mapped accordingly. - -| Example Packages | Azure Service | Emulator | -|-----------------|--------------|----------| -| `@azure/storage-blob`, `@azure/storage-queue`, `@azure/data-tables` | Azure Storage | azurite | -| `pg`, `postgres`, `@prisma/client`, `typeorm`, `sequelize`, `Npgsql`, `psycopg2` | PostgreSQL | postgresql | -| `@azure/cosmos` | Cosmos DB | cosmosdb-emulator | -| `@azure/service-bus` | Service Bus | servicebus-emulator | -| `@azure/event-hubs` | Event Hubs | eventhubs-emulator | -| `mssql`, `Microsoft.Data.SqlClient` | Azure SQL | azure-sql-edge | - -> Multiple storage bindings (blob + queue + table) consolidate to a **single** azurite entry. -> Cross-check `local.settings.json`, `.env`, and app config for existing connection references to confirm findings. - ---- - -## Step 3: API Test Collection Inventory - -For each service, identify whether it exposes testable HTTP endpoints or triggers and provide a brief summary for the plan. Inventory the implemented route and trigger registrations in the workspace, not only the route table in `.azure/project-plan.md`. The project plan intentionally omits derived authentication routes, so include implemented registration, login, and current-user endpoints when `API Login` is enabled. Detailed endpoint parsing happens during the generation phase. diff --git a/.github/agents/azure-debug-plan/references/migrations.md b/.github/agents/azure-debug-plan/references/migrations.md deleted file mode 100644 index 7134757..0000000 --- a/.github/agents/azure-debug-plan/references/migrations.md +++ /dev/null @@ -1,47 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Database Migrations — Detection - -When a database dependency is found, detect the migration tool so the plan can record it. The generation phase uses this to configure migration automation within the orchestrator. - ---- - -## Detection - -Scan three layers, then synthesize. - -### Layer 1: Migration Files - -Non-exhaustive detection pattern examples: - -| Pattern | Tool | -|---------|------| -| `prisma/migrations/` | Prisma | -| `alembic/`, `alembic.ini` | Alembic | -| `**/migrations/*.py` | Django | -| `Migrations/*.cs` | EF Core | -| `flyway.conf` | Flyway | -| `migrations/*.sql` | Raw SQL | - -### Layer 2: Dependencies - -Check dependency manifests for migration tools, ORMs with built-in migration support, and database driver packages. - -### Layer 3: Scripts - -Check script runners (`package.json`, `Makefile`, etc.) for existing migration commands (grep for common migration key words like: `migrate`, `schema`, `seed`). - -### Synthesis - -1. Cross-reference all three layers — they should agree -2. If an existing migration command exists, use it (don't invent a new one) -3. If layers conflict, ask the user which tool is active - -### Insufficient Evidence - -If a database dependency exists but no migration strategy is found across all three layers: - -1. Do not guess -2. Ask the user via `ask_user` how they manage schema changes -3. Record the gap in the plan's Migrations section with `⚠️ Not detected` diff --git a/.github/agents/azure-debug-plan/references/multi-service.md b/.github/agents/azure-debug-plan/references/multi-service.md deleted file mode 100644 index 5ae2712..0000000 --- a/.github/agents/azure-debug-plan/references/multi-service.md +++ /dev/null @@ -1,48 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Multi-Service Orchestration - -> Runs when `classify.md` finds **2+ service roots**, before inventory scanning begins. - ---- - -## Service ID Assignment - -Derive a short kebab-case ID per service root. Use project manifest name if available, otherwise fall back to directory name. - -| Runtime | Manifest Source | Field | -|---------|----------------|-------| -| `node-ts`, `node-js` | `package.json` | `"name"` | -| `dotnet` | `*.csproj` | `` or filename | -| *Other* | Service directory name | Fallback when no manifest name exists | - -If two IDs collide, append the project type (e.g. `payments-api` becomes `payments-api-functions`). - ---- - -## Emulator Deduplication - -Collect emulator lists from all services. Each emulator appears **once** in the plan's Emulators table — multiple services may depend on the same emulator. - ---- - -## Partial Configuration - -Check each service root for existing debug config before planning. - -| State | Plan Action | -|-------|------------| -| Fully configured | Skip | -| Partially configured | Generate only missing artifacts | -| Unconfigured | Full generation | - ---- - -## Compound Debug Configuration - -Required when 2+ service roots are detected (including Frontend SPAs). - -### Startup Dependencies - -If a frontend SPA has a proxy pointing to a local backend (detected via [project-types.md](project-types.md) § Backend Proxy Dependencies), record the `proxyTarget` service ID on that service entry. The compound config uses this to order startup (backends before frontends) that depend on them. diff --git a/.github/agents/azure-debug-plan/references/plan-template.md b/.github/agents/azure-debug-plan/references/plan-template.md deleted file mode 100644 index 0fc2d12..0000000 --- a/.github/agents/azure-debug-plan/references/plan-template.md +++ /dev/null @@ -1,222 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Plan Template - -> Generate `.azure/vscode-debug-plan.md` using this template. This file is the -> **single source of truth** for the generation phase. The generation phase reads this plan and -> generates all artifacts from it — no re-scanning of the workspace is needed. -> -> The plan is generated directly from the workspace scan. The user reviews the plan, -> edits it as needed, then approves it before generation proceeds. - -## ⛔ BLOCKING REQUIREMENT - -You **MUST** create this plan file and get user approval BEFORE generating any configuration files. - ---- - -## Markdown Table Integrity - -When editing markdown tables with `replace_string_in_file` or `multi_replace_string_in_file`, always **read the file back** after the edit and verify that each table row is on its own line. Markdown tables require exactly one row per line — a missing newline between `|---|` and `| data |` breaks parsing completely. - -**Post-edit verification rule:** After any edit to `.azure/vscode-debug-plan.md` that modifies a table, immediately read the affected lines back to confirm the table renders correctly (header, separator, and each data row on separate lines). If rows are concatenated, fix before proceeding. - ---- - -## ⚠️ Mermaid Label Quoting — ALWAYS Quote Every Label - -In the `## Architecture Diagram` mermaid block, **every node label and every edge label MUST be wrapped in double quotes.** This rule is unconditional — quote every label, every time, even when it looks harmless. - -```mermaid -graph LR - %% ✅ CORRECT — every label quoted - API["Commerce API
Azure Functions :7071"] -->|"@azure/storage-blob"| AZ["Azurite
:10000"] - API -->|"pg"| PG[("PostgreSQL
:5432")] - - %% ❌ WRONG — unquoted labels; the leading @ is a parse error - %% API[Commerce API
Azure Functions :7071] -->|@azure/storage-blob| AZ[Azurite] -``` - -**Why:** mermaid v11 uses `@` for edge-ID (`e1@-->`) and node-metadata (`id@{ shape: ... }`) syntax, so an `@` at the **start** of an unquoted label is lexed as a link ID and the whole diagram fails to parse with `Expecting 'AMP', 'COLON', ... got 'LINK_ID'`. Scoped npm package names (`@azure/storage-blob`, `@azure/identity`, `@prisma/client`) are the most common trigger, and they are exactly what shows up in Azure dependency edge labels. The debug-plan webview renders this block with mermaid — a parse error replaces the diagram with a raw `mermaid (error)` code block. - -Quoting is always valid in mermaid and also protects labels containing `(`, `)`, `:`, `-`, `/`, `#`, or `·`. Do **not** try to detect which labels are "risky" — just quote all of them. - ---- - -## Template - -````markdown -# Azure Debug Plan - -> This plan is the source of truth for generating the -> VS Code debug setup in this workspace. -> -> **Status:** {Planning | Approved | Executing | Implemented} -> **Execution Mode:** {Auto | Guided} -> **Created:** {ISO-8601 datetime} -> **Last Updated:** {ISO-8601 datetime} -> -> -> - ---- - -## Prerequisites - - - - - - -| Tool / Extension | Category | Service(s) | Installed | Version | -|------------------|----------|------------|-----------|---------| -| {name} | {Runtime / Package manager / …} | {service(s) or *} | {✅/❓} | {version or —} | - -> ⚠️ **Action required:** Confirm any tool or extension marked ❓ is installed and ready before approving this plan — rerun the recheck to confirm CLI tools provided by a version manager. - ---- - -## Debug Configurations - - - - - - - - - -Each checked row below produces a VS Code debug configuration in the `.vscode/launch.json`. - -| Generate | Debug Config Name | Service Label | Service Root | Project Type | Runtime | Version | Azure Dependencies | -|----------|--------------------|---------------|--------------|--------------|---------|---------|-----| -| {[x] / [ ]} | {e.g. Payments API (debug)} | {label} | {path} | {type} | {runtime} | {version} | {comma-separated azure service labels} | - - - - - - - - - -
-ℹ️ Project Type Descriptions - -| Project Type | Description | -|-------------|-------------| -| {type} | {brief description of what this project type means} | - - - - - - - -
- - - - ---- - -## Orchestrator - - - - - - - - -| Orchestrator | Container Runtime | Compose Command | Description | -|-------------|-------------------|-----------------|-------------| -| {Docker Compose / Podman Compose} | {Docker / Podman / Podman (Docker-compatible)} | {`docker compose` / `podman compose`} | {description} | - - - - - - - - ---- - -## Emulators - - - -| Dependent Service | Emulator | Purpose | -|-------------------|----------|---------| -| {azure service label} | {emulator name} | {description of what this emulator provides} | - - - - - ---- - -## Architecture Diagram - -{One sentence describing how the app connects to its dependencies during debugging.} - -```mermaid -graph LR - %% Generated from Debug Configurations + Emulators tables above. - %% Show each service as a node, each emulator as a node, - %% and edges for the Azure Dependencies that connect them. - %% MANDATORY: wrap EVERY node label and EVERY edge label in double quotes — - %% e.g. API["Commerce API
Azure Functions :7071"] -->|"@azure/storage-blob"| AZ["Azurite"] - %% An unquoted leading @ (scoped npm packages) is parsed as mermaid edge-ID - %% syntax and breaks the whole diagram. See "Mermaid Label Quoting" above. -``` - ---- - -## Migrations - - - - -When selected, the generation phase creates automated VS Code tasks that run migration scripts on launch — so emulator databases are automatically provisioned with the correct schema and seed data before the app starts debugging. No manual migration steps needed. - -| Generate | Service | Migration Tool | -|----------|---------|---------------| -| {[x] / [ ]} | {service label} | {tool name, e.g. Prisma / Knex / Drizzle / EF Core} | - ---- - -## API Test Collections - - - - -When selected, the generation phase produces lightweight, runnable API test scripts in the project so you can quickly smoke-test endpoints and triggers once everything is launched and connected locally. - -| Generate | Service | Description | -|----------|---------|-------------| -| {[x] / [ ]} | {service label} | {collapsible lists of HTTP endpoints and/or triggers — see format below} | - - - - - - - ---- - -## Convenience Scripts - - - - -| Generate | Script | Registered In | Description | -|----------|--------|---------------|-------------| -| {[x] / [ ]} | {script name} | {path to file where script is registered, e.g. ./package.json} | {what the script does} | - - - - - - diff --git a/.github/agents/azure-debug-plan/references/project-types.md b/.github/agents/azure-debug-plan/references/project-types.md deleted file mode 100644 index 52f5db7..0000000 --- a/.github/agents/azure-debug-plan/references/project-types.md +++ /dev/null @@ -1,71 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Project Types - -> These are **common examples, not an exhaustive list**. If a service root does not match -> any of these, classify it by whatever best describes its purpose (e.g. `background-worker`, `console`, etc.). - -## Detection Table - -| Detection Signals | Project Type | Include in Plan? | -|-------------------|-------------|------------------| -| `host.json` + Azure Functions SDK | **functions** | ✅ Yes | -| SPA framework in `package.json`, or `vite.config.*` / `next.config.*` / `angular.json` (no `host.json`) | **frontend-spa** | ✅ Yes | -| HTTP framework (Express, Fastify, Flask, FastAPI, ASP.NET, Spring Boot, etc.) | **app-service** | ✅ Yes | -| `Dockerfile` with a containerized application | **container-app** | ✅ Yes | -| No entry point, no framework, exports modules only (shared/common/util packages) | **library** | ❌ Exclude | - ---- - -## functions - -For Azure Functions projects, scan bindings to identify Azure service dependencies. Parse `function.json` files or decorator/attribute bindings in source code. - -> **Implicit dependency:** All Azure Functions projects require Azure Storage for the host runtime (trigger management, lease coordination, internal state). Always emit an Azurite emulator entry in the plan regardless of whether application-level storage SDK packages are detected. - -| Binding | Azure Service | Emulator | -|---------|--------------|----------| -| `blobTrigger`, `blob` | Blob Storage | azurite | -| `queueTrigger`, `queue` | Queue Storage | azurite | -| `table` | Table Storage | azurite | -| `httpTrigger` | (built-in) | — | -| `timerTrigger` | (built-in) | — | -| `warmupTrigger` | (built-in) | — | -| `durableClient`, `orchestrationTrigger`, `activityTrigger` | Durable Functions | durable-task-scheduler | -| `cosmosDBTrigger`, `cosmosDB` | Cosmos DB | cosmosdb-emulator | -| `serviceBusTrigger`, `serviceBus` | Service Bus | servicebus-emulator | -| `eventHubTrigger`, `eventHub` | Event Hubs | eventhubs-emulator | -| `eventGridTrigger`, `eventGrid` | Event Grid | — | -| `signalRTrigger`, `signalR` | SignalR Service | — | -| `sql`, `sqlTrigger` | Azure SQL | azure-sql-edge | - -> Multiple storage bindings (blob + queue + table) consolidate to a **single** azurite entry. -> This table is **not exhaustive** — map any other bindings to their Azure service accordingly. - ---- - -## frontend-spa - -Frontend SPA projects do not require emulators or Azure bindings, but they **are** service roots. When a frontend is detected alongside a backend, the workspace is multi-service and **must** produce a compound debug configuration. - -### Framework Detection - -| Framework | Detection Signals | -|-----------|-------------------| -| Vite | `vite.config.*` or `vite` in devDependencies | -| Next.js | `next.config.*` or `next` in dependencies | -| Angular | `angular.json` | -| Create React App | `react-scripts` in dependencies | -| Blazor WASM | `*.razor` + `WebAssembly` SDK in `*.csproj` | - -### Backend Proxy Dependencies - -If a proxy config points to a local backend, record the dependency so the compound debug configuration can order startup (backends before frontends). - -| Framework | Proxy Config Location | -|-----------|----------------------| -| Vite | `server.proxy` in `vite.config.*` | -| Create React App | `"proxy"` in `package.json` | -| Angular | `proxy.conf.json` | -| Next.js | `rewrites()` in `next.config.*` | diff --git a/.github/agents/azure-debug-plan/references/runtimes.md b/.github/agents/azure-debug-plan/references/runtimes.md deleted file mode 100644 index 31d531a..0000000 --- a/.github/agents/azure-debug-plan/references/runtimes.md +++ /dev/null @@ -1,33 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Runtimes - -> These are **common examples, not an exhaustive list**. If a service root uses a runtime -> not listed here, identify it by the language and toolchain present (e.g. `rust`, `ruby`, etc.). - -## Detection Table - -| Detection Signals | Runtime | Version Source | -|-------------------|---------|---------------| -| `package.json` + `tsconfig.json` | **node-ts** | `engines.node` / `.nvmrc` / `.node-version` | -| `package.json` (no `tsconfig.json`) | **node-js** | Same | -| `*.csproj` | **dotnet** | `` element (e.g. `net8.0` → `8.0`) | -| `requirements.txt`, `pyproject.toml`, or `Pipfile` | **python** | `.python-version` / `pyproject.toml` `requires-python` | -| `pom.xml` or `build.gradle` | **java** | `` / `sourceCompatibility` | -| `go.mod` | **go** | `go` directive in `go.mod` | - ---- - -## dotnet - -### Version Detection - -Read `` from the `.csproj` to determine the runtime version (e.g. `net8.0` → `8.0`). - -### Assembly Name - -Derive the assembly name for the plan's Service Label: - -1. If `` is set → use that value -2. Otherwise → `.csproj` filename without extension (e.g. `Functions.csproj` → `Functions`) diff --git a/.github/agents/azure-deploy/.metadata.json b/.github/agents/azure-deploy/.metadata.json deleted file mode 100644 index 3f2c84f..0000000 --- a/.github/agents/azure-deploy/.metadata.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "azure-deploy", - "version": "0.0.1" -} diff --git a/.github/agents/azure-deploy/cor-references/migration-access.md b/.github/agents/azure-deploy/cor-references/migration-access.md deleted file mode 100644 index 18acdd5..0000000 --- a/.github/agents/azure-deploy/cor-references/migration-access.md +++ /dev/null @@ -1,131 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Migration database access — tier ladder - -How to reach the provisioned database to run migrations. **Work down the tiers in order.** -Record the tier you used in `deploy-result.json`. - -The generated IaC already grants the *deployed app* database access via -`AllowAllAzureServicesAndResourcesWithinAzureIps` (`0.0.0.0`, consented at the Scaffold Gate). -That rule allows **Azure services**, not arbitrary public IPs — so compute already running inside -Azure can reach the database and a developer laptop cannot. This is why tiers 1 and 2 need no -network change and tier 3 does. - -## Tier 1 — Exec inside the deployed app (default) - -The migration tool, its dependencies, and the connection string already exist in the running app. -Nothing to install, no secret to move, no network change. - -**Container Apps** - -```powershell -az containerapp exec -n {ca} -g {rg} --subscription {sub} --command "{migration_command}" -# If the migration tool lives outside WORKDIR: -az containerapp exec -n {ca} -g {rg} --subscription {sub} --command "cd /app/backend && alembic upgrade head" -``` - -**App Service (Linux only)** - -```powershell -az webapp ssh -n {app} -g {rg} --subscription {sub} -``` - -Discover `{migration_command}` from the table in -[`deploy/references/database-post-deploy.md`](../deploy/references/database-post-deploy.md). - -**Move to tier 2 when:** - -- The image is a slim/multi-stage build that dropped the migration CLI (`alembic`, `prisma`, - `dotnet-ef`, `sequelize-cli` not on `PATH`). -- Container Apps has scaled to zero and there is no replica to exec into. -- App Service on **Windows** (`az webapp ssh` is Linux-only). -- `exec` cannot return a usable exit code or output for verification. - -> ⛔ `az containerapp exec` is TTY-oriented. Capture the output explicitly and verify the schema -> state afterward — a zero exit code from the shell is **not** proof the migration applied. - -## Tier 2 — One-shot job in the same environment - -Runs inside the same Container Apps environment (and therefore the same VNet), so it inherits the -app's database connectivity with **no firewall change**. This is the correct choice when the app -image lacks migration tooling or cannot be exec'd into. - -Use a job image that contains the migration tool (commonly the app's *build* stage rather than its -runtime stage), the same connection configuration as the app, and a `Never` restart policy. Delete -the job once it completes, and record its creation in `deploy-result.json` so it is not mistaken -for an orphaned resource by `capture_deployment_inventory`. - -**Move to tier 3 only when:** there is no Azure-side compute in the database's network at all (for -example a database provisioned without an accompanying app service), or the job cannot be created. - -## Tier 3 — Temporary single-IP firewall rule (last resort) - -⛔ **Preconditions — all must hold. If any fails, do not proceed; fall back to tier 2 or fail the -deploy.** - -1. Tiers 1 and 2 are genuinely impossible. State why in `deploy-result.json`. -2. The server's `publicNetworkAccess` is already **Enabled**. If it is Disabled, or the server is - reachable only through a private endpoint, **stop** — do not enable public access. -3. The migration can run with the developer's own Entra credentials. Do **not** copy the admin - password to the local machine to satisfy this tier. - -**Use the extension's tools.** They scope the rule to a single IP and guarantee its removal even -if this session dies — which prompt instructions cannot: - -- `open_database_migration_access` → records a lease, then creates `cor-tempmigration-{sessionId}-{expiry}` -- `close_database_migration_access` → deletes that rule and clears the lease - -The lease is written **before** the rule is created, and the extension reconciles outstanding -leases **on its next activation**. So if this session crashes, is compacted, or the window closes -mid-migration, the rule is removed when the workspace is reopened rather than surviving unnoticed. - -> ⛔ Two things these tools do **not** do, so do not tell the user otherwise. They do **not** -> snapshot or compare the server's full firewall rule list — they only ever create and delete their -> own uniquely-named rule, and never read, modify, or remove any other rule. And cleanup on a crash -> happens at the **next activation**, not instantly. If `close_database_migration_access` reports -> that it could not remove the rule, the rule is still there until then: say exactly that, name the -> rule, and fail the deploy. - -`open_database_migration_access` refuses rather than guessing when it cannot confirm the server's -network posture, or when ten exceptions are already outstanding in the workspace. A refusal is not -a failure to route around — fall back to tier 2, or fail the deploy. - -Both are `copilot-azure-resources-extension-tools/*` tools. If they are not in your active tool -list, load them with `tool_search` → `activate_tools` as described in the agent's MCP tools -section — do **not** treat them as unavailable and fall back to `az`. - -**Only if those tools genuinely cannot be loaded**, use `az` — and obey every rule below. Note that -this path has no crash safety at all: nothing records what you changed, so restoring it is entirely -your responsibility. - -- **Record the baseline first**, before any change: - `az postgres flexible-server firewall-rule list`, `az mysql flexible-server firewall-rule list`, - or `az sql server firewall-rule list`. Write it into the session directory. -- **Add only a single-IP rule** (`startIpAddress` equal to `endIpAddress`) for the current client. -- **Never** widen to `0.0.0.0`–`255.255.255.255`, **never** disable enforcement, **never** delete - or modify a pre-existing rule, **never** touch VNet rules or `publicNetworkAccess`. -- **Restore on every path** — success, failure, timeout, or user abort — so the firewall matches - the recorded baseline exactly. -- **If restoration fails**, treat it as a deploy failure, name the exact rule that remains, and do - not report a clean deployment. - -### Determining the client IP - -Do **not** call a third-party IP echo service. Let Azure tell you: attempt the connection and read -the refusal, which contains the egress IP as Azure sees it — the only value the rule needs. - -```text -Azure SQL: Client with IP address 'x.x.x.x' is not allowed to access the server. -PostgreSQL: no pg_hba.conf entry for host "x.x.x.x" -``` - -Confirm the detected IP with the user before creating the rule. - -## Verification (all tiers) - -A green HTTP status is not proof. Confirm the migration tool reports up to date **and** that the -expected tables exist, then re-run the health check so the app exercises the migrated schema. - -If migrations cannot be completed, write the failure into `deploy-result.json` and treat the deploy -as failed rather than reporting success over an un-migrated database. diff --git a/.github/agents/azure-deploy/deploy/instructions.md b/.github/agents/azure-deploy/deploy/instructions.md deleted file mode 100644 index 6efe440..0000000 --- a/.github/agents/azure-deploy/deploy/instructions.md +++ /dev/null @@ -1,65 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Deploy — IaC Execution & Health Verification - -> ⛔ **Read [`references/blocked-patterns.md`](references/blocked-patterns.md) before your first resource-creating command.** -> -> The trigger is **any** command that can create Azure resources — `az … create`, `az … up`, `azd up`, `azd provision`, `terraform apply` — not only `az deployment sub create`. Keying it to the declarative command alone made the rule unreachable on the path that most needs it: an agent that decided to provision imperatively never ran `az deployment sub create`, so it never reached the file explaining that imperative provisioning is forbidden. -> -> **This phase deploys a template. It does not create resources by hand.** If `infra/` holds no template, you are not ready to deploy — return to scaffold and generate one. Reaching for `az containerapp up` because the template is failing is the one fallback that is never available. - -## Quick Reference - -| Property | Value | -|----------|-------| -| Best for | Executing validated IaC against Azure, health-checking deployed resources | -| Inputs | `prepare-plan.json` + `scaffold-manifest.json` from `.copilot-azure/sessions/{id}/` | -| Outputs | `deploy-result.json` written to session directory | -| Parent | [azure-app-onboard](../instructions.md) | - -## When to Use This Agent - -Invoked by the `azure-app-onboard` orchestrator at Phase 4 when `scaffold-manifest.json` exists with `files[]` and `validationResult`. Not directly user-routable. - -> **Return to orchestrator:** When complete, return control to `azure-app-onboard` for handoff (Step 10). Do NOT start new phases. - -## When NOT to Use - -| Scenario | Use Instead | -|----------|-------------| -| Plan architecture, map services, estimate costs | [prepare](../prepare/instructions.md) | -| Generate IaC files from a plan | `azure-app-onboard` Step 7 (scaffold) | -| Run `azd up` or execute existing deployment templates | `azure-deploy` | -| Debug a running app after deployment | `azure-diagnostics` | -| Optimize existing Azure spending | `azure-cost` | - -## Workflow - -> ⛔ **Sub-agent delegation is MANDATORY for Step 0.** Read `subagent-preflight.md`, then dispatch as a `task` with the **COMPLETE and UNMODIFIED** template text between `<<>>` / `<<>>` delimiters. Do NOT summarize or rewrite the template — the sub-agent needs every "Read [file]" instruction to produce a correct `deploy-checklist.md`. Append session artifact data AFTER the template block. If your next action after reading the template is anything other than `task`, you are executing it inline instead of delegating. - -> ⛔ **Healing loop:** ask user after 3 attempts, then every 5 (counter = `healingAttempts[].length`). - -> ⛔ **Region lock:** Before `az deployment` retry, compare `--location` against `prepare-plan.json.deploymentVariables.location`. If changed → re-approval gate required. Update plan after approval. - -> ⛔ **After compaction or any `az deployment`/`az webapp deploy`/`az acr build`/failed health check: re-read `deploy-checklist.md`.** If missing → fill from [`deploy-checklist-template.md`](references/deploy-checklist-template.md). On significant context loss: also re-read this instructions.md. - -> ⛔ **Deterministic resource inventory (MANDATORY — do NOT rely on memory for what was created).** The `capture_deployment_inventory` MCP tool records exactly which Azure resources this session created by diffing a `resources.list()` snapshot taken **before** the first deployment against one taken **after** each attempt. Call it: -> - **Step 5b** with `phase: "baseline"` — BEFORE the first `az deployment` (once). -> - **Every time the user resumes this deployment** with `phase: "baseline"` — AFTER the user chooses Resume and the subscription is resolved, but BEFORE any further command that can provision resources. Preserve the inventory already in `deploy-result.json`; subsequent captures cover only the resumed segment and must be unioned into the preserved `createdResources[]`/`orphanedResourceGroups[]` by normalized resource ID/case-insensitive RG name. -> - **After EVERY `az deployment` attempt** (success, failure, or healing retry) with `phase: "capture"`, passing `expectedResourceGroup`, all `deploymentNames[]`, and every `resourceGroups[]` touched (including abandoned healing RGs). -> - **On the Step 9 failure path** before returning `status: "failed"`. -> It holds the baseline snapshot **in memory** (no files are written to the workspace) and, on `phase: "capture"`, returns the created resources classified as `expected`/`failed`/`orphaned`/`unverified` plus the orphaned resource groups. Write that output into `deploy-result.json.createdResources[]` and `orphanedResourceGroups[]` — the durable record. ⛔ **Build ready-to-run cleanup commands from `failed` entries only.** `orphaned` means "appeared during the deploy window but no deployment claimed it" — it may be a healing stray, or it may belong to someone else on a shared subscription, so present it as "review before deleting" with no delete command. If the tool returns `inventoryUnverified`, present no cleanup list at all (see [`references/mcp-tools.md`](references/mcp-tools.md)). After a resume, merge the new segment into the durable record rather than replacing prior entries. The tool never deletes anything and never writes to disk; it only reports. - -| # | Step | Action | Artifact | Reference | -|---|------|--------|----------|-----------| -| 0 | **Dispatch preflight sub-agent** | ⛔ **You MUST dispatch [`subagent-preflight.md`](references/subagent-preflight.md) as a `task`.** ⛔ agent_type: `"task"` — NEVER `"general-purpose"`. Read the template, then your NEXT action MUST be `task`. If after reading the template your next action is `powershell`, `view`, or anything other than `task`, STOP — you are executing inline instead of delegating. Writes `deploy-checklist.md`. **`view` it immediately after return.** | `deploy-checklist.md` | ⛔ **You MUST read [`subagent-preflight.md`](references/subagent-preflight.md)** | -| 1 | **Read upstream artifacts** | Load `prepare-plan.json` + `scaffold-manifest.json`. Check `validationResult`. Resolve subscription + deployment variables. | — | — | -| 3 | **Preflight checks** | Auth, **mandatory what-if preview**, RBAC, RG per `deploy-checklist.md` § Preflight. | — | ⛔ **You MUST read `deploy-checklist.md`** (re-read if compaction occurred) | -| 4 | **Deploy approval gate** | Present cost + resource summary per `deploy-checklist.md` § Deploy approval gate format. | — | — | -| 5b | **Write deploy-result.json skeleton + baseline inventory** | ⛔ Read [`deploy-schemas.ts`](references/deploy-schemas.ts), write skeleton (`status: "in-progress"`). Must exist BEFORE first `az` command. ⛔ **Then call `capture_deployment_inventory` with `phase: "baseline"`** (passing `sessionId`, `subscriptionId`) BEFORE the first `az deployment` — this snapshots pre-existing resources (held in memory) so the post-deploy diff is accurate. | `deploy-result.json` | ⛔ **You MUST read [`deploy-schemas.ts`](references/deploy-schemas.ts)** | -| 6 | **Execute deployment** | ⛔ **BEFORE `az deployment sub create`:** Generate portal link — `$dn="{deploymentName}"; $r="/subscriptions/{subId}/providers/Microsoft.Resources/deployments/$dn"; $l="https://portal.azure.com/#view/Microsoft_Azure_Resources/DeploymentDetails.MenuView/~/overview/id/$($r.Replace('/','%2F'))"; Write-Output "LINK=$l"`. ⛔ **Do NOT auto-open the link** — never call `Start-Process` (or any browser launcher) on it. Print the bare URL in chat (ctrl-clickable) so the user can open it if they choose.
Auto-generate ALL app-internal `@secure()` params (`openssl rand -base64 32 \| tr -d '/+='`), NEVER `ask_user` for secrets (databases use managed identity — no passwords); on retry reuse from `deploy-secrets.env` — NEVER regenerate (see deploy-safety.md § Deploy Checklist). THEN deploy IaC. ⛔ **After the deployment command returns (success OR failure), call `capture_deployment_inventory` with `phase: "capture"`** (`expectedResourceGroup`, all `deploymentNames[]`, every `resourceGroups[]` used) to record what was actually created and surface any orphans early. | — | ⛔ **You MUST read `deploy-checklist.md`** § Execute deployment | -| 6b | **Deploy application code** | ⛔ Deploy code for EVERY service in `prepare-plan.json.services[]`. Follow `deploy-checklist.md` § Code deploy. | — | ⛔ **You MUST read `deploy-checklist.md`** § Code deploy | -| 7 | **Health-check + SCM re-disable** | HTTP GET per endpoint (max 3 iterations). ⛔ **Multi-service apps:** Also inspect the response body for error patterns (`connection refused`, `MODULE_NOT_FOUND`, `localhost`, `SET-IN-DEPLOY-PHASE`) — HTTP 200 alone does not mean functional when the app depends on another service or KV secrets. Then ⛔ for EVERY App Service and Premium/EP1 Functions app — but ⛔ **NOT** Flex Consumption function apps, which have no SCM basic-auth policy (see [code-deployment-functions-flex.md](references/code-deployment-functions-flex.md)) — run BOTH commands, no exceptions: `az rest --method put --url "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/sites/{app}/basicPublishingCredentialsPolicies/scm?api-version=2023-12-01" --headers "Content-Type=application/json" --body '{"properties":{"allow":false}}'` then verify: `az rest --method get --url "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/sites/{app}/basicPublishingCredentialsPolicies/scm?api-version=2023-12-01" --query properties.allow -o tsv` (must return `false`). | `deploy-result.json` full | ⛔ **You MUST read `deploy-checklist.md`** § Health check | -| 8 | **Finalize artifacts** | ⛔ Read [`deploy-schemas.ts`](references/deploy-schemas.ts). ⛔ Re-read `deploy-checklist.md` § Artifact verification — follow ALL 5 checks. ⛔ **Run a final `capture_deployment_inventory` (`phase: "capture"`) and populate `deploy-result.json.createdResources[]` + `orphanedResourceGroups[]` FROM its returned output** — do NOT hand-author these fields. ⛔ **No "live"/handoff message until you overwrite the skeleton `deploy-result.json`** — flip `status` off `"in-progress"` (→ `succeeded`/`failed`) and fill healthStatus, endpoints, completedUtc, deploymentNames, healingAttempts. Write `deployment-summary.md` (status table + health + portal link(s) + cleanup commands — same content as your handoff message). Update `context.json` — add `"deploy"` to `completedPhases`, `currentPhase: null`, `lastModifiedUtc`. Read back to confirm `status != "in-progress"` and `"deploy"` ∈ `completedPhases`. ⛔ **Then STOP — return to orchestrator. No further CLI commands.** | `deploy-result.json` final + `deployment-summary.md` + `context.json` update | ⛔ **You MUST read [`deploy-schemas.ts`](references/deploy-schemas.ts)** + ⛔ **Re-read `deploy-checklist.md` § Artifact verification** | -| 9 | **Error handling + healing** | ⛔ **Only if Steps 6/6b/7 returned nonzero exit code or health check failed.** Skip entirely on clean deploys. Classify errors, healing loop, PLAN_LEVEL_CHANGE re-approval per `deploy-checklist.md` § During healing. ⛔ **Even on unrecoverable failure:** first run `capture_deployment_inventory` (`phase: "capture"`) to record what was partially created, then write `deploy-result.json` with `status: "failed"`, `errorDetails`, and `createdResources[]`/`orphanedResourceGroups[]` from its returned output, and surface cleanup commands built from its `failed` entries only (list `orphaned` entries separately as "review before deleting", with no delete command) — before returning to orchestrator. `deploy-result.json` and the cleanup report must ALWAYS exist, especially on failure. | — | ⛔ **You MUST read [`error-classification.md`](references/error-classification.md)** | diff --git a/.github/agents/azure-deploy/deploy/references/approval-gate-template.md b/.github/agents/azure-deploy/deploy/references/approval-gate-template.md deleted file mode 100644 index 7d61252..0000000 --- a/.github/agents/azure-deploy/deploy/references/approval-gate-template.md +++ /dev/null @@ -1,59 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Approval Gate Template - -Display template for the deploy approval gate. Present after preflight validation, before execution. - -## Display Format - -``` -## Deploy Approval - -🏢 **Subscription:** {context.json.azure.subscriptionName} (`{context.json.azure.subscriptionId}`) -📁 **Resource Group:** {context.json.azure.resourceGroup} -🌍 **Region:** {context.json.azure.region} - -**Services:** -| Service | SKU | Region | Resource Name | -|---------|-----|--------|---------------| -{for each service in prepare-plan.json.services[]} - -**💰 Estimated Monthly Cost:** ${costEstimate.monthlyUsd}/month -| Service | SKU | Monthly | -|---------|-----|---------| -{for each item in costEstimate.breakdown[]} -{costEstimate.disclaimer} - -**🔒 Validation:** {scaffold-manifest.json.validationResult.status} -{if FLAGGED findings from scaffold self-review → ⚠️ list each} - -**📋 What-if preview:** {N} resources to create, {N} to modify, {N} unchanged -{if any Delete operations → ⚠️ list each deleted resource with name + type} - -**Generated Files:** -{for each file in scaffold-manifest.json.files[]} - -**📦 Deployment Summary:** -{Display the "What's Being Deployed" table here, built from prepare-plan.json.services[] — showing which components map to which Azure services, SKUs, and estimated costs. (deployment-summary.md is not written until the deploy/handoff phase)} - ---- -**🚀 Ready to deploy? (Yes / Run manually / Edit plan / Cancel)** -``` - -## Response Handlers - -| Response | Action | -|----------|--------| -| **Yes** | Execute deployment (Step 6 of deploy workflow). Do NOT re-confirm. | -| **Run manually** | Show exact CLI commands based on `iacFormat`:
**Bicep (subscription-scope, default):** `az deployment sub create --subscription {subscriptionId} --location {location} --template-file infra/main.bicep --parameters @infra/main.parameters.json --query properties.provisioningState -o tsv`
**Bicep (resource-group scope, after 403 fallback):** `az deployment group create --resource-group {rg} --template-file infra/main.bicep --parameters @infra/main.parameters.json --query properties.provisioningState -o tsv`
**Terraform (alternative):** `cd infra && terraform init && terraform plan -out=tfplan && terraform apply tfplan`. Stop. | -| **Edit plan** | Ask what to change → write to `context.json.overrides[]` → re-run prepare (Step 4) → re-scaffold → return to approval gate | -| **Cancel** | Preserve all session artifacts. Say "💾 Session preserved — resume anytime." Stop. | - -## Rules - -- ⛔ Gate is the LAST content in the response — no continued execution until user replies -- ⛔ SKU column must show exact Azure SKU code + tier name (e.g., "B1 Linux (Basic)", "P1v3 (Premium)") — not generic labels like "cheapest tier". F1/D1/Free are never selected. -- Always show the monthly cost estimate — there is no $0 tier (B1 floor ~$13/mo) -- Surface ALL `FLAGGED` self-review findings — user must see risks before approving -- If validation failed, show failures and block Yes option until resolved diff --git a/.github/agents/azure-deploy/deploy/references/blocked-patterns.md b/.github/agents/azure-deploy/deploy/references/blocked-patterns.md deleted file mode 100644 index 23cee83..0000000 --- a/.github/agents/azure-deploy/deploy/references/blocked-patterns.md +++ /dev/null @@ -1,36 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Blocked Patterns - -Commands the agent must NEVER execute. Block decisions are non-negotiable — user must run blocked commands manually outside AppOnboard. - -| Pattern | Action | Reason | -|---------|--------|--------| -| `rm -rf` (any path outside a fresh temp dir) | ⛔ Block | Prevents accidental deletion of IaC, app code, or session artifacts — especially `infra/`, `.azure/`, `.copilot-azure/`. | -| `git reset --hard`, `git checkout -- `, `git restore`, `git clean` | ⛔ Block | Discards uncommitted work. During region-fallback healing the agent edits Bicep/app config; these wipe the user's unstaged changes irrecoverably. | -| `git push --force` / `--force-with-lease` (any branch) | ⛔ Block | Prevents force-push of generated code over remote history. | -| `--no-verify` (on `git commit` / `git push`) | ⛔ Block | Bypasses hooks (secret-scan, lint) that guard the commit. | -| `DROP TABLE` / `DROP DATABASE` | ⛔ Block | Prevents data loss | -| `terraform destroy` | ⛔ Block | Prevents accidental teardown (user must run manually) | -| `az group delete` | ⛔ HARD BLOCK | **NEVER delete resource groups yourself.** During healing: if switching regions/RGs, add the old RG to your `orphanedResourceGroups[]` list (per `OrphanResourceGroup` in [`deploy-schemas.ts`](deploy-schemas.ts)) instead of deleting it. At handoff: emit `az group delete` commands in the handoff message for the USER to run — the agent never executes them. If you are about to type `az group delete` into a terminal command, STOP — you are violating this rule. Track it in `orphanedResourceGroups[]` instead. | -| `az containerapp up --source` / `az containerapp create` | ⛔ Block | Creates ACR + CA Environment + Log Analytics imperatively — orphan resources invisible to `terraform destroy`, `az deployment sub delete`, and session tag-based bulk cleanup. State drift from IaC is unrecoverable. The Container App MUST be created via Bicep `az deployment sub create` — for code deploy on an existing CA use `az containerapp update --source` (Step 6d) | -| `az appservice plan update` | ⛔ Block | Imperative SKU change — edit Bicep + redeploy | -| `az webapp update` | ⛔ Block | Imperative resource modification — all changes via IaC | -| `az functionapp update` | ⛔ Block | Imperative resource modification — all changes via IaC | -| `az webapp deployment source config-zip` | ⛔ Block | Requires SCM basic auth — use `az webapp deploy` (Entra auth). ⛔ **This block is the `webapp` command only.** `az functionapp deployment source config-zip` is the **ALLOWED** deploy channel for Flex Consumption function apps (blob-container package, not SCM basic auth) — see [`code-deployment-functions-flex.md`](code-deployment-functions-flex.md). | -| `az webapp deploy --track-status` | ⛔ Block | `--track-status` flag does not exist. Remove it. | -| `az webapp up` / `az webapp create` / `az appservice plan create` | ⛔ Block | Creates App Service Plan + App imperatively — bypasses IaC entirely | -| `az containerapp update` (config changes) | ⛔ Block | Imperative resource modification — all changes via IaC | -| `az containerapp update --revision-suffix` (no config changes) | ⚠️ ALLOWED | KV secret rotation only — when KV secrets were updated post-deploy and a new revision is needed to pick up cached values | -| `az webapp delete` | ⛔ Block | Imperative resource deletion — destroys resources outside IaC | -| `az appservice plan delete` | ⛔ Block | Imperative plan deletion — remove from Bicep + redeploy instead | -| `az containerapp update --image` | ⛔ Block (during healing) | Imperative image swap causes IaC drift — update Bicep + redeploy | -| Inline secret values in CLI args | ⛔ Block | `--parameters password=MyP@ss$word!` breaks shell escaping and leaks secrets in terminal history. Pass app-internal secrets via `main.parameters.json`, `terraform.tfvars`, or an `@secure()` param from a variable reloaded from `deploy-secrets.env` (no Key Vault). | -| Writing secrets to temp files on disk | ⛔ Block | ⛔ NEVER write rendered secret values to arbitrary temp files. App-internal secrets are passed as `@secure()` params and stored on the compute resource (App Service app settings / CA native secrets); the only persisted copy is the git-ignored `deploy-secrets.env` reload cache under `.copilot-azure/`. Temp files risk exposure in crash dumps, logs, and unprotected storage. | -| `az group create` (during healing) | ⛔ HARD BLOCK — **one sanctioned exception** | **NEVER create resource groups imperatively during healing.** All RG creation must go through `az deployment sub create` with Bicep `targetScope = 'subscription'`. If you need a new RG for region fallback, update the Bicep region parameter and redeploy. **Sole exception:** the documented 403 scope-fallback in [`deploy-safety.md`](deploy-safety.md) § 403 Scope Fallback — when `az deployment sub create` returns 403, rescoping Bicep to RG-scope requires `az group create` (with all 5 AppOnboard tags). That path is allowed. | -| `az rest --method put/patch` (for individual resource creation) | ⛔ HARD BLOCK | **NEVER create individual Azure resources via REST API as a fallback for Bicep failures.** After a deployment failure, the ONLY allowed remediation is: fix the Bicep parameters/template → re-run `az deployment sub create`. Compiling Bicep→ARM and deploying via REST is still imperative resource creation. | -| Disabling a security control to unblock — `require_secure_transport`/TLS → OFF, HTTPS-only off, KV purge protection off, auth off (via `az ... parameter set` OR editing the Bicep) | ⛔ HARD BLOCK | **NEVER weaken a security control to make a failing deploy pass.** A DB TLS handshake failure means the *client* lacks SSL config — fix the client (prereq `W-MYSQL-SSL`/`W-PG-SSL`) or surface the tradeoff to the user. Downgrading the server control is forbidden. | -| `Compress-Archive -Path $files.FullName` | ⛔ Block | Absolute paths flatten directory structure — app crashes on `./src/app` not found. Use `System.IO.Compression.ZipFile` with relative paths from workspace root. On Windows, normalize: `$entryName = $relativePath.Replace('\', '/')`. | - -> **Repos with existing `azure.yaml`:** See [`pipeline-rules.md`](../../references/pipeline-rules.md) § azure.yaml prohibition. Deploy via `az deployment sub create` — do NOT run `azd up`. diff --git a/.github/agents/azure-deploy/deploy/references/code-deployment-appservice.md b/.github/agents/azure-deploy/deploy/references/code-deployment-appservice.md deleted file mode 100644 index 0be134a..0000000 --- a/.github/agents/azure-deploy/deploy/references/code-deployment-appservice.md +++ /dev/null @@ -1,84 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Code Deployment — App Service (and Premium/EP1 Functions) - -After IaC deployment creates the Azure resources, deploy application code. - -> ⛔ **Functions on Flex Consumption (the default Functions floor) do NOT deploy through this SCM/Kudu path.** They use a blob-container package channel with no `basicPublishingCredentialsPolicies` toggle. Use [code-deployment-functions-flex.md](code-deployment-functions-flex.md) instead. This file applies to App Service web apps and to Functions only on **Premium (EP1)**. - -> ⛔ **`--subscription {subscriptionId}` on EVERY `az` command.** - -> ⛔ **Verify `SCM_DO_BUILD_DURING_DEPLOYMENT=true` is active BEFORE deploying.** ARM timing can delay propagation. Check: `az webapp config appsettings list -g {rg} -n {app} --query "[?name=='SCM_DO_BUILD_DURING_DEPLOYMENT'].value" -o tsv`. If not `true`: `az webapp config appsettings set -g {rg} -n {app} --settings SCM_DO_BUILD_DURING_DEPLOYMENT=true ENABLE_ORYX_BUILD=true`. Wait 10s. If `az webapp deploy` reports "Build successful. Time: 0(s)", Oryx was skipped — use Kudu zipdeploy instead. - -> ⛔ **`ORYX_DISABLE_COMPRESSION=true`** and **`WEBSITES_CONTAINER_START_TIME_LIMIT=1800`** must be in Bicep app settings (from `prepare-plan.json.deployStrategy.requiredAppSettings`). - -## Pre-Deploy Verification (Step 6a) - -> ⛔ **TypeScript projects:** Oryx with `NODE_ENV=production` skips devDependencies. If `typescript`, `@types/*`, or build tools are in `devDependencies`, move them to `dependencies` before zipdeploy. Alternative: set `NPM_CONFIG_PRODUCTION=false` as app setting so Oryx installs devDeps during build. - -> ⛔ **Wait for App Service to stabilize** (B1: ~30-90s cold start on a freshly created plan). Poll `az webapp show -g {rg} -n {app} --query state` every 10s, max 2 min. If not `Running`, check logs. - -When `deployStrategy.codeDeployPattern == "startup-install"`, surface: "⚠️ First cold start: 2-5 min (native module compilation)." - -## Zip Deploy (Step 6b) - -SCM lifecycle: enable → deploy → re-disable. - -**Enable SCM:** -```powershell -az rest --method put --url "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Web/sites/{app}/basicPublishingCredentialsPolicies/scm?api-version=2023-12-01" --headers "Content-Type=application/json" --body '{"properties":{"allow":true}}' -``` - -**Choose deploy method:** - -| Runtime | Needs Oryx? | Method | -|---|---|---| -| Python, Node.js, Ruby, PHP | Yes | **Kudu zipdeploy** (`/api/zipdeploy`) | -| .NET, Java, static front-end | No | `az webapp deploy --type zip` | - -⛔ **OneDeploy NEVER triggers Oryx** — use Kudu zipdeploy for runtimes needing server-side install. -⛔ **NEVER use `az webapp deployment source config-zip`** — deprecated. -⛔ **`az webapp deploy` does NOT support `--track-status`.** - -### Kudu Zipdeploy (Oryx-Dependent Runtimes) - -For apps needing server-side package installation (Python, Node.js, Ruby, PHP), use Kudu zipdeploy directly: - -```powershell -# Get publishing credentials -$creds = az webapp deployment list-publishing-credentials --subscription {sub} -g {rg} -n {app} --query "{user:publishingUserName, pass:publishingPassword}" -o json | ConvertFrom-Json -$auth = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($creds.user):$($creds.pass)")) - -# Deploy via Kudu zipdeploy (triggers Oryx pip install) -Invoke-WebRequest -Uri "https://{app}.scm.azurewebsites.net/api/zipdeploy?isAsync=true" -Method POST -InFile $zipPath -Headers @{Authorization="Basic $auth"} -ContentType "application/zip" -UseBasicParsing - -# Poll until build completes -for ($i = 1; $i -le 40; $i++) { - Start-Sleep -Seconds 15 - $resp = Invoke-WebRequest -Uri "https://{app}.scm.azurewebsites.net/api/deployments/latest" -Headers @{Authorization="Basic $auth"} -UseBasicParsing - $deploy = $resp.Content | ConvertFrom-Json - if ($deploy.complete -eq $true) { break } -} -``` - -Prerequisites: -- `SCM_DO_BUILD_DURING_DEPLOYMENT=true` must be set (verified in Step 6a) -- Runtime manifest must be at the zip root — Oryx detects the runtime from it: - - Python: `requirements.txt` (or `pyproject.toml`) - - Node.js: `package.json` (+ lockfile) - - Ruby: `Gemfile` - - PHP: `composer.json` -- Do NOT pre-install packages locally — let Oryx run the install remotely - -> **SCM auth lifecycle (REST API toggle — no Bicep edits):** -> -> IaC has `scm.allow: true` (deploy convenience). Deploy phase: -> Deploy code → health check → re-disable via REST API → verify `false`. -> If re-disable fails, log but don't block — add postDeployRecommendation. - -**Zip creation:** Use `System.IO.Compression.ZipFile` with relative paths from workspace root. **On Windows, normalize entry paths: `$entryName = $relativePath.Replace('\', '/')` — ZipFile preserves backslashes which Linux App Service cannot resolve.** Never use `Compress-Archive -Path $files.FullName` — absolute paths flatten the directory structure, causing app crashes (`./src/app` not found). - -## Database Post-Deploy Verification - -> ⛔ **You MUST read [`database-post-deploy.md`](database-post-deploy.md)** for migration discovery, execution via App Service SSH, error handling, and PostgreSQL-specific checks. diff --git a/.github/agents/azure-deploy/deploy/references/code-deployment-container-apps.md b/.github/agents/azure-deploy/deploy/references/code-deployment-container-apps.md deleted file mode 100644 index 491dfed..0000000 --- a/.github/agents/azure-deploy/deploy/references/code-deployment-container-apps.md +++ /dev/null @@ -1,98 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Code Deployment — Container Apps - -After IaC deployment creates the Container App with a placeholder image (`mcr.microsoft.com/azuredocs/containerapps-helloworld:latest`), deploy the actual application code. This is **Phase 2** of the two-phase Container Apps deployment pattern. - -> ⛔ **`--subscription {subscriptionId}` on EVERY `az` command** (from `context.json.azure.subscriptionId`). Without it, the CLI uses whatever subscription is currently active — which may have changed since the prepare phase. This applies to ALL commands below. - -> ⛔ **Phase 2 is NOT optional.** If IaC deployed a Container App with a placeholder image, you MUST execute Phase 2 before health checks. Do NOT leave a placeholder image and tell the user to deploy code manually. - -**Determine the code deploy path:** - -| Condition | Path | Steps | -|-----------|------|-------| -| ACR exists in IaC (`services[]` has Container Registry) | ACR build + image update | Steps 1–4 below | -| No ACR in IaC, simple app (single Dockerfile or source) | Add ACR to IaC + redeploy, then ACR build | Step 0 + Steps 1–4 | -| No ACR in IaC, no Dockerfile, Oryx-compatible (Node/Python/Go/.NET) | `az containerapp update --source .` | Step 5 (Oryx shortcut) | - -## Step 0 — Add ACR to IaC (if not already present) - -Add `container-registry.bicep` module (ACR Basic, `adminUserEnabled: false`) + AcrPull role assignment for CA's managed identity (role GUID: `7f951dda-4ed3-4680-a7ca-43fe172d538d`). Redeploy IaC, wait ~60s for AcrPull role propagation. Log as healing attempt. - -⛔ Update `prepare-plan.json` (add ACR to `services[]`), `scaffold-manifest.json.files[]`, and `costEstimate`. - -## Steps 1–4 — ACR Build + Image Update (IaC-compliant) - -| Step | Command | Notes | -|------|---------|-------| -| 1. Build image | `az acr build --subscription {subscriptionId} -r {acrName} -t {appName}:latest . --no-logs` | Builds from workspace root. If `buildRequirements.hasBuildKitSyntax`, use `-f Dockerfile.azure` (see BuildKit handling below). ⛔ **Build-time env vars:** If Dockerfile has `ARG NEXT_PUBLIC_*` or `ARG VITE_*`, pass `--build-arg NEXT_PUBLIC_API_URL=https://{api-fqdn}` to inject the deployed API URL. These vars are baked into the JS bundle at build time — runtime env vars have no effect on client-side code. Get the API FQDN from Phase 1 output: `az containerapp show -g {rg} -n {apiApp} --query properties.configuration.ingress.fqdn -o tsv`. | -| 2. Update Bicep image | Edit `infra/modules/{containerapp}.bicep`: replace placeholder with `image: '{acrLoginServer}/{appName}:latest'`. Add ACR registry config: `registries: [{ server: acrLoginServer, identity: 'system' }]` | IaC-only — no imperative `az containerapp update` | -| 3. Redeploy | `az deployment sub create --subscription {subscriptionId} --location {location} --template-file infra/main.bicep --parameters @infra/main.parameters.json --parameters containerImage='{acrLoginServer}/{appName}:latest' --name app-onboard-code-deploy-{timestamp} --query properties.provisioningState -o tsv` | Emit new portal link. ⛔ No DB password param — databases are managed-identity only (see [Parameter Pass-Through](#parameter-pass-through-bicep-redeploy-safety)). | -| 4. Verify revision | `az containerapp show --subscription {subscriptionId} -g {rg} -n {ca} --query "{revision:properties.latestReadyRevisionName, image:properties.template.containers[0].image}" -o json` | Confirm image is not the placeholder | - -> ⛔ **`az acr build` failures count toward the `deploy-result.json.healingAttempts[]` counter.** After 3 failed builds (even with different root causes), pause and present a diagnosis to the user: "Web image build failed 3 times: [root causes]. Continue healing? (Yes / Cancel)." Each build fix attempt = 1 healing entry. Cross-reference deploy instructions.md healing loop rule. - -> ⛔ **Windows: `az acr build` may fail with `UnicodeEncodeError`.** Azure CLI log streaming crashes on non-ASCII characters (✓, ✗) using Windows `charmap` codec. Append `--no-logs` to the build command. Build success/failure is still reported via exit code. - -## Step 5 — Oryx Shortcut (no ACR needed, no Dockerfile) - -```powershell -az containerapp update --subscription {subscriptionId} -g {rg} -n {ca} --source . --set-env-vars NODE_ENV=production -``` - -> ⛔ **`az containerapp update --source` is allowed for code deploy ONLY.** This is different from `az containerapp up --source` (which is ⛔ BLOCKED because it creates resources imperatively). `update --source` updates an EXISTING Container App — no state drift. - -## App-Internal Secrets (native CA secrets — No Key Vault, no seeding step) - -App-internal secrets are set as **native Container Apps secrets** directly in the Bicep, with the value supplied as an `@secure()` param on the deployment command. ⛔ **No Key Vault, no `az keyvault secret set` step.** Generate each secret ONCE and pass it on every `az deployment sub create`: - -```powershell -$secretKey = (openssl rand -base64 32) -replace '[/+=]','' # generate ONCE, persist to deploy-secrets.env -az deployment sub create ... --parameters secretKey=$secretKey --query properties.provisioningState -o tsv -``` - -> ⛔ Reload the SAME value from `deploy-secrets.env` on every retry/redeploy (shell variables don't persist between tool calls). Pass it to EVERY deployment so the desired-state apply keeps the CA secret stable. - -## After Code Deploy (all paths) - -- Wait 30–60s for the new revision to become ready -- Proceed to Step 7 (Health-Check Endpoints) -- If health check returns placeholder content → image update didn't take effect. Check `az containerapp revision list` - -> ⛔ **`az containerapp revision restart` does NOT pick up a changed secret.** Container Apps bind secret values at revision *creation* time. To pick up an updated native secret, create a NEW revision by redeploying Bicep (preferred) or `az containerapp update --revision-suffix rev{timestamp}`. - -> ⛔ **ACR registries require the CA managed identity + AcrPull to exist first.** Phase 1 of the two-phase deploy creates the Container App with a placeholder image and `registries: []` (native secrets MAY be set in Phase 1 — they have no RBAC dependency). ACR `registries` with `identity: 'system'` fails in Phase 1 because the CA's managed identity doesn't exist yet (no principalId → AcrPull fails → "Operation expired"). After Phase 1 completes and RBAC propagates (~60s), Phase 2 redeploys with ACR registries + real image + correct `targetPort`. - -## Config-File Apps (Go/Viper, Spring Boot, etc.) - -Creating Azure-specific config (e.g., `config-azure.yml`) is fine for non-secret values. - -> ⛔ **NEVER bake secrets into config files COPY'd into Docker images.** - -**Secret injection:** Config uses placeholders → `az containerapp secret set` → `az containerapp update --set-env-vars KEY=secretref:name` → framework env var override. - -> **Go/Viper:** `AutomaticEnv()` maps `POSTGRES_PASSWORD` → `postgres_password` (underscores), NOT `postgres.password`. Without `SetEnvKeyReplacer(strings.NewReplacer(".", "_"))`, env vars can't override nested keys. - -## BuildKit Dockerfile Handling - -ACR's `az acr build` uses the classic Docker builder — it does NOT support BuildKit. When `buildRequirements.hasBuildKitSyntax == true`: build using the ACR-compatible copy instead: `az acr build -f Dockerfile.azure .`. The user's original Dockerfile stays untouched. If `Dockerfile.azure` doesn't exist yet, create it by stripping BuildKit syntax from the original Dockerfile per [`dockerfile-generation.md § ACR Build Compatibility`](../../scaffold/references/dockerfile-generation.md). - -## Parameter Pass-Through (Bicep Redeploy Safety) - -⛔ **On every redeploy, pass the SAME values you originally used** — a full desired-state apply, not a patch, so an omitted param reverts to its default. The one param that bites: - -- **`containerImage`** — omitting it reverts the Container App to the placeholder image. - -> ⛔ **No `administratorLoginPassword` (or any DB password/key) param exists** — databases are Entra/managed-identity only. There is nothing to re-pass and no `deploy-secrets.env` DB password. App-internal secrets (e.g. `SECRET_KEY`) live in Key Vault and are read via the app's MI — they are not passed as deploy params. - -```powershell -az deployment sub create ... ` - --parameters containerImage='{acrLoginServer}/{appName}:latest' ` - --query properties.provisioningState -o tsv -``` - -## Database Post-Deploy Verification - -> ⛔ **You MUST read [`database-post-deploy.md`](database-post-deploy.md)** for migration discovery, execution via Container Apps exec, error handling, and PostgreSQL-specific checks. diff --git a/.github/agents/azure-deploy/deploy/references/code-deployment-functions-flex.md b/.github/agents/azure-deploy/deploy/references/code-deployment-functions-flex.md deleted file mode 100644 index 1f07901..0000000 --- a/.github/agents/azure-deploy/deploy/references/code-deployment-functions-flex.md +++ /dev/null @@ -1,93 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Code Deployment — Azure Functions (Flex Consumption) - -How to deploy application code to a **Flex Consumption** function app after its IaC is provisioned. This is a -**different channel** from App Service — read this instead of -[code-deployment-appservice.md](code-deployment-appservice.md) whenever the compute is Functions on Flex -Consumption (the default Functions floor). Only **Functions on Premium (EP1)** use the App Service SCM/Kudu -path. - -> ⛔ **`--subscription {subscriptionId}` on EVERY `az` command.** - -## The Flex deploy model (why the App Service path does not apply) - -Flex Consumption has a **single deployment path**: you build and zip your project, the package is written to a -**blob storage container**, and the app runs from that package on startup. There is **no** Kudu -`SCM_DO_BUILD_DURING_DEPLOYMENT`/Oryx-on-SCM build, **no** `az webapp deploy --type zip`, and **no** -`basicPublishingCredentialsPolicies` (`scm`/`ftp`) enable/re-disable dance. Do not run any of those against a -Flex app — they either error or silently do nothing. - -## Deploy the package - -Build/zip the project, then deploy with the Functions channel. Prefer a **remote build** for interpreted -runtimes so native wheels/modules are built on Linux, not on the local machine: - -```powershell -# Python / Node / TypeScript — zip the PROJECT ROOT (manifest at the zip root) and request a remote build. -az functionapp deployment source config-zip ` - --subscription {sub} -g {rg} -n {app} ` - --src $zipPath --build-remote true - -# .NET (isolated) / Java / Go — deploy a precompiled package (build output at the zip root); no remote build. -az functionapp deployment source config-zip --subscription {sub} -g {rg} -n {app} --src $zipPath -``` - -`func azure functionapp publish {app}` (Azure Functions Core Tools) is an equivalent channel and also performs -a remote build — use it if Core Tools is already the project's workflow. - -- The runtime is fixed by `functionAppConfig.runtime` in IaC — do **not** pass or set `FUNCTIONS_WORKER_RUNTIME`. -- The deploying identity writes the package to the deployment container. With shared-key access disabled, that - identity (the signed-in user for a local deploy, or the CI principal) needs **Storage Blob Data Contributor** - on the deployment storage account, in addition to the function app's own identity role. -- After deploy, sync is automatic; then run the health check below. - -## ⛔ Local (non-CI) deploys: hardening can lock you out — do not deny-by-default before first deploy - -This pipeline hardens network access, which is CI/CD-friendly but **can block a deploy run from a developer -machine.** Two Flex-specific facts matter: - -1. **Flex outbound is a platform-managed gateway pool, not a per-instance IP.** An SCM/site allow rule built - from the deployer's observed public IP (e.g. via ipify) will **not** match the channel's real egress, so it - does not unblock the deploy and often changes between attempts. Do **not** try to punch a `/32` SCM hole to - fix a 403 — it is the wrong lever for Flex. -2. **Model the main site and the deployment/SCM surface independently.** Restricting one must not implicitly - deny the other. - -**Rules for the deploy phase:** - -- ⛔ **Do NOT set `publicNetworkAccess: 'Disabled'` or a deny-by-default rule on the function app or its - deployment storage BEFORE the first successful code deploy + health check.** Provision with access open, - deploy, verify, *then* harden. -- Before hardening, **tell the user** (once, plainly): *"This deployment hardens network access for - production. Publishing the code from your machine happens first, while access is still open; after the app - is healthy I'll restrict it. If you need to redeploy later from your machine, you may need to temporarily - re-open access or deploy from CI."* -- If a package deploy returns **HTTP 403**, the cause is network hardening applied too early or a missing - Storage Blob Data role — **not** a missing SCM allow rule. Re-open public access on the app + deployment - storage, confirm the deployer's role assignment, redeploy, then re-harden. Never fall back to imperative - provisioning. -- ⛔ **Azure inserts a terminal "Deny all" rule as the last entry of any `ipSecurityRestrictions` / - `scmIpSecurityRestrictions` list — this is the expected default sentinel, NOT an error.** Validation must - treat a trailing deny-all with priority `2147483647` (name `Deny all`/`Explicit deny`) as normal. Only flag - it if there are **no** preceding allow rules AND you intended the surface to be reachable. - -## Hardening (only AFTER a healthy deploy) - -If the plan calls for a locked-down app, apply `publicNetworkAccess`/IP restrictions as the **last** step, -after the health check passes. Keep the deployment storage reachable by the app identity. Record any access -you re-open for a local deploy so cleanup is idempotent (re-running the step must not error if the rule is -already gone). - -## Health check - -HTTP GET the function endpoint(s) (max 3 iterations, honoring cold start). A Flex app can take up to ~30s to -initialize on first start; `System.TimeoutException`/gRPC host messages during that window are startup noise, -not deploy failures. Inspect the response body for error patterns (`connection refused`, `MODULE_NOT_FOUND`, -`SET-IN-DEPLOY-PHASE`) — HTTP 200 alone is not proof of health when the app depends on another service. - -## Database post-deploy - -> ⛔ If the app uses a managed database, run [`database-post-deploy.md`](database-post-deploy.md) for migration -> discovery and execution. diff --git a/.github/agents/azure-deploy/deploy/references/code-deployment-swa.md b/.github/agents/azure-deploy/deploy/references/code-deployment-swa.md deleted file mode 100644 index 8dec2da..0000000 --- a/.github/agents/azure-deploy/deploy/references/code-deployment-swa.md +++ /dev/null @@ -1,57 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Code Deployment — Static Web Apps - -## Static Web Apps — Content Deployment (Step 6c) - -Deploy content using the SWA CLI with deployment token. - -**Pre-check:** Verify `swa` CLI is installed: `npx --yes @azure/static-web-apps-cli --version`. If not available, install: `npm install -g @azure/static-web-apps-cli`. - -> ⛔ **Pre-deploy: Build the frontend if SPA source (not pre-built HTML).** Check if the SWA component directory has a `package.json` (or equivalent manifest) with a `build` script. If yes, detect the package manager from the lockfile (`package-lock.json` → npm, `yarn.lock` → yarn, `pnpm-lock.yaml` → pnpm, `bun.lock`/`bun.lockb` → bun; default to npm if no lockfile) and run: -> ```powershell -> cd {component-path} # e.g., web/ -> {pm} install # npm install, yarn install, pnpm install, etc. -> {pm} run build # npm run build, yarn build, pnpm build, etc. -> ``` -> The deploy path should point to the **build output directory** — read the framework config to determine the output dir (`vite.config.*` → `build.outDir`, `next.config.*` → `.next/` or `out/`, CRA → `build/`; default to `dist/`). If no `package.json` exists (plain HTML/CSS/JS), deploy the source directory directly — no build needed. -> -> ⛔ **This is NOT optional for SPA frameworks.** Raw JSX/TSX/Vue/Svelte source files cannot be served by SWA — the app must be compiled to static HTML/JS/CSS first. Skipping the build produces a broken deployment. - -> ⛔ **Pre-deploy: Update frontend config with deployed backend URLs.** If `prereq-output.json.cloudSdkSwaps[]` mapped cloud SDK endpoints (AWS API Gateway, GCP Cloud Functions) to Azure equivalents, the frontend config file (`config.ts`, `.env`, `environment.ts`) still references the original cloud URLs. After IaC deploy (Step 6), read `deploy-result.json.endpoints[]` to get the deployed Azure backend URLs. Update the frontend config with these URLs before running `swa deploy`. This is a string replacement in the config file — NOT a code rewrite. Do NOT defer as a post-deploy step — the SWA will show a broken page if the frontend calls non-existent AWS/GCP endpoints. - -| Step | Command | Notes | -|------|---------|-------| -| 1. Get token | `$token = az staticwebapp secrets list --name {swa} -g {rg} --query "properties.apiKey" -o tsv 2>$null` | Store in variable first — do NOT pass inline. ⛔ **On Windows, use `2>$null`** (not `2>&1`) — Azure CLI Python warnings corrupt `-o tsv` output | -| 2. Set env var | `$env:SWA_CLI_DEPLOYMENT_TOKEN = $token` | ⛔ Use env var ONLY — do NOT pass `--deployment-token $token` as CLI arg (leaks token in process args / transcript) | -| 3. Copy to temp | `$tempDir = "C:\temp\swa-deploy"; Remove-Item -Recurse -Force $tempDir -ErrorAction SilentlyContinue; New-Item -ItemType Directory -Path $tempDir -Force \| Out-Null; Copy-Item -Path .\* -Destination $tempDir -Recurse -Exclude @('.git','.copilot-azure','node_modules','.azure','infra')` | ⛔ **MANDATORY on Windows** — `$env:TEMP` often contains spaces (e.g., `C:\Users\Jane Doe\AppData\Local\Temp`). Always use a short, space-free path on the FIRST attempt — do NOT use `$env:TEMP` and retry. On macOS/Linux, use `/tmp/swa-deploy` instead. **If a build step ran:** copy the build output directory (e.g., `{component}/dist/`) instead of the entire workspace — `Copy-Item -Path {component}\dist\* -Destination $tempDir -Recurse`. | -| 4. Deploy | `swa deploy $tempDir --app-name {swaName} --env production` | ⛔ **`--app-name` is MANDATORY** — without it, the SWA CLI launches an interactive "create new project?" prompt that fails in automation. `{swaName}` = the SWA resource name from `prepare-plan.json.naming.resources[]`. SWA CLI reads `SWA_CLI_DEPLOYMENT_TOKEN` from env var automatically — do NOT pass `--deployment-token` flag (leaks token in command args) | -| 5. Clean up | `Remove-Item -Recurse -Force $tempDir` | Clean temp dir after successful deploy | - -⛔ **`az staticwebapp deploy` does NOT exist** — the correct CLI is `swa deploy` (from `@azure/static-web-apps-cli`). - -## Fallback: Direct StaticSitesClient Upload - -If `swa deploy` fails (binary crash, path errors on Windows), use the underlying `StaticSitesClient.exe` directly: - -```powershell -# Find the binary bundled with @azure/static-web-apps-cli -$swaCliPath = (Get-Command swa).Source | Split-Path -Parent -$client = Get-ChildItem -Path $swaCliPath -Recurse -Filter "StaticSitesClient*" | Select-Object -First 1 - -# Upload from PARENT directory with relative paths (avoids "identical to artifact folder" error) -Push-Location (Split-Path $tempDir -Parent) -& $client.FullName upload --app (Split-Path $tempDir -Leaf) --apiToken $token --skipAppBuild true -Pop-Location -``` - -> ⛔ **Run from a PARENT directory** with a relative `--app` path. Running from inside the app directory causes `StaticSitesClient` to error with "Current directory cannot be identical to or contained within artifact folders." - -## Finalize deploy-result.json (after `swa deploy` succeeds) - -⛔ **`swa deploy` succeeding is NOT the end of the deploy phase.** SWA finalizes through the **generic Step 8** (see [`deploy-checklist-template.md`](deploy-checklist-template.md) §"Before handoff (Step 8)") — overwrite the `deploy-result.json` skeleton IN PLACE with the full `DeployResult` contract, not a `status`+`subscriptionId` stub. Only the values Step 8 can't derive on the SWA path are below: - -- **Hostname** — `swa deploy` produces no ARM endpoint output, so fetch it: `$swaHost = az staticwebapp show -n {swa} -g {rg} --query defaultHostname -o tsv` -- `endpoints[]` — `[{ name, url: "https://$swaHost", healthStatus: "healthy" }]` (HTTP GET `https://$swaHost/` → 2xx confirms healthy) -- `resourceIds[]` — include the `Microsoft.Web/staticSites` resource id diff --git a/.github/agents/azure-deploy/deploy/references/database-post-deploy.md b/.github/agents/azure-deploy/deploy/references/database-post-deploy.md deleted file mode 100644 index e5a1502..0000000 --- a/.github/agents/azure-deploy/deploy/references/database-post-deploy.md +++ /dev/null @@ -1,136 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Database Post-Deploy Verification (Managed-Identity / Entra-Only) - -Run schema migrations on AppOnboard-created databases (listed in `prepare-plan.json.services[]`) before health checks. The app must be running first — if it's crashing, fix that before attempting migrations. - -> ⛔ **No passwords anywhere.** Databases are provisioned Entra-only (no `administratorLogin`/`administratorLoginPassword`, no access keys). Admin operations (create DB, grant the app's managed identity a role, run migrations) authenticate with an **Entra access token** minted for the deploying principal, which the Bicep set as the server's Entra administrator. There is no `deploy-secrets.env` DB password and no `pgAdminPassword` parameter. - -## 0. Mint an Entra token for the deploying principal - -The deployer is the Entra admin on the server (set by the DB module's `administrators` child resource). Mint a short-lived token and use it as the "password" for CLI connections. `{entraAdminName}` = the deployer's UPN / app display name (the `login` set in the server's `administrators` block). - -```powershell -# PostgreSQL / MySQL Flexible Server (OSS RDBMS audience) -$dbToken = az account get-access-token --resource-type oss-rdbms --query accessToken -o tsv -# Azure SQL (SQL audience) -$sqlToken = az account get-access-token --resource https://database.windows.net/ --query accessToken -o tsv -``` - -## 1. Create the app database (if needed) - -> ⛔ **Azure PostgreSQL/MySQL Flexible Server only creates the system `postgres`/`mysql` database by default.** If the app's config references a named database (e.g., `car_sale_db`, `myapp_production`), create it BEFORE the container starts — using token auth, never a password: -> -> ```powershell -> az postgres flexible-server execute -n {pg} -g {rg} -u "{entraAdminName}" -p $dbToken ` -> -d postgres --querytext "CREATE DATABASE {dbName};" -> ``` -> -> Detect the database name from: (1) `prereq-output.json.initCommands[]` with `type: "db-migrate"`, (2) app config files (`config-docker.yml`, `.env`, `database.yml`), (3) compose `POSTGRES_DB` env var. If the container crashes with `database "X" does not exist`, this step was missed. (MySQL app DBs are emitted in Bicep as a `flexibleServers/databases` child — no CLI step needed.) - -## 2. Grant the app's managed identity a database role - -The app authenticates with its **managed identity**, which must exist as a database principal. Do this ONCE, as the Entra admin, before migrations. `{appMiName}` = the compute resource name (system-assigned MI display name); `{appMiObjectId}` = its object id. - -**PostgreSQL** — two connections are required. The `pgaadauth_*` functions exist **only on the `postgres` database** (running them on the app DB fails with `No function matches the given name and argument types`), and for a **managed identity** you must use the `_with_oid` variant with `objectType 'service'` (the name-only `pgaadauth_create_principal` can't resolve a managed identity by name). Roles are cluster-wide, so create the principal on `postgres`, then GRANT on `{dbName}`: - -```powershell -# 2a. Create the Entra principal for the app MI — MUST run on the `postgres` database. -az postgres flexible-server execute -n {pg} -g {rg} -u "{entraAdminName}" -p $dbToken -d postgres --querytext @" -SELECT * FROM pgaadauth_create_principal_with_oid('{appMiName}', '{appMiObjectId}', 'service', false, false); -"@ - -# 2b. Grant privileges — run on the app database {dbName} (the role already exists cluster-wide). -# NOTE: a PowerShell here-string does NOT treat "" as an escape — use a single " around the identifier -# (doubling it emits `TO ""myapp"";` → Postgres `zero-length delimited identifier`). -az postgres flexible-server execute -n {pg} -g {rg} -u "{entraAdminName}" -p $dbToken -d {dbName} --querytext @" -GRANT ALL PRIVILEGES ON DATABASE {dbName} TO "{appMiName}"; -GRANT ALL ON SCHEMA public TO "{appMiName}"; -ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "{appMiName}"; -"@ -``` - -**MySQL:** `CREATE AADUSER '{appMiName}' IDENTIFIED BY '{appMiObjectId}'; GRANT ALL PRIVILEGES ON `` `{dbName}`.* `` TO '{appMiName}'@'%'; FLUSH PRIVILEGES;` - -**Azure SQL:** `CREATE USER [{appMiName}] FROM EXTERNAL PROVIDER;` then add to `db_datareader`, `db_datawriter`, and (if the app runs migrations) `db_ddladmin`. - -**Discover the migration command** from the codebase (check in order): - -| Signal | Command | Working directory | -|--------|---------|-------------------| -| `alembic.ini` exists | `alembic upgrade head` | Directory containing `alembic.ini` | -| Django `manage.py` exists | `python manage.py migrate` | Directory containing `manage.py` | -| `prisma/schema.prisma` exists | `npx prisma migrate deploy` | Project root | -| EF `Migrations/` directory | `dotnet ef database update` | Project root | -| Rails `db/migrate/` directory | `rails db:migrate` | Project root | -| Sequelize `migrations/` + `.sequelizerc` | `npx sequelize-cli db:migrate` | Project root | - -## Execute via the Deployed Environment - -Migrations run where the managed identity lives — inside the app, not from your workstation. - -> ⛔ **Authenticate with a token, never a password — and PREFER the app's own credential.** -> - **(1) Preferred — `DefaultAzureCredential`:** if the app's DB config uses `DefaultAzureCredential` / `Authentication=Active Directory Default`, the migration command works **unchanged** and authenticates as the app MI. No token handling, no `curl`/`jq`, works on slim/distroless images. Use this whenever possible. -> - **(2) Fallback — inject an MI token:** only if the app cannot self-authenticate. Fetch a token from the container's IMDS endpoint and pass it as the driver password env var (see below). ⛔ This requires `curl` + `jq` (or `wget`) **in the image** — absent on `slim`/`distroless` builds. If the tools aren't present, do NOT hand-roll it: surface a `FLAGGED` finding + `postDeployRecommendation` to add `DefaultAzureCredential` to the app's DB config. -> - ⛔ Never re-enable password auth to unblock. - -### App Service (Linux only) - -```powershell -az webapp ssh -n {app} -g {rg} --subscription {sub} -``` -Then, **inside the SSH session** (the container's own shell — no PowerShell layer), for the token-injection fallback (requires `curl`+`jq` in the image): -```bash -export PGPASSWORD="$(curl -s "$IDENTITY_ENDPOINT?resource=https://ossrdbms-aad.database.windows.net&api-version=2019-08-01" -H "X-IDENTITY-HEADER: $IDENTITY_HEADER" | jq -r .access_token)" -export PGUSER={appMiName} PGSSLMODE=require -{migration_command} -``` - -### Container Apps - -**Preferred (DefaultAzureCredential — nothing to inject, works on distroless):** -```powershell -az containerapp exec -n {ca} -g {rg} --subscription {sub} --command '{migration_command}' -``` - -**Fallback (token injection; image must have `curl`+`jq`):** -```powershell -# ⛔ SINGLE-QUOTE the whole --command so $(...) and $IDENTITY_* evaluate IN THE CONTAINER, not on the deployer. -# (A double-quoted PowerShell string would run curl locally and expand $IDENTITY_* to empty → empty PGPASSWORD.) -az containerapp exec -n {ca} -g {rg} --subscription {sub} --command 'sh -lc "export PGPASSWORD=$(curl -s \"$IDENTITY_ENDPOINT?resource=https://ossrdbms-aad.database.windows.net&api-version=2019-08-01\" -H \"X-IDENTITY-HEADER: $IDENTITY_HEADER\" | jq -r .access_token); export PGUSER={appMiName} PGSSLMODE=require; {migration_command}"' -``` - -> Preferred over both: bake the migration into the app's startup (`initCommands[]` → `appCommandLine`) using the app's own `DefaultAzureCredential` config, so migrations run token-based on every cold start (idempotent) with no exec step and no `curl`/`jq` dependency. - -## Error Handling - -If the migration command fails, classify as `IAC_ERROR` and check based on the database type: -- **AAD token / auth failure** (`password authentication failed`, `Login failed for token-identified principal`) → the app MI was not granted a DB role (§2), or Entra-admin/RBAC propagation hasn't completed. Verify the app MI principal exists, wait 60s, retry. -- DB unreachable → check firewall rules (PostgreSQL: `AllowAllAzureServicesAndResourcesWithinAzureIps`, SQL: server firewall, MySQL: similar) -- Extension/feature missing → check DB-specific config (PostgreSQL: `azure.extensions`, SQL: compatibility level, MySQL: `require_secure_transport`) -- Module not found → verify the runtime includes the migration tool - -> ⛔ **Never weaken auth to unblock.** Do NOT set `passwordAuth: 'Enabled'`, add an `administratorLoginPassword`, or re-enable access keys to make a failing migration pass. Fix the grant (§2) or the client token config. - -## PostgreSQL-Specific Checks - -Run BEFORE migrations when `services[]` includes PostgreSQL Flexible Server: - -1. **Token connectivity:** `az postgres flexible-server execute -n {pg} -g {rg} -u "{entraAdminName}" -p $dbToken -d postgres --querytext "SELECT 1"` — if this fails, the firewall rule is missing, the deployer is not the Entra admin, or AAD propagation hasn't completed. Check `AllowAllAzureServicesAndResourcesWithinAzureIps` exists and the `administrators` child deployed, wait 60s, retry -2. **Extension availability:** `az postgres flexible-server parameter show -g {rg} -n {pg} --name azure.extensions --query value -o tsv` — verify the extensions the app needs (e.g., `uuid-ossp` for Alembic/Django UUID fields) are in the allow-list. If missing, the Bicep module should have set them — check `infra/modules/postgresql.bicep` - -## MySQL Flexible Server Checks - -Run BEFORE migrations when `services[]` includes MySQL Flexible Server: - -1. **Token connectivity:** `az mysql flexible-server execute -n {mysql} -u "{entraAdminName}" -p $dbToken -d mysql -q "SELECT 1"` — if this fails, check the firewall rule, that the Entra admin is set, and AAD propagation. Note: `execute` resolves by server name (no `-g` needed) -2. **SSL enforcement:** `az mysql flexible-server parameter show -g {rg} -n {mysql} --name require_secure_transport --query value -o tsv` — verify matches the app's connection SSL mode - -## Azure SQL Checks - -Run BEFORE migrations when `services[]` includes Azure SQL: - -1. **Database online:** `az sql db show -g {rg} -s {sqlServer} -n {dbName} --query status -o tsv` — verify the database is `Online` -2. **Server firewall:** `az sql server firewall-rule list -g {rg} -s {sqlServer} -o table` — verify `AllowAllWindowsAzureIps` (0.0.0.0 → 0.0.0.0) exists for Azure-internal access -3. **Entra-only auth:** verify `azureADOnlyAuthentication` is `true` and the app MI has a contained user (§2) diff --git a/.github/agents/azure-deploy/deploy/references/deploy-checklist-template.md b/.github/agents/azure-deploy/deploy/references/deploy-checklist-template.md deleted file mode 100644 index 06d2cce..0000000 --- a/.github/agents/azure-deploy/deploy/references/deploy-checklist-template.md +++ /dev/null @@ -1,111 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Deploy Checklist Template (compaction-safe — generated at Step 5b) - -Long-running deploy sessions lose rules when the conversation compacts. At Step 5b, generate a checklist file tailored to this deployment. Write it to disk so it survives compaction — re-reading costs ~100 tokens. - -**Write** to `.copilot-azure/sessions/{id}/deploy-checklist.md` using the `create` tool at Step 5b. -**Re-read** via `view` after every long-running command (`az deployment`, `az webapp deploy`, `az acr build`), after each failed health check, and after any conversation compaction. - -## How to generate - -Read `prepare-plan.json` to determine the service types, then build the checklist from the template below. **Replace `{placeholders}` with real values** and **delete sections that don't apply** (e.g., remove the App Service section for a Container Apps deploy). - -```markdown -# Deploy Checklist for {appName} -# RG: {rgName} | Sub: {subscriptionId} | Session: {sessionId} - -## ⛔ Secret generation (BEFORE first az deployment) -- ⛔ **No database/cache/storage passwords or access keys exist** — those services are Entra/managed-identity only (no `@secure()` DB password params, no `administratorLoginPassword`). See [database-post-deploy.md](database-post-deploy.md). -- Auto-generate ONLY app-internal `@secure()` params (e.g. `SECRET_KEY`, JWT key) before first `az deployment sub create` — NEVER `ask_user` -- ⛔ On ANY retry OR redeploy (incl. after a conversation compaction): read the SAME `@secure()` value back from `deploy-secrets.env` (or `deploy-audit.log`) — NEVER regenerate. Pass it to EVERY deployment. An app-internal secret that's regenerated overwrites the live on-compute value → auth 500s while provisioning still reports success. - -## ⛔ Read deploy/instructions.md -- You MUST `view` deploy/instructions.md BEFORE running any `az deployment` command -- Path: `.github/agents/azure-deploy/deploy/instructions.md` -- If you have not read it in this conversation (or since the last compaction), read it NOW -- It covers preflight checks, portal links, what-if, SCM lifecycle, deploy-result.json schema, audit logging, and health checks — skip it and none of these happen - -## After every `az` command -- Append 2 lines to `deploy-audit.log`: `{timestamp} | {command} | started` then `{timestamp} | {command} | succeeded/failed` - -## After IaC deployment (Step 6) -- ⛔ Call `capture_deployment_inventory` (`phase: "capture"`) after the deployment command returns — records created resources via a before/after `resources.list()` diff and flags orphans -- Verify 5 tags: `az group show -n {rgName} --query tags` -- ⛔ Do NOT set startup command or app settings via CLI — they are already in Bicep from scaffold. If `az webapp show` doesn't reflect them yet, wait 30s and re-check (ARM propagation delay). Do NOT run `az webapp config` imperatively. - Required: app-onboard-skill, app-onboard-session-id, created-at, environment, deployed-by -- Verify portal link is still correct if healing changed the deployment name - -## Code deploy — Functions (Flex Consumption) (delete if not using Flex Functions) -- ⛔ **Read [`code-deployment-functions-flex.md`](code-deployment-functions-flex.md).** Flex is NOT App Service: no SCM/Kudu, no `basicPublishingCredentialsPolicies`, no `SCM_DO_BUILD_DURING_DEPLOYMENT`/`ENABLE_ORYX_BUILD`. -- Deploy the package: `az functionapp deployment source config-zip --subscription {sub} -g {rgName} -n {appName} --src {zip} [--build-remote true]` (remote build for Python/Node) — or `func azure functionapp publish {appName}`. -- ⛔ **Local (non-CI) deploy warning — say it to the user:** hardening network access can block publishing from your machine. Deploy with access OPEN, verify health, THEN harden. An ipify-derived `/32` SCM allow rule will NOT match Flex egress (platform gateway pool) — do not try it. -- ⛔ A trailing `Deny all` rule (priority `2147483647`) in `ipSecurityRestrictions`/`scmIpSecurityRestrictions` is the expected platform sentinel — NOT an error. -- The app identity needs a Storage Blob Data role on the deployment storage (shared-key is disabled). - -## Code deploy — App Service (delete if not using App Service) -- ⛔ **Deploy command: `az webapp deploy --type zip`** (Entra-capable, supports `--async`). NEVER `az webapp deployment source config-zip` — it needs SCM basic auth and is disallowed. -- Wait for stabilization: `az webapp show -g {rgName} -n {appName} --query state` → "Running" -- Verify `SCM_DO_BUILD_DURING_DEPLOYMENT=true` is active before deploy (ARM timing can delay) -- If build reports "0 seconds" but app needs deps: re-set the setting, wait 10s, retry -- If 0s persists after 2 retries: fall back to Kudu `/api/zipdeploy` -- Python: if no `antenv/` after deploy, use Kudu `/api/zipdeploy` immediately (OneDeploy may skip Oryx) -- Windows zip paths: normalize with `.Replace('\', '/')` before creating zip entries -- ⛔ Verify ORYX_DISABLE_COMPRESSION=true is set (prevents output.tar.zst extraction failures at startup — applies to ALL tiers, not just F1) -- Set WEBSITES_CONTAINER_START_TIME_LIMIT=1800 for safety -- TypeScript apps: verify `typescript` and `@types/*` are in `dependencies` (not devDependencies) — Oryx with NODE_ENV=production skips devDeps. Alternative: set app setting NPM_CONFIG_PRODUCTION=false -- Enable SCM before zip deploy, re-disable after: `az rest --method put` → allow:false → verify -- After deploy: check response body for Azure default page ("Your app service is up and running" = app didn't start) - -## Code deploy — Container Apps (delete if not using Container Apps) -- Phase 2 is NOT optional — deploy actual image, don't leave placeholder -- Wait ~60s for RBAC propagation (AcrPull role) before code deploy -- BuildKit Dockerfiles: create Dockerfile.azure without --mount syntax -- Pass real image on EVERY Bicep redeploy: --parameters containerImage='{acr}/{app}:latest' -- Native CA secrets: `revision restart` does NOT refresh — must create new revision -- ACR build failures count toward healing counter -- Windows: append `--no-logs` to `az acr build` to avoid UnicodeEncodeError -- ⛔ After the revision is ready, run an explicit live HTTP probe against the ingress FQDN (`curl -sSfL`/`iwr` on `*.azurecontainerapps.io`) — this call IS the health check; capture the result into `deploy-result.json.endpoints[].healthStatus`. - -## Code deploy — Static Web Apps (delete if not using SWA) -- ⛔ **Build before deploy (SPA only):** If the SWA component has a manifest (`package.json`) with a `build` script: detect the package manager from the lockfile, run install + build, then deploy the build output directory (not raw source). Plain HTML repos (no manifest): skip build, deploy source directly. -- ⛔ **Build-time env vars:** Before `npm run build`, set `VITE_API_BASE_URL` / `NEXT_PUBLIC_API_URL` / `REACT_APP_API_URL` to the deployed backend URL from `deploy-result.json.endpoints[]`. These are baked into the JS bundle at build time — runtime SWA env vars have no effect on client-side code. -- If frontend config references cloud SDK endpoints (AWS API Gateway, GCP), update with deployed Azure backend URLs from `deploy-result.json.endpoints[]` before `swa deploy` -- Use `swa deploy` (NOT `az staticwebapp deploy` — doesn't exist) -- `--app-name {swaName}` is mandatory -- Store token in $env:SWA_CLI_DEPLOYMENT_TOKEN — never as CLI arg - -## During healing / retries -- ⛔ REGION LOCK: Deploy region MUST match plan region ({region}). Any region change → RE-PRESENT deploy approval gate with old and new region. Do NOT silently switch. After approval: update `prepare-plan.json.services[].region`, `deploymentVariables.location`, AND append attempt number to `naming.suffix` (e.g., `edd6` → `edd602`). Recompute ALL resource names from the new suffix before redeploying — globally unique names (App Service, Storage, ACR) from the old region may be soft-deleted and unavailable. -- ⛔ IaC-only: NEVER use `az containerapp update --image`, `az webapp update`, `az appservice plan delete`, or `az group create` — fix the Bicep and redeploy via `az deployment sub create` -- ⛔ IaC-only for app-managed roles: NEVER `az role assignment create` for AcrPull or KV Secrets User — Bicep-managed (deterministic GUID), so an imperative grant collides on redeploy (`RoleAssignmentExists`). Missing app role = fix the Bicep module and redeploy. (Deployer/subscription-scope 403s are the ONLY exception — see [`error-classification.md`](error-classification.md).) -- ⛔ **On error: read [`error-classification.md`](error-classification.md)** to classify the failure and follow the prescribed remediation. Do NOT ad-hoc heal without reading the classification. -- ⛔ **Never weaken a security control to unblock** — do NOT flip `require_secure_transport`/TLS, HTTPS-only, KV purge protection, or auth OFF to make a failing deploy pass. A DB TLS handshake failure = fix the client SSL config (prereq `W-MYSQL-SSL`) or ask the user; never downgrade the server. -- Count ALL attempts in deploy-result.json.healingAttempts[] - After 3: STOP and ask user ("Yes / I have a suggestion / Stop") -- NEVER run `az group delete` — track in orphanedResourceGroups[] (run `capture_deployment_inventory` after the healing attempt so the abandoned RG's resources are recorded deterministically) -- ⛔ **RG deletion timeout:** If you ran `az group delete --no-wait`, wait max 2 minutes then `ask_user`: "Resource group deletion is slow. Wait longer / Proceed without cleanup / Cancel." Do NOT poll indefinitely. -- Region/SKU/service changes require re-approval gate - -## Before handoff (Step 8) -- ⛔ Read [`deploy-schemas.ts`](deploy-schemas.ts) for exact DeployResult field names -- ⛔ Run a final `capture_deployment_inventory` (`phase: "capture"`) — then populate `createdResources[]` and `orphanedResourceGroups[]` from its returned output (do NOT hand-author them) -- Finalize `deploy-result.json` — overwrite skeleton IN PLACE (keep exact field names, do NOT rename): status (lowercase `succeeded`/`failed`), resourceGroupName, subscriptionId, deploymentNames (all used), resourceIds, endpoints, healthStatus (worst across endpoints), duration.completedUtc, resourceResults from `az deployment operation list`, createdResources + orphanedResourceGroups from the inventory. Read back to verify. -- ⛔ `deployment-summary.md` — generate from `deploy-result.json` fields (Status, Health, Portal Links, Cleanup). NOT a separate data source. -- ⛔ `context.json` — add "deploy" to completedPhases, set currentPhase to null, update lastModifiedUtc. VERIFY by reading back. -- SCM re-disabled (App Service) or image param set (Container Apps) -- If prereq found migration frameworks: run migrations before declaring healthy - -## Artifact verification (Step 8 — MANDATORY) -⛔ Before returning to orchestrator, verify ALL artifacts exist by reading each one back: -1. `deploy-result.json` — MUST contain (exact names): `status` (lowercase `succeeded`/`failed`), `resourceGroupName`, `subscriptionId`, `deploymentNames[]`, `resourceIds[]`, `endpoints[]`, `healthStatus`, `duration.completedUtc`, `resourceResults[]`, `createdResources[]`, `orphanedResourceGroups[]`. `createdResources[]`/`orphanedResourceGroups[]` come from a final `capture_deployment_inventory` (`phase: "capture"`) — if empty or missing, run it NOW and write its output. Missing/renamed fields → rewrite with real values NOW -2. `deploy-audit.log` — MUST exist with ≥2 entries (started + result for at least 1 command). Missing → reconstruct from memory -3. `deployment-summary.md` — MUST contain Status, Health, Portal Links sections. Missing → generate from deploy-result.json -4. `context.json` — MUST have `"deploy"` in `completedPhases`, `currentPhase: null`, updated `lastModifiedUtc` -5. ⛔ **Endpoint completeness** — EVERY service in `prepare-plan.json.services[]` that hosts application code MUST have a corresponding entry in `deploy-result.json.endpoints[]` with code deployed and a valid `healthStatus` (`healthy`, `degraded`, `unreachable`, `unknown`). If ANY compute endpoint is missing or has code not deployed, set `partial: true` and `status: "failed"`. A deployment with undeployed user components is NOT `"succeeded"`. - -If ANY artifact is missing or incomplete, write it NOW — do NOT return to orchestrator without all 5 checks passing. - -⛔ **Then STOP — return to orchestrator. No further CLI commands or agent invocations.** -``` diff --git a/.github/agents/azure-deploy/deploy/references/deploy-safety.md b/.github/agents/azure-deploy/deploy/references/deploy-safety.md deleted file mode 100644 index f74e3b5..0000000 --- a/.github/agents/azure-deploy/deploy/references/deploy-safety.md +++ /dev/null @@ -1,66 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Deploy Safety - -Hook-based safety rules for the deploy phase. Block destructive operations. - -## deploy-result.json Skeleton - -Created by scaffold validate sub-agent. If missing at deploy Step 5b, create from [`deploy-schemas.ts`](deploy-schemas.ts) with `status: "in-progress"`. Append to `deploymentNames[]` on each healing retry. - -## Deploy Checklist (compaction-safe — generated at Step 5b) - -> ⛔ **You MUST read [`deploy-checklist-template.md`](deploy-checklist-template.md)** at Step 5b to generate the checklist. Write the result to `.copilot-azure/sessions/{id}/deploy-checklist.md`. Re-read the generated checklist after every long-running command, failed health check, and conversation compaction. - -## Finalize deploy-result.json (Step 8) - -Overwrite the skeleton with real values: -- `status` → `"succeeded"` or `"failed"` -- `deploymentNames` → ALL names used (initial + retries) -- `healthStatus` → worst across endpoints -- `duration.completedUtc` → now -- `resourceResults` → one entry per resource from `az deployment operation list` - -## Blocked Patterns - -> ⛔ **You MUST read [`blocked-patterns.md`](blocked-patterns.md)** before running ANY `az` command during the deploy phase. This file contains every command the agent is forbidden from executing. Block decisions are non-negotiable — user must run blocked commands manually outside AppOnboard. - -## 403 Scope Fallback - -When `az deployment sub create` returns 403 (insufficient subscription-scope permissions), do NOT halt immediately: - -1. **Restructure Bicep to RG-scope** — change `targetScope = 'subscription'` to resource-group scope, remove the `Microsoft.Resources/resourceGroups` resource. -2. **Create the RG via CLI** — `az group create -n {rg} -l {region} --tags app-onboard-skill=true app-onboard-session-id={sessionId} created-at={createdAt} environment={environmentName} deployed-by={deployedBy}`. All 5 AppOnboard tags MUST be included. -3. **Retry with `az deployment group create`** — use `--resource-group {rg}` instead of subscription scope. -4. **Regenerate portal link** for RG-scope — `$resId` must include `/resourceGroups/{rg}`: `$resId = "/subscriptions/{subscriptionId}/resourceGroups/{rg}/providers/Microsoft.Resources/deployments/$deploymentName"`. Re-run `Write-Output "LINK=$l"` and print the new bare URL. ⛔ Do NOT auto-open it (no `Start-Process`). -5. **If retry ALSO fails with 403** → classify as `ENVIRONMENT_BLOCKING`. Surface required role: `az role assignment create --role Contributor --assignee {user} --scope /subscriptions/{sub}/resourceGroups/{rg}`. - -## Deploy Checklist - -> ⛔ **Use sync shells** so state persists. **App-internal secrets only** (e.g. `SECRET_KEY`, JWT signing key, third-party API keys) are generated at deploy and passed as `@secure()` params → stored on the compute resource (App Service app settings / Container Apps native secrets). ⛔ **No Key Vault.** ⛔ **There are NO database/cache/storage passwords or access keys** — those services are managed-identity + token (see [database-post-deploy.md](database-post-deploy.md)). Persist any app-internal secret to `.copilot-azure/sessions/{id}/deploy-secrets.env` — generate each ONCE (URL-safe, no `/+=`), reload in every later shell, and pass to EVERY deployment. Never regenerate an existing key (the desired-state apply would overwrite the live value). The file is a git-ignored cross-shell reload cache. NEVER echo or log rendered secret values. -> -> ⛔ **URL-safe app-internal secrets** when embedded in URLs. Forbidden chars: `# @ / ? % : & = + ;`. -> ⛔ **`az webapp deploy` does NOT support `--track-status`.** -> ⛔ **`az rest` on Windows PowerShell:** ALWAYS include `--headers "Content-Type=application/json"`. -> ⛔ **Suppress deployment output:** Add `--query properties.provisioningState -o tsv` to deployment commands. For `az acr build`, append `--no-logs`. - -## Post-Deploy Tag Verification - -After deployment, verify all 5 AppOnboard tags: `az group show -n {rg} --query tags -o json`. Re-apply missing via `az tag update`. - -## Deployment Operation Polling - -For deployments with >5 resources, poll every 30s: `az deployment operation list --name {name} --subscription {sub} --query "[?properties.provisioningState=='Failed']" -o table`. Wait for FULL completion before healing — collect ALL errors in one pass. - -## Re-Approval Gates - -Region, service type, or SKU changes from user-approved values → re-present approval gate. Resource name changes → informational only. Same-deployment retries → no re-approval. - -## Antipatterns - -⛔ Do NOT `az group delete --no-wait` then `az group create` same name — background deletion takes 5-15 min and destroys the new RG. Use a different name or wait: `az group wait --name {rg} --deleted --timeout 900`. - -## Artifact Reconciliation After Healing - -After ANY healing that changes deployed resources, update `prepare-plan.json`, `scaffold-manifest.json`, and `context.json` to reflect actual state. Track orphaned RGs in `deploy-result.json.orphanedResourceGroups[]` immediately when switching to a new RG. diff --git a/.github/agents/azure-deploy/deploy/references/deploy-schemas.ts b/.github/agents/azure-deploy/deploy/references/deploy-schemas.ts deleted file mode 100644 index 8c125ed..0000000 --- a/.github/agents/azure-deploy/deploy/references/deploy-schemas.ts +++ /dev/null @@ -1,150 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/** - * Deploy artifact schema — deploy-result.json. - * Read by deploy instructions.md Step 8 (finalize artifacts). - */ - -export type HealthStatus = "healthy" | "degraded" | "unreachable" | "unknown"; - -// ─── Deploy healing types ──────────────────────────────────────────────────── - -export type DeployErrorClassification = "IAC_ERROR" | "INFRA_TRANSIENT" | "ENVIRONMENT_BLOCKING"; -export type DeployHealingPhase = "validation" | "deployment"; - -/** Tracks a resource group created during a healing attempt that is no longer - * the final deployment target. Surfaced at handoff for manual cleanup. */ -export interface OrphanResourceGroup { - /** Azure resource group name */ - name: string; - /** Region where the RG was created. Omitted when derived by the deterministic - * `capture_deployment_inventory` diff, which doesn't track a region. */ - region?: string; - /** Which healing attempt created or targeted this RG. Omitted when derived by the - * deterministic `capture_deployment_inventory` diff rather than a tracked healing attempt. */ - healingAttempt?: number; - /** Why this RG was abandoned (e.g., "region fallback to westus2") */ - reason: string; -} -export type DeployHealingAction = "routed-to-scaffold" | "retried" | "surfaced-to-user"; -export type DeployHealingResult = "fixed" | "still-failing" | "blocked"; - -// ─── Deterministic resource inventory (capture_deployment_inventory) ───────── - -/** How a resource created during this session relates to the tracked deployment(s). - * Computed deterministically by the `capture_deployment_inventory` MCP tool from a - * before/after `resources.list()` diff — NOT inferred from chat history. */ -export type CreatedResourceClassification = - /** Reported `Succeeded` by a tracked deployment and located in the final target RG. - * Part of the working deployment — never a cleanup candidate. */ - | "expected" - /** Reported by a tracked deployment but with a non-succeeded provisioning state. - * Confirmed to belong to this deployment, so a delete command may be offered. */ - | "failed" - /** Appeared during the deploy window but no tracked deployment reported it, or it landed - * outside the final target RG. Probably a healing retry or an imperative `az` fallback — - * but on a shared subscription it may belong to someone else entirely. Surface for the - * user to REVIEW; never describe it as safe to delete. */ - | "orphaned" - /** The tracked deployment's ARM operations could not be read (permissions, throttling, - * transient failure), so nothing could be attributed. Never a cleanup candidate. */ - | "unverified"; - -/** A resource that exists now because of this session (post − baseline diff). */ -export interface CreatedResource { - /** Full ARM resource ID. */ - id: string; - name?: string; - type?: string; - /** Resource group parsed from the ARM ID. */ - resourceGroup?: string; - /** Provisioning state reported by the deployment operations, when known. */ - provisioningState?: string; - classification: CreatedResourceClassification; -} - -export interface DeployHealingError { - source: string; - detail: string; - classification: DeployErrorClassification; -} - -export interface DeployHealingAttempt { - attempt: number; - phase: DeployHealingPhase; - errors: DeployHealingError[]; - action: DeployHealingAction; - result: DeployHealingResult; - /** Resource group targeted by this attempt — useful for audit and debugging */ - resourceGroupName?: string; - /** True when this healing attempt changed the service type or region — requires re-approval */ - planLevelChange?: boolean; - /** What changed: "service-type", "region", "sku" */ - changeType?: "service-type" | "region" | "sku"; - /** Original value before the change (e.g., "App Service B1 eastus") */ - originalValue?: string; - /** New value after the change (e.g., "Container Apps Consumption eastus") */ - newValue?: string; -} - -// ─── deploy-result.json ────────────────────────────────────────────────────── - -export interface DeployEndpoint { - name: string; - url: string; - healthStatus: HealthStatus; -} - -export interface DeployDuration { - startedUtc: string; - completedUtc: string; -} - -export type DeployStatus = "in-progress" | "succeeded" | "failed"; - -export type ResourceDeployStatus = "succeeded" | "failed" | "skipped"; - -export interface ResourceResult { - resourceId: string; - type: string; - status: ResourceDeployStatus; - error?: string; -} - -export interface DeployResult { - sessionId: string; - /** Azure subscription ID from context.json.azure.subscriptionId */ - subscriptionId: string; - /** All ARM deployment names used during this session (initial + healing retries). - * First entry is the initial deployment; subsequent entries are from scope/RG changes. */ - deploymentNames: string[]; - resourceGroupName: string; - status: DeployStatus; - resourceIds: string[]; - endpoints: DeployEndpoint[]; - healthStatus: HealthStatus; - duration: DeployDuration; - warnings: string[]; - partial: boolean; - resourceResults: readonly ResourceResult[]; - /** Resources that exist now because of this session, computed deterministically by - * `capture_deployment_inventory` (before/after `resources.list()` diff). Includes - * expected, failed, and orphaned resources — the source of truth for the handoff - * cleanup section. Populated at Step 8 and on the Step 9 failure path. */ - createdResources: readonly CreatedResource[]; - /** RGs created during healing that are not the final deployment target. - * Derived from `createdResources` (resources whose RG != the final target RG). - * Surfaced at handoff (Step 9) with manual cleanup commands. */ - orphanedResourceGroups: readonly OrphanResourceGroup[]; - /** Set when `capture_deployment_inventory` could not read the deployment's ARM operations, - * so `createdResources` could not be attributed. When true, present NO cleanup list and - * tell the user to review the resource group in the portal. */ - inventoryUnverified?: boolean; - /** Why verification failed: `"forbidden"` (missing - * `Microsoft.Resources/deployments/operations/read`), `"throttled"`, or `"error"`. */ - inventoryUnverifiedReason?: string; - healingAttempts?: readonly DeployHealingAttempt[]; -} diff --git a/.github/agents/azure-deploy/deploy/references/error-classification.md b/.github/agents/azure-deploy/deploy/references/error-classification.md deleted file mode 100644 index a9378b2..0000000 --- a/.github/agents/azure-deploy/deploy/references/error-classification.md +++ /dev/null @@ -1,62 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Error Classification - -Three error categories for deploy-time failures. - -## Multi-Error Triage - -> ⛔ When 3+ errors, classify ALL before fixing ANY. Group by root cause. Present: "Deploy failed with {N} errors from {M} root causes." - -## Categories - -### `IAC_ERROR` — Route Back to Scaffold - -| Example | Fix | -|---------|-----| -| Invalid property name | Call `mcp_bicep_build_bicep` with `{ filePath: "infra/main.bicep" }` for structured error details, then fix. Fallback: `az bicep build` | -| Wrong SKU for region | Substitute from `rejectedAlternatives[]` | -| Missing required field | Call `mcp_bicep_build_bicep` for structured error, fix from diagnostics. Fallback: `az bicep build` | -| Policy violation | Substitute per policy | -| API version not found | Call `mcp_bicep_list_az_resource_types_for_provider` with `{ providerNamespace: "..." }`. Use latest GA — no `-preview`. Fallback: `az provider show` | -| `listKeys()` in output | ⛔ Security risk — replace with managed-identity access (no shared keys, no Key Vault) | -| Redis `InvalidRequestBody` for `properties.sku.name` | Bicep type issue — create via `az redis create --sku Basic --vm-size c0`, switch Bicep to `existing` keyword | - -**Flow:** Deploy → classifies IAC_ERROR → scaffold self-healing → re-validate → retry. - -### `INFRA_TRANSIENT` — Retry with Backoff - -| Example | Strategy | -|---------|----------| -| ARM 429, 409 conflict, RBAC delay, network timeout | 30s → 60s → 120s (3 max) | -| Container Apps `Operation expired` | Check root cause first: image pull failure, port mismatch, health probe timeout, crash loop. Port mismatch → IAC_ERROR. Image pull → retry. Crash → ENVIRONMENT_BLOCKING. | - -After 3 failures → escalate to user. - -### `ENVIRONMENT_BLOCKING` — Surface to User - -| Example | Action | -|---------|--------| -| 403 on sub-scope deploy | ⛔ Try [403 Scope Fallback](deploy-safety.md) first (RG-scope). Only block if retry also fails | -| 403 on RG-scope | Surface: `az role assignment create --role Contributor --assignee {objectId} --scope {rg}` | -| AuthorizationFailed / deny assignment | Get user objectId, suggest role assignment command. Deny assignments → contact admin | -| Quota exhausted | ⛔ PLAN_LEVEL_CHANGE — HALT, present region fallback with re-approval | -| Region doesn't support resource | ⛔ PLAN_LEVEL_CHANGE — same as quota | -| `LocationIsOfferRestricted` | ⛔ PLAN_LEVEL_CHANGE — read `quotaValidation.offerRestrictions[]` for unblocked regions | -| MI sidecar OOM on F1/B1 | Upgrade SKU, remove MI, or switch to Container Apps | -| AADSTS530084 / TF auth failure | Re-scaffold as Bicep + `az deployment group create`. Never fall back to imperative CLI | - -> ⛔ **During ALL healing: NEVER run `az group delete`, `az postgres flexible-server delete`, `az redis delete`, `az webapp delete`, or any destructive resource deletion.** Track failed RGs in `deploy-result.json.orphanedResourceGroups[]` instead. The user deletes at handoff. If you need a clean region retry, use a NEW RG name (append `-2`) — do NOT delete-and-recreate. ⛔ **After each failed/healing `az deployment`, call `capture_deployment_inventory` (`phase: "capture"`, passing every `resourceGroups[]` including the abandoned one)** so orphaned resources from the healing retry are recorded deterministically rather than left for you to remember. - -## Healing Trace - -Log to `deploy-result.json.healingAttempts[]`: `{ attempt, phase, errors: [{ source, detail, classification }], action, result, planLevelChange, changeType, originalValue, newValue }`. - -> ⛔ **Repeat failure (same error 2+):** Read [iac-resources.md](../../references/iac-resources.md) § Deploy Troubleshooting and `fetch_webpage` the matching URL. - -After 3 failed cycles: write `partial: true`, surface remaining errors. Do NOT auto-rollback. - -## Known Platform Bugs / Deploy Timing - -See [`pipeline-rules-runtime.md`](../../references/pipeline-rules-runtime.md). diff --git a/.github/agents/azure-deploy/deploy/references/health-check-patterns.md b/.github/agents/azure-deploy/deploy/references/health-check-patterns.md deleted file mode 100644 index ddacf08..0000000 --- a/.github/agents/azure-deploy/deploy/references/health-check-patterns.md +++ /dev/null @@ -1,98 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Health Check Patterns - -Post-deployment health verification for AppOnboard-deployed resources. - -## HTTP Endpoints - -For each endpoint: HTTPS GET, 30s timeout, 3 retries (10s/20s/40s backoff). - -| Status | Health | Note | -|--------|--------|------| -| 2xx | `healthy` | **Verify not a placeholder page (see below)** | -| 401/403 | `healthy` | Auth working, app running | -| 5xx ×3 | `degraded` | | -| Timeout/DNS ×3 | `unreachable` | | - -> ⛔ **DB-backed apps: a 200 on `/` is NOT healthy.** When `prepare-plan.json.services[]` includes a database, probing only `/` (or any non-DB route) just proves the web server booted. Probe at least one **data-backed route** (derive from the app's detected routes, e.g. a REST resource path) and inspect the body for DB errors (`insecure transport`, `Access denied`, `connection refused`, `Unknown database`, `doesn't exist`) → mark `degraded`, not `healthy`. - -### HTTP Redirect Handling (Container Apps) - -> ⛔ **ACA health probes do NOT follow HTTP redirects.** A 301/302 response from the probe path causes `ActivationFailed` — the probe treats it as a failure, not a redirect. - -If the first health check returns **301 or 302**: -1. Read the `Location` header: `curl -sI "https://{fqdn}{probePath}" | Select-String "^location:" -CaseSensitive:$false` -2. Update `probePath` in Bicep to the redirect target (e.g., `/wetty/` → `/wetty`) -3. Redeploy: `az deployment sub create` with updated Bicep -4. Re-check health after new revision activates - -Common redirect patterns: Express trailing-slash normalization (`/app/` → `/app`), framework-level path canonicalization, HTTPS redirects on mixed-content paths. - -### App Service Default Page Detection - -> ⛔ **HTTP 200 ≠ app started.** Azure serves its own default page with 200 when the app fails to start — false positive. - -After HTTP 200 from App Service, check first 2KB of body: - -| Body contains | Meaning | -|---------------|--------| -| `"Your app service is up and running"` | Default page — app didn't start | -| `"Time to take the next step and deploy your code"` | Default page — no code or app failed | -| `"Hey, Python developers!"` / `"Hey, Node.js developers!"` | Runtime default — app didn't start | -| `"Error 503"` / `"Application Error"` | App crashed on startup | - -If detected → `healthStatus: "degraded"` + warning: `"App Service default page detected — check logs: az webapp log tail -g {rg} -n {app}"`. - -## Non-HTTP Resources - -```bash -az resource show --ids {resourceId} --query "properties.provisioningState" -o tsv -``` - -`Succeeded` → `healthy`. `Failed` → `degraded`. Other → `unknown`. - -| Service | Health Signal | -|---------|---------------| -| Container Apps | `latestReadyRevisionName` not empty + HTTP on ingress FQDN | -| App Service | HTTP GET `https://{name}.azurewebsites.net/` + `/health` | -| Static Web Apps | HTTP GET `https://{defaultHostname}/` → 2xx = `healthy` (hostname: `az staticwebapp show -n {swa} -g {rg} --query defaultHostname -o tsv`) | -| Azure SQL | `provisioningState` + `az sql db show` | -| Cosmos DB | `provisioningState` | -| Storage | `provisioningState` + `statusOfPrimary` | -| Functions | HTTP trigger URL + HTTP check | - -> ⛔ **Container Apps — run an explicit live HTTP probe (the pipeline status is NOT sufficient).** After `latestReadyRevisionName` is set, run an observable request against the ingress FQDN and capture the result into `deploy-result.json.endpoints[].healthStatus`: -> ```powershell -> iwr "https://{ingressFqdn}/{probePath}" -UseBasicParsing # PowerShell -> curl -sSfL "https://{ingressFqdn}/{probePath}" # bash -> ``` -> This live HTTP call against `*.azurecontainerapps.io` IS the health verification — do NOT infer health from the revision's internal status alone. - -## Output - -Write to `deploy-result.json.endpoints[]`: - -```jsonc -{ - "name": "api", - "url": "https://myapp-ca-dev-a1b2.azurecontainerapps.io", - "healthStatus": "healthy" // healthy | degraded | unreachable | unknown -} -``` - -Overall `healthStatus` = worst status across all endpoints. If any `unreachable` → overall `unreachable`. - -## Functional Endpoint Verification - -> ⛔ **A 200 on `/` only proves the web server booted — not that the app works.** After the HTTP check, confirm the app actually functions against the services the plan provisioned (database, cache), not just that it responds. - -Health checks only confirm the web server is responding. Exercise a route that depends on the backing services — for example: - -| Pattern | Functional Check | -|---------|-----------------| -| Database in the plan (MySQL/PostgreSQL/SQL/Cosmos) | Probe a route that reads/writes the DB (a detected app route, NOT `/` — root often serves a static page with no DB access, so 200 on `/` masks broken DB connectivity). A 5xx or a DB error in the body (`connection refused`, `does not exist`, token/auth failure) → `degraded` — usually the app MI wasn't granted a DB role (see [database-post-deploy.md](database-post-deploy.md) § 2). | -| `FIRST_SUPERUSER` env var or `prestart.sh`/`init_db()` | After health passes, attempt login endpoint. If 401/500 → startup scripts may have failed. Trigger `az containerapp revision restart` to re-run startup. | -| Migration frameworks (Alembic, Django, Prisma, EF) | After health passes, check `prereq-output.json` for migration signals. If found, run migrations per [database-post-deploy.md](database-post-deploy.md). | -| Two-phase Container Apps (managed identity) | Wait 60s after Phase 2 for AcrPull RBAC propagation. If the DB fails with auth/token errors, the app MI may not yet have its DB role — grant it (see [database-post-deploy.md](database-post-deploy.md)) and create a new revision. | diff --git a/.github/agents/azure-deploy/deploy/references/mcp-tools.md b/.github/agents/azure-deploy/deploy/references/mcp-tools.md deleted file mode 100644 index bbebab4..0000000 --- a/.github/agents/azure-deploy/deploy/references/mcp-tools.md +++ /dev/null @@ -1,104 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Deploy Phase — MCP Tools - -Phase-exclusive tool parameters for the deploy phase. For shared tools (`subscription_list`, `group_list`, `extension_cli_install`, `get_azure_bestpractices`), see [mcp-tool-reference.md](../../references/mcp-tool-reference.md). - -> **Troubleshooting:** If a tool call fails with unknown parameter or missing command errors, consult the official docs: - ---- - -## `capture_deployment_inventory` (Copilot on Rails extension tool) - -Deterministic resource-inventory capture provided by the Azure Resources extension (in-proc MCP, `copilot-azure-resources-extension-tools/*`). Snapshots the subscription's resources via ARM and diffs before/after to record exactly what this session created. Holds the baseline in memory (no workspace files). **Report-only — never deletes.** - -| Param | Required | Description | -|-------|----------|-------------| -| `sessionId` | ✅ | App Onboard session id; locates `.copilot-azure/sessions/{id}/`. | -| `subscriptionId` | ✅ | Target subscription. | -| `phase` | ✅ | `"baseline"` (before first deployment) or `"capture"` (after each attempt / on failure / at finalize). | -| `expectedResourceGroup` | — | Final target RG; created resources outside it are flagged `orphaned`. | -| `deploymentNames` | — | All ARM deployment names (initial + healing) used to classify created resources. | -| `resourceGroups` | — | RGs touched (incl. abandoned healing RGs) so RG-scoped deployment operations are read. | - -Holds the baseline snapshot in memory (no files written). On `phase: "capture"` returns the created resources classified `expected`/`failed`/`orphaned`/`unverified` plus orphaned resource groups; write those into `deploy-result.json.createdResources[]`/`orphanedResourceGroups[]`. - -⛔ **Classification confidence differs, and your wording to the user MUST reflect it:** - -| Classification | Meaning | How to present it | -|---|---|---| -| `expected` | Tracked deployment reported it as `Succeeded` in the target RG. | Part of the working deployment. Never suggest deleting it. | -| `failed` | Tracked deployment reported it with a non-succeeded state. | Confirmed to belong to this deployment. Safe to offer a delete command. | -| `orphaned` | Appeared during the deploy window but **no tracked deployment reported it**. | It *may* be a healing/imperative stray — or another person's resource on a shared subscription. Present as "review before deleting", never as "safe to delete" or "created by this deployment". | -| `unverified` | Deployment operations could not be read at all, so nothing could be attributed. | Say the inventory could not be verified and point the user at the portal. Do **not** produce any cleanup list or delete command. | - -When the tool returns `inventoryUnverified: true`, record it in `deploy-result.json` as `inventoryUnverified` + `inventoryUnverifiedReason` and skip the cleanup section entirely. - -See the [Phase 4 Tool Map](#phase-4-tool-map) below and [`../instructions.md`](../instructions.md) Steps 5b/6/8/9. - ---- - -## `mcp_azure_mcp_deploy` (hierarchical — deploy-phase sub-commands) - -| Sub-command | Required Params | Optional Params | Read-Only | -|-------------|----------------|-----------------|-----------| -| `deploy_app_logs_get` | `workspace-folder`, `azd-env-name` | `limit` (default 200) | ✅ | -| `deploy_architecture_diagram_generate` | `workspaceFolder`, `services[]` (complex array with name, path, language, port, azureComputeHost, dependencies, settings) | `projectName` | ✅ | - -## `mcp_azure_mcp_resourcehealth` (hierarchical) - -| Sub-command | Required Params | Optional Params | Read-Only | -|-------------|----------------|-----------------|-----------| -| `resourcehealth_availability-status_get` | *(none)* | `subscription`, `resourceId` (full ARM ID), `resource-group` | ✅ | -| `resourcehealth_health-events_list` | *(none)* | `subscription`, `event-type` (ServiceIssue\|PlannedMaintenance\|HealthAdvisory\|Security), `status` (Active\|Resolved), `tracking-id`, `filter`, `query-start-time`, `query-end-time` | ✅ | - -## `mcp_azure_mcp_monitor` (hierarchical) - -| Sub-command | Required Params | Optional Params | Read-Only | -|-------------|----------------|-----------------|-----------| -| `monitor_activitylog_list` | `resource-name` | `resource-group`, `resource-type`, `hours`, `event-level` (Critical\|Error\|Informational\|Verbose\|Warning), `top`, `subscription` | ✅ | -| `monitor_metrics_query` | `resource`, `metric-names` (comma-sep), `metric-namespace` | `resource-group`, `resource-type`, `start-time`, `end-time`, `interval`, `aggregation` (Average\|Maximum\|Minimum\|Total\|Count), `filter`, `max-buckets` | ✅ | -| `monitor_metrics_definitions` | `resource` | `resource-group`, `resource-type`, `metric-namespace`, `search-string`, `limit` | ✅ | -| `monitor_workspace_list` | *(none)* | `subscription` | ✅ | -| `monitor_workspace_log_query` | `resource-group`, `workspace`, `table`, `query` (KQL or "recent"\|"errors") | `hours`, `limit`, `subscription` | ✅ | -| `monitor_resource_log_query` | `resource-id` (full ARM ID), `table`, `query` | `hours`, `limit`, `subscription` | ✅ | - -## `mcp_azure_mcp_appservice` (hierarchical) - -| Sub-command | Required Params | Optional Params | Read-Only | -|-------------|----------------|-----------------|-----------| -| `appservice_webapp_get` | *(none)* | `app`, `resource-group`, `subscription` | ✅ | -| `appservice_webapp_deployment_get` | `resource-group`, `app` | `deployment-id`, `subscription` | ✅ | -| `appservice_webapp_diagnostic_diagnose` | `resource-group`, `app`, `detector-name` (Availability\|CpuAnalysis\|MemoryAnalysis) | `start-time`, `end-time`, `interval`, `subscription` | ✅ | -| `appservice_webapp_diagnostic_list` | `resource-group`, `app` | `subscription` | ✅ | -| `appservice_webapp_settings_get-appsettings` | `resource-group`, `app` | `subscription` | ✅ (⚠️ secret) | -| `appservice_database_add` | `resource-group`, `app`, `database-type` (SqlServer\|MySQL\|PostgreSQL\|CosmosDB), `database-server`, `database` | `connection-string`, `subscription` | ❌ | -| `appservice_webapp_settings_update-appsettings` | `resource-group`, `app`, `setting-name`, `setting-update-type` (add\|set\|delete) | `setting-value`, `subscription` | ❌ (destructive) | - -## `mcp_azure_mcp_role` (hierarchical) - -| Sub-command | Required Params | Optional Params | Read-Only | -|-------------|----------------|-----------------|-----------| -| `role_assignment_list` | `scope` (ARM scope path) | `subscription` | ✅ | - ---- - -## Phase 4 Tool Map - -| Tool | Sub-command | AppOnboard Step | Purpose | -|------|-----------|----------|---------| -| `capture_deployment_inventory` | *(baseline)* | Step 5b | Snapshot pre-existing resources before the first deployment | -| `capture_deployment_inventory` | *(capture)* | Steps 6, 8, 9 | Diff resources.list() to record created/orphaned/failed resources; build cleanup commands from `failed` entries only | -| `mcp_azure_mcp_resourcehealth` | `resourcehealth_availability-status_get` | Step 7 | Post-deploy health. Call with `resourceId` from `deploy-result.json.resourceIds[]` | -| `mcp_azure_mcp_resourcehealth` | `resourcehealth_health-events_list` | Step 3 | Pre-deploy outage check. `event-type: "ServiceIssue"`, `status: "Active"` | -| `mcp_azure_mcp_monitor` | `monitor_activitylog_list` | Step 7+ | Failed deployment analysis. `resource-name` from deploy output, `event-level: "Error"`, `hours: 1` | -| `mcp_azure_mcp_monitor` | `monitor_metrics_query` | Step 7 | Performance validation. Requires `metric-namespace` from `monitor_metrics_definitions` first | -| `mcp_azure_mcp_appservice` | `appservice_webapp_get` | Step 7 | Verify App Service state, hostnames, runtime | -| `mcp_azure_mcp_appservice` | `appservice_webapp_deployment_get` | Step 7 | Verify deployment completed successfully | -| `mcp_azure_mcp_appservice` | `appservice_webapp_diagnostic_diagnose` | Step 7 | Run `Availability` detector on deployed app | -| `mcp_azure_mcp_deploy` | `deploy_app_logs_get` | Step 7 | App logs for azd-deployed apps only. Requires `workspace-folder` + `azd-env-name` | -| `mcp_azure_mcp_role` | `role_assignment_list` | Step 3 | Preflight RBAC check. `scope: "/subscriptions/{sub}/resourceGroups/{rg}"` | -| `mcp_azure_mcp_subscription_list` | *(flat)* | Step 1 | Resolve target subscription | -| `mcp_azure_mcp_group_list` | *(flat)* | Step 3 | Verify RG exists before deploy | -| `mcp_azure_mcp_extension_cli_install` | *(flat)* | Step 3 | Ensure az/azd/func CLI installed | diff --git a/.github/agents/azure-deploy/deploy/references/portal-links.md b/.github/agents/azure-deploy/deploy/references/portal-links.md deleted file mode 100644 index 6b71066..0000000 --- a/.github/agents/azure-deploy/deploy/references/portal-links.md +++ /dev/null @@ -1,54 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Portal Monitoring Links - -Generate the portal link BEFORE deploying — the deployment name is deterministic, so the link works before the deployment even starts. The user wants to watch resources being created in real time. - -## Generate via PowerShell — NEVER construct manually - -The `%2F` encoding is critical and models consistently decode it back to `/` during text generation, producing broken links. Always use `.Replace('/', '%2F')`. - -```powershell -# Subscription-scope deployment -$deploymentName = "app-onboard-deploy-$("{sessionId}".Substring(0,8))" -$resId = "/subscriptions/{subscriptionId}/providers/Microsoft.Resources/deployments/$deploymentName" -$link = "https://portal.azure.com/#view/HubsExtension/DeploymentDetailsBlade/~/overview/id/$($resId.Replace('/', '%2F'))" -Write-Output "LINK=$link" -``` - -Read `LINK=` from terminal output and print the URL in chat on its own bare line — no backticks, no markdown. Then deploy: -```powershell -az deployment sub create --name $deploymentName --subscription {subscriptionId} --location {location} --template-file infra/main.bicep --parameters @infra/main.parameters.json -``` - -### RG-scope (403 fallback) - -Use `az deployment group create --name $deploymentName --resource-group {rg}` and adjust `$resId` to include `/resourceGroups/{rg}`: -```powershell -$resId = "/subscriptions/{subscriptionId}/resourceGroups/{rg}/providers/Microsoft.Resources/deployments/$deploymentName" -$link = "https://portal.azure.com/#view/HubsExtension/DeploymentDetailsBlade/~/overview/id/$($resId.Replace('/', '%2F'))" -Write-Output "LINK=$link" -``` - -For Terraform (no single ARM deployment): `https://portal.azure.com/#@{tenantId}/resource/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/activitylog` - -> 💡 Resolve `{subscriptionId}` and `{resourceGroup}` from `context.json`. For Terraform, resolve `{tenantId}` via `az account show --query tenantId -o tsv`. - -## Same-Scope Retries vs New Names - -The portal link stays valid for same-scope retries — ARM overwrites in-place. Generate a new name (e.g., `$deploymentName = "app-onboard-deploy-{first8}-2"`) **only** when scope or RG changes. - -## Chat Output Rules - -1. **Terminal command:** the PowerShell snippet above (outputs the bare URL via `Write-Output "LINK=..."`). ⛔ **Do NOT auto-open the link in a browser** — never call `Start-Process` (or any browser-launching command) on the portal URL. Just print it so the user can open it if they choose. -2. **Read the terminal output** — find the line starting with `LINK=` and extract the URL -3. **Chat output:** paste the bare URL on its own line — no backticks, no markdown, no emoji on the same line - -⛔ **Link must be ctrl+clickable.** The URL MUST be the ONLY content on its line — no emoji, no text, no backticks, no markdown formatting, no markdown link syntax on the same line. Terminals auto-linkify bare URLs but ONLY when the URL is alone on the line. - -⛔ **Emit a NEW link whenever deployment name changes.** When healing causes a redeploy with a different `--name`, you MUST re-run the PowerShell snippet with the new name and print the new link: -``` -⚠️ Previous deployment link is stale — use this one: -https://portal.azure.com/.../{newDeploymentName} -``` diff --git a/.github/agents/azure-deploy/deploy/references/preflight-checks.md b/.github/agents/azure-deploy/deploy/references/preflight-checks.md deleted file mode 100644 index 41c3477..0000000 --- a/.github/agents/azure-deploy/deploy/references/preflight-checks.md +++ /dev/null @@ -1,129 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Preflight Checks - -Pre-deployment validation steps. Run after user approval, before deployment execution. - -> AppOnboard runs direct deployment (no `azd`). - -## Check Sequence - -Branch on `scaffold-manifest.json.iacFormat`: - -### 0. Auth Token Verification - -```bash -az account show -``` - -- Success → proceed. Active subscription + tenant confirmed. -- Failure → `ENVIRONMENT_BLOCKING`. Suggest `az login` (plain, no scope). -- ⛔ NEVER suggest `az login --scope https://graph.microsoft.com/.default` — Graph scope is irrelevant for ARM deployments. - -### 0b. Resource Name Availability - -Check globally-unique names before deploy: `az acr check-name`, `az storage account check-name`, `az webapp show`, `az keyvault show`. Name taken → suggest alternate from `prepare-plan.json.naming.suffix`: "Name `{name}` taken. Use `{altName}`?" - -### 0c. F1/Free Tier Warning - -If plan includes F1/D1/free SKUs, surface at deploy gate (do NOT block): -> ⚠️ Free tier: no custom domains, no SSL, no always-on, 60 min/day compute (F1). Dev/test only. - -### 0d. RBAC Scope Pre-Check - -```bash -az role assignment list --assignee {userId} --scope /subscriptions/{sub} --query "[].roleDefinitionName" -o tsv -``` - -Subscription-scope deploy requires `Contributor`/`Owner` on subscription. Missing → `ENVIRONMENT_BLOCKING` with `az role assignment create` command. - -### 1. Deployment Preview - -⛔ **MANDATORY — do NOT skip.** What-if validates + previews in one call. Use `what-if` exclusively — `az deployment sub/group validate` hits a known CLI bug (HTTP stream consumed error). If what-if fails, log + warn user — do not skip to execution. - -#### Bicep (subscription scope) - -```bash -az deployment sub what-if \ - --name "{deploymentName}" \ - --location {location} \ - --template-file infra/main.bicep \ - --parameters @infra/main.parameters.json \ - --subscription {subscriptionId} \ - --what-if-result-format FullResourcePayloads -``` - -#### Bicep (resource-group scope) - -```bash -az deployment group create \ - --resource-group {rg} \ - --template-file infra/main.bicep \ - --parameters @infra/main.parameters.json \ - --subscription {subscriptionId} \ - --what-if \ - --what-if-result-format FullResourcePayloads -``` - -- Review changes: `Create`, `Modify`, `Delete`, `NoChange`. Surface `Delete` as warnings — user must acknowledge. -- Auth error → `ENVIRONMENT_BLOCKING`. - -#### Terraform - -```bash -terraform plan -out=tfplan -detailed-exitcode -``` - -- Exit 0 → no changes. Exit 2 → changes (normal). Exit 1 → error. -- Surface `destroy` as warnings — user must acknowledge. Auth error → `ENVIRONMENT_BLOCKING`. - -### 3. RBAC Permission Check - -```bash -az role assignment list \ - --assignee {currentUserObjectId} \ - --scope /subscriptions/{sub}/resourceGroups/{rg} \ - --query "[].roleDefinitionName" -o tsv -``` - -Required: `Contributor` or `Owner` on the target resource group. If missing → `ENVIRONMENT_BLOCKING` with remediation command. - -### 4. SKU Quota Verification - -⛔ **`what-if` does NOT catch quota errors.** It returns `Succeeded` even when target SKU has limit=0. - -If `prepare-plan.json.quotaValidation.verified == true` → proceed. - -Otherwise → **read [sku-quota-validation.md](../../prepare/references/sku-quota-validation.md)** and run direct quota checks NOW (per-provider API patterns, offer restrictions). If limit=0 → HALT, present region fallback. Skip regions in `quotaValidation.checkedRegions` with zero availability. - -⛔ `SubscriptionIsOverQuotaForSku` or `LocationIsOfferRestricted` in deploy output → HALT. See [error-classification.md](error-classification.md). - -### 5. Resource Group Existence - -```bash -az group show --name {rg} --query "location" -o tsv 2>/dev/null -``` - -- Exists → verify location matches `prepare-plan.json` region. Mismatch → warn. -- Not exists → will be created by deployment (if `main.bicep` has subscription scope). - -## Error Handling - -Each check runs independently. Collect all results, then present structured report. - -| Check | Fail Behavior | -|-------|---------------| -| Deployment preview | Warn, don't block (can fail on unsupported types) | -| RBAC | Block. Surface `az role assignment create`. | -| RG check | Warn on location mismatch. Don't block. | - -## Report Format - -``` -## Preflight Results -✅ IaC syntax: valid (terraform validate / bicep build) -⚠️ Deployment preview: 3 creates, 0 destroys, 1 update -✅ RBAC: Contributor role confirmed -✅ Resource group: rg-myapp-dev (eastus2) -``` diff --git a/.github/agents/azure-deploy/deploy/references/subagent-preflight.md b/.github/agents/azure-deploy/deploy/references/subagent-preflight.md deleted file mode 100644 index 23f17bb..0000000 --- a/.github/agents/azure-deploy/deploy/references/subagent-preflight.md +++ /dev/null @@ -1,81 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Subagent Template — Deploy Preflight & Checklist Generation - -Read deploy reference files and distill deployment-specific rules into `deploy-checklist.md`. This is the main agent's ONLY source of deploy rules. - -## Critical Rules - -- ⛔ Do NOT invoke any agents, run `az` commands, or modify IaC/app code. Read-only sub-agent. -- ⛔ Do NOT read `deploy-schemas.ts` or `error-classification.md` — main agent reads those on-demand. - -## Input (provided by caller) - -| Field | Source | -|-------|--------| -| `prepare-plan.json` content | services[], naming, region, costEstimate, deploymentVariables | -| `scaffold-manifest.json` content | deployCommand, validationResult, files[] | -| `context.json` content | azure.subscriptionId, subscriptionName, resourceGroup, sessionId, intent | -| `prereq-output.json` content | buildRequirements, warnings[], components[] | -| Session folder path + working directory | Required | - -## Output - -| Artifact | Location | -|----------|----------| -| `deploy-checklist.md` | Session folder | -| `deploy-result.json` skeleton (if missing) | Session folder | -| Summary (≤300 tokens) | Return to caller | - -## Workflow - -### Step 1 — Read session artifacts + write skeleton - -Read all 4 session artifacts. Extract: services[], naming, costEstimate, deployCommand, validationResult, deploymentVariables, subscriptionId, sessionId, buildRequirements, warnings[], quotaValidation. - -If `deploy-result.json` missing, write skeleton with these EXACT field names (do NOT rename): `{ sessionId, subscriptionId, resourceGroupName, deploymentNames: ["app-onboard-deploy-{first 8 of sessionId}"], status: "in-progress", startedUtc, resourceIds: [], endpoints: [], healthStatus: "unknown", resourceResults: [], healingAttempts: [] }`. - -### Step 2 — Read safety + blocked patterns refs - -Read [deploy-safety.md](deploy-safety.md) and [blocked-patterns.md](blocked-patterns.md). Bake into checklist sections: deploy-result.json rules, blocked commands table, shell rules (sync shells, secrets persistence, no --track-status, az rest headers on Windows), 403 scope fallback (4-step procedure with real values), post-deploy tag verification, deployment operation polling (conditional: >5 resources), re-approval gates, antipatterns, artifact reconciliation. - -### Step 3 — Read preflight + approval gate refs - -Read [preflight-checks.md](preflight-checks.md) and [approval-gate-template.md](approval-gate-template.md). - -Bake preflight into checklist: auth token check, resource name availability (real names), RBAC scope pre-check, ⛔ MANDATORY what-if command (pre-filled with real deploymentName, region, subscriptionId), RG existence check, offer restriction check (conditional: if `offerRestrictionsVerified` false AND DB services in plan). - -Bake approval gate VERBATIM with real values: subscription, RG, region, service table, cost table, validation status, files list, response handlers with exact CLI commands. If F1/D1 detected, append warning. - -### Step 4 — Read code-deployment + health refs - -Read ONLY the code-deployment ref(s) matching the compute types in `prepare-plan.json.services[]`. **Do NOT read** references for compute types absent from the plan: -- If `App Service` or `Functions` in plan → read [code-deployment-appservice.md](code-deployment-appservice.md) -- If `Container Apps` in plan → read [code-deployment-container-apps.md](code-deployment-container-apps.md) -- If `Static Web Apps` in plan → read [code-deployment-swa.md](code-deployment-swa.md) - -Bake per-service-type `## Code deploy` sections (one per compute service — do NOT merge). - -Read [health-check-patterns.md](health-check-patterns.md). Bake health check section: HTTP checks (timeout 30s, 3 retries), status interpretation, Azure default page detection strings, non-HTTP resource checks, functional verification (conditional). - -### Step 5 — Read conditional refs + handoff + write checklist - -**Database (conditional):** If DB services in plan → read [database-post-deploy.md](database-post-deploy.md). Bake migration discovery, execution commands, PG-specific checks. - -Read [../../references/handoff-protocol.md](../../references/handoff-protocol.md). Bake cleanup commands (with real rg, sessionId), orphan listing, healing summary, post-deploy recommendations, agent-based next steps, auth-aware handoff. - -**Write `deploy-checklist.md`** to session folder. If it already exists, replace all content via `edit`; if not, use `create`: -``` -# Deploy Checklist for {appName} -# RG: {rgName} | Sub: {subscriptionId} | Session: {sessionId} -# ⚠️ If compaction recently occurred: re-read deploy/instructions.md Steps 6-8 -``` - -Delete inapplicable sections (e.g., remove App Service section for Container Apps deploys). - -> ⛔ **Copy `## Before handoff (Step 8)` and `## Artifact verification (Step 8 — MANDATORY)` from the template VERBATIM.** Do NOT paraphrase, merge, or weaken `⛔` markers. These sections are the compaction-safe finalization anchor — diluting them causes artifact writes to be skipped after compaction. - -### Step 6 — Return summary - -Return ≤300 tokens: preflight warnings, deploy command, service types, confirmation of checklist write, database migration note if applicable. diff --git a/.github/agents/azure-deploy/instructions.md b/.github/agents/azure-deploy/instructions.md deleted file mode 100644 index 7c0ef0c..0000000 --- a/.github/agents/azure-deploy/instructions.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -name: azure-deploy -description: "End-to-end Azure deployment instructions for the azure-deploy agent: from app analysis to a running Azure deployment with cost estimates and pre-deploy approval. Auto-detects the right Azure services, scaffolds infrastructure code, and deploys — tailored to your app, not a template. Handles moving existing apps to Azure without rewriting or with minimal changes." -license: MIT -metadata: - author: Microsoft - version: "1.2.1" ---- - - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - - -# Azure App Onboard - -> ⛔ **Every repo goes through the full pipeline (Steps 1–10). No exceptions.** Do not skip steps, refuse, or short-circuit based on what you recognize. Follow the Workflow table below sequentially — read each step's references before acting. - -## Quick Reference - -| Property | Value | -|----------|-------| -| Best for | Developers who know what to build but not which Azure services to use | -| Inputs | Business idea or existing codebase, budget/scale preferences (optional) | -| Outputs | Architecture plan, cost estimate, IaC files, deployed Azure resources | -| Phases | Discover → Architect → Scaffold → Deploy (self-contained, no external agent calls) | - -## When to Use This Agent - -- Deploy existing code without knowing which Azure services to use -- Check if your existing code is ready to deploy to Azure -- Move an existing app to Azure without rewriting or with minimal changes -- Get cost estimates before committing to infrastructure -- Understand architecture decisions and rejected alternatives -- Get answers to Azure architecture or service selection questions (e.g., "What database should I use?") -- Get guided Azure onboarding without prior experience - -## When NOT to Use - -| Scenario | Use Instead | -|----------|-------------| -| Run `azd up` or execute an existing deployment | `azure-deploy` | -| Optimize existing Azure spend | `azure-cost` | -| Generate Bicep/Terraform for a known architecture | `azure-prepare` | -| Validate infrastructure or run preflight checks | `azure-validate` | -| Troubleshoot a running Azure deployment | `azure-diagnostics` | -| Deploy to or manage AKS/Kubernetes directly | `azure-kubernetes` | -| Look up or list existing Azure resources | `azure-resource-lookup` | - -## Pipeline Rules - -> ⛔ **You MUST read [`references/pipeline-rules.md`](references/pipeline-rules.md) at the start of every AppOnboard session.** It contains approval gates, phase lifecycle, session artifacts, deploy-as-is, and security baseline rules. - -## Workflow - -> ⛔ **Deploy recovery:** After deploy gate approval OR before any `az deployment`/`az webapp deploy`/`az acr build` — if you haven't read `deploy/instructions.md`, read `.copilot-azure/sessions/{id}/deploy-checklist.md` first, then `deploy/instructions.md`. ⛔ Always use this pipeline's own deploy phase (`deploy/instructions.md`) — do NOT hand off to any other deployment workflow. - -> ⛔ **Post-scaffold transition (MANDATORY):** Immediately after `scaffold-manifest.json` is written, YOUR NEXT ACTION MUST be Step 8 (Deploy Approval Gate) — NOT a summary report, NOT a "here are the generated files" message, NOT a completion signal. Confirm `context.json` has `completedPhases: [...,"scaffold"]` + `currentPhase: "deploy"` (update it yourself if the scaffold subagent didn't). Re-read [approval-gates.md § Deploy Gate](references/approval-gates.md) if evicted from context (scaffold reference loading is heavy), then present the exact prompt: **"🚀 Ready to deploy? (Yes / Run manually / Edit plan / Cancel)"**. This gate is the LAST content in your response — wait for the user's reply. - -| # | Step | Action | Reference | -|---|------|--------|-----------| -| 1 | **Session check + Azure login** | Create/resume session, verify Azure CLI auth, resolve subscription + user identity | ⛔ **You MUST read [session-protocol.md](references/session-protocol.md)** | -| 2 | **Scope triage** | Check azd markers, triage question. Empty workspace or code-only (no infra) → Step 3 directly. | ⛔ Read [intent-gathering.md](references/intent-gathering.md) § Scope Triage | -| 3 | **Prereq scan** | ⛔ Skip if `completedPhases` includes `"prereq"`. Otherwise: evaluate repo readiness, write `prereq-output.json`, update `context.json`. **Halt if:** `overallHealth: "blocked"` OR `routeToSkill` set. | ⛔ **You MUST read and follow [prereq/instructions.md](prereq/instructions.md)** | -| 4 | **Gather intent** | Present prereq results, confirm stack + Azure services, ask remaining questions. | ⛔ Read [intent-gathering.md](references/intent-gathering.md) § After Prereq Returns | -| 5 | **Plan architecture** | Write `prepare-plan.json`. | ⛔ **You MUST read [prepare/instructions.md](prepare/instructions.md)** | -| 6 | **Scaffold approval gate** | Display plan for user approval BEFORE generating any files. | ⛔ Read [approval-gates.md](references/approval-gates.md) § Scaffold Gate | -| 7 | **Scaffold** | Generate IaC, self-review. Write `scaffold-manifest.json`. Update `context.json`. | ⛔ **You MUST read [scaffold/instructions.md](scaffold/instructions.md)** | -| 8 | **Deploy approval gate** | Display validation summary. ⛔ After approval: FIRST read deploy-checklist.md → deploy/instructions.md. Always use this pipeline's own `deploy/instructions.md` — do NOT hand off elsewhere. | ⛔ Read [approval-gates.md](references/approval-gates.md) § Deploy Gate | -| 9 | **Deploy** | Execute IaC, health-check. Write `deploy-result.json`. | ⛔ **You MUST read [deploy/instructions.md](deploy/instructions.md)** | -| 10 | **Handoff** | Surface deployment identity, cleanup commands, next steps. | ⛔ **You MUST read [`handoff-protocol.md`](references/handoff-protocol.md)** | - -## Error Handling - -| Error | Remediation | -|-------|-------------| -| Phase fails | Halt, report phase + error. User decides: retry, skip, abort. | -| MCP server unavailable | Skip affected checks, add disclaimer to `costEstimate.assumptions[]` and every approval gate. | -| Missing RBAC | Report required role + `az role assignment` command. | - -> **Shared references:** [MCP tools](references/mcp-tool-reference.md) (cross-phase tool parameters) | [IaC resources](references/iac-resources.md) (Azure resource docs for troubleshooting) \ No newline at end of file diff --git a/.github/agents/azure-deploy/prepare/instructions.md b/.github/agents/azure-deploy/prepare/instructions.md deleted file mode 100644 index 88e3a63..0000000 --- a/.github/agents/azure-deploy/prepare/instructions.md +++ /dev/null @@ -1,74 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Prepare — Architecture Planning & Cost Estimation - -## Quick Reference - -| Property | Value | -|----------|-------| -| Best for | Mapping app components to Azure services with cost estimation and quota validation | -| Inputs | `prereq-output.json` + `context.json` from `.copilot-azure/sessions/{id}/` | -| Outputs | `prepare-plan.json` written to session directory | -| Parent | [azure-app-onboard](../instructions.md) | - -## When to Use This Agent - -Invoked by the `azure-app-onboard` orchestrator at Phase 2 when `prereq-output.json` exists. Not directly user-routable. - -> **Return to orchestrator:** When complete, return control to `azure-app-onboard`. Do NOT directly invoke scaffold or deploy. - -## When NOT to Use - -| Scenario | Use Instead | -|----------|-------------| -| Code readiness or prereq scanning | `azure-app-onboard` Step 3 (prereq) | -| IaC generation from a completed plan | `azure-app-onboard` Step 7 (scaffold) | -| Deploying resources to Azure | `azure-app-onboard` Step 9 (deploy) | -| Optimizing existing Azure spend | `azure-cost` | -| Estimating VM-specific costs | `azure-compute` | -| Enterprise landing zone architecture | `azure-enterprise-infra-planner` | - -## MCP Tools - -| Tool | Purpose | -|------|----------| -| `mcp_azure_mcp_pricing` / `azure-pricing` (router → `command: pricing_get`) | Cost estimation (inline — see Step 6). Fallback: dispatch [`subagent-pricing.md`](references/subagent-pricing.md) | -| `mcp_azure_mcp_policy` | Subscription policy constraints | -| `az rest` | Quota validation (via sub-agent — see Step 5) | -| `mcp_azure_mcp_cloudarchitect` → `cloudarchitect_design` | WAF-aligned architecture design | -| `mcp_azure_mcp_wellarchitectedframework` | Per-service WAF guidance | -| `mcp_azure_mcp_advisor` → `advisor_recommendation_list` | Optimization recommendations | - -## Workflow - -| # | Step | Action | Reference | -|---|------|--------|-----------| -| 1 | **Read session state** | Load `prereq-output.json` + `context.json`. Resolve subscription | Cross-ref [subscription-resolution.md](../references/subscription-resolution.md) if needed | -| 2 | **Query policy constraints** | Inline MCP: fetch policy + advisor recommendations | `mcp_azure_mcp_policy` + `mcp_azure_mcp_advisor` | -| 3 | **Map components to services** | Per-component Azure service selection, Dockerfile routing, deploy-as-is. ⛔ Existing Azure Functions components MUST remain separate Azure Functions services. | ⛔ **You MUST read [service-mapping.md](references/service-mapping.md) and [deploy-strategy.md](references/deploy-strategy.md)** | -| 4 | **Select SKUs + WAF analysis** | Budget-aware SKU selection, inline WAF service guidance | ⛔ **You MUST read [sku-matrix.md](references/sku-matrix.md)** | -| 5 | **Validate quotas + region capacity** | ⛔ Read [`subagent-quota.md`](references/subagent-quota.md) → dispatch as `task` (NEXT action MUST be `task`, ⛔ agent_type: `"task"` — NEVER `"general-purpose"`). Copy the **COMPLETE and UNMODIFIED** template text into the task prompt between `<<>>` / `<<>>` delimiters — do NOT summarize. Append the caller-provided inputs listed in [`subagent-quota.md`](references/subagent-quota.md)'s Input table AFTER the template block. ⛔ **After dispatching, proceed to Step 6 (cost estimation) while the subagent runs. Do NOT run quota checks yourself — the subagent handles it. Collect subagent results before Step 9 (write plan).** | ⛔ **You MUST read [`subagent-quota.md`](references/subagent-quota.md)** | -| 6 | **Estimate costs** | ⛔ **You MUST read [pricing-guide.md](references/pricing-guide.md)** for methodology, then [pricing-guide-services.md](references/pricing-guide-services.md) for per-service filters. Call the pricing router (`mcp_azure_mcp_pricing`/`azure-pricing`) with `command: "pricing_get"` + a `parameters{}` object inline per paid service. If MCP unavailable or fails → ⛔ Read [`subagent-pricing.md`](references/subagent-pricing.md) → dispatch as `task` (NEXT action MUST be `task`, ⛔ agent_type: `"task"` — NEVER `"general-purpose"`). Copy the **COMPLETE and UNMODIFIED** template text into the task prompt between `<<>>` / `<<>>` delimiters — do NOT summarize. Append data (services[], region, budget tier) AFTER the template block. Write results to `prepare-plan.json.costEstimate`. | [pricing-guide.md](references/pricing-guide.md) | -| 7 | **Generate naming** | Centralized naming: suffix, prefix, all resource names | ⛔ **You MUST read [naming-patterns.md](references/naming-patterns.md)** | -| 8 | **Determine IaC format** | Existing non-Azure `.tf` → `ask_user` Bicep vs TF, write to `overrides[].iacFormat`. No `.tf` → default Bicep. | (inline) | -| 9 | **Write prepare-plan.json** | Per `PreparePlan` schema. Include postDeployRecommendations, deploymentVariables | ⛔ **You MUST read [prepare-schemas.ts](references/prepare-schemas.ts)** for `PreparePlan` schema | -| 10 | **Return summary** | Structured summary for orchestrator approval gate | (inline — 1 line) | -| 11 | **Validate plan** | 4-dimension check: Goal Alignment, WAF Alignment, Dependency Completeness, Deployment Viability. Fix inline on failure, document tradeoffs in `assumptions[]`. | All must pass before writing | - -### Step 5 — Post-Quota Validation - -> ⛔ **NEVER present a region without checking quota first.** Skipping quota validation causes cascading deploy failures and extended healing loops. -> ⛔ If plan includes PostgreSQL/MySQL, verify `offerRestrictionsVerified: true` — if false/missing, region is blocked. Do NOT proceed to scaffold with unchecked DB services. -> ⛔ **Paid ≠ unlimited.** Every compute SKU — including B1, Consumption, and Serverless tiers — has a per-subscription, per-region quota. Do NOT skip quota checks. (F1/D1/Free are never selected — the compute floor is B1.) -> ⛔ After region fallback, update ALL `services[].region` in `prepare-plan.json`. Do not leave stale values. - -## Error Handling - -| Error | Remediation | -|-------|-------------| -| Pricing API 400 | Verify `--sku` included in the query | -| MCP pricing unavailable | Dispatch [`subagent-pricing.md`](references/subagent-pricing.md) as `task` fallback (uses direct HTTP to `prices.azure.com`) | -| Prereq output missing | Trigger prereq backfill | -| Quota check fails | Fall back to best-effort estimate + disclaimer | -| Override conflicts | Re-run from Step 3 with new constraints | diff --git a/.github/agents/azure-deploy/prepare/references/deploy-strategy.md b/.github/agents/azure-deploy/prepare/references/deploy-strategy.md deleted file mode 100644 index 1478951..0000000 --- a/.github/agents/azure-deploy/prepare/references/deploy-strategy.md +++ /dev/null @@ -1,124 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Deploy Strategy - -Determine how application code will be deployed to Azure based on prereq scan results. The prepare phase writes `deployStrategy` to `prepare-plan.json`; scaffold encodes it in Bicep; deploy executes it. - -## Deployment Patterns - -Three patterns — select based on `prereq-output.json.components[].buildRequirements`: - -> ⛔ **Dockerfile ≠ Container Apps.** A Dockerfile that only serves static files (nginx, httpd, `COPY . /usr/share/nginx/html`) is NOT a backend app. Route static-only Dockerfiles as static sites per `service-mapping.md § Static Dockerfile sites`, not Pattern C. - -| Pattern | When | `deployStrategy` needed? | -|---------|------|--------------------------| -| **A: Oryx auto-build** | No native modules, no Dockerfile | Yes — startup command + app settings go in Bicep at scaffold time | -| **B: Startup-install** | Native modules detected (`hasNativeModules: true`) | Yes — startup command + fallback install + app settings in Bicep | -| **C: Container-only** | Has Dockerfile that runs a server process (Express, Flask, uvicorn, etc.) | No — route to Container Apps (Dockerfile IS the deploy strategy) | - -**Additional routing:** - -| Condition | Action | -|-----------|--------| -| Jib build plugin (Java + `com.google.cloud.tools.jib`) | Container-only via Jib push to ACR — no Dockerfile needed | - ---- - -## Pattern A: Oryx Auto-Build (Default) - -**Languages:** Node.js, Python, .NET, Go, Java, PHP, Ruby - -Oryx detects the stack from project manifests, installs dependencies, and builds automatically during zip deploy. - -**Still write `deployStrategy` to `prepare-plan.json`** — even though Oryx auto-detects, the startup command and app settings MUST be in Bicep at scaffold time (not generated at deploy time). This eliminates imperative CLI commands during deploy. - -```json -"deployStrategy": { - "codeDeployPattern": "oryx-auto", - "requiredAppSettings": { - "SCM_DO_BUILD_DURING_DEPLOYMENT": "true", - "ENABLE_ORYX_BUILD": "true", - "ORYX_DISABLE_COMPRESSION": "true" - }, - "reason": "Standard Oryx build — no native modules, no Dockerfile. Compression disabled to avoid startup extraction delays. No custom appCommandLine — Oryx launcher handles decompression + start." -} -``` - - Read `prereq-output.json.entryPoint` for the app's start file — do NOT re-read manifests. Build the startup command from `package.json` `start` script (Node.js) or framework convention (Python gunicorn, .NET/Go/Java Oryx-native). - -> ⛔ **Do NOT set a custom `appCommandLine` for Pattern A.** Let Oryx use `package.json` `start` script or framework defaults natively. A custom `appCommandLine` (`cd /home/site/wwwroot && node {entryPoint}`) replaces the Oryx launcher entirely — the launcher handles `node_modules.tar.gz` decompression, and bypassing it causes `MODULE_NOT_FOUND` crashes. Only set `appCommandLine` when `initCommands[]` has `required: true` entries (migrations). -> -> ⛔ **TypeScript projects:** Verify `typescript` + `@types/*` are in `dependencies` (not `devDependencies`) — Oryx production mode skips devDeps, causing `tsc` build failures. -> -> ⛔ **When `initCommands[]` has `required: true` entries:** Set `startupCommand` to prepend migrations: `"cd /home/site/wwwroot && {initCommand} && {framework-default-start}"`. Migrations are idempotent — safe on every cold start. Otherwise, omit `startupCommand` entirely (let Oryx handle it). - -Scaffold encodes `startupCommand` → Bicep `appCommandLine`, and `requiredAppSettings` → Bicep `siteConfig.appSettings`. Deploy only does: wait → zip → health check. - ---- - -## Pattern B: Startup-Install (Native Modules) - -When native modules are detected, Oryx may fail to compile them. The startup-install pattern provides a two-layer safety net. - -### Two-Layer Strategy - -1. **Primary — Oryx zip build:** `SCM_DO_BUILD_DURING_DEPLOYMENT=true` + `ENABLE_ORYX_BUILD=true` tells Oryx to run dependency installation during the Kudu-side zip deploy. The Kudu build environment on App Service Linux has `gcc`, `make`, and build tools available, so native compilation CAN succeed here. - -2. **Fallback — startup-install command:** `appCommandLine` runs dependency installation on first container boot IF the dependency directory doesn't exist. The existence guard ensures it only runs when needed — subsequent restarts skip it because `/home` is persistent storage. - -Both layers are set in Bicep at scaffold time. The startup command is insurance — not the primary mechanism. - -> **Why two layers?** `az webapp deploy --type zip` uses the OneDeploy API, which may not trigger Oryx even with `SCM_DO_BUILD_DURING_DEPLOYMENT=true`. The startup command catches this case. If Oryx DID build successfully, the guard skips the redundant install. - -### Deploy Strategy Schema - -Write to `prepare-plan.json.deployStrategy`: - -```json -"deployStrategy": { - "codeDeployPattern": "startup-install", - "startupCommand": "cd /home/site/wwwroot && if [ ! -d node_modules ]; then npm install --production; fi && node index.js", - "requiredAppSettings": { - "WEBSITES_CONTAINER_START_TIME_LIMIT": "1800", - "SCM_DO_BUILD_DURING_DEPLOYMENT": "true", - "ENABLE_ORYX_BUILD": "true", - "ORYX_DISABLE_COMPRESSION": "true" - }, - "reason": "Native module (better-sqlite3 via node-gyp) requires server-side npm install." -} -``` - -Replace `startupCommand` and `reason` with language-specific values from the entry point table below. - -### Entry Point & Startup Commands - -Same as Pattern A, but Node.js adds a dependency guard: `if [ ! -d node_modules ]; then npm install --production; fi` before the start command. - -> ⛔ **Inline commands only.** Never generate a `.sh` startup script file — CRLF causes `bash` exit code 2. -> ⛔ **Python: do NOT use `venv` in startup commands.** - -### SKU Implications - -The compute floor is **B1 (Basic, ~$13/mo)** — F1/D1/Free are never selected (managed identity is mandatory and the free-tier MI sidecar OOMs). When native modules, TypeScript build, large deps, or a WSGI/ASGI server push resource needs higher, size up from B1 (B2/S1) and surface the reason at the approval gate: "⚠️ {reason}. {sku} required." Never size below B1. - -### Container Timeout - -`WEBSITES_CONTAINER_START_TIME_LIMIT` controls how long Azure waits for the container to start responding. - -| Value | Use case | -|-------|----------| -| 230 (default) | Standard apps, no native compilation | -| 1800 (max) | Startup-install — native compilation takes 2-5 min. Python with scipy/scikit-learn can take longer | - -Always set to `1800` when `codeDeployPattern == "startup-install"`. - ---- - -## Pattern C: Container-Only - -When the component has a Dockerfile with backend logic, route to **Container Apps**. The Dockerfile IS the deploy strategy — no `deployStrategy` needed in `prepare-plan.json`. - -The deploy phase handles: ACR build → image push → Bicep redeploy with real image. See [code-deployment-container-apps.md](../../deploy/references/code-deployment-container-apps.md). - -For Java apps using Jib (`build.gradle` + `com.google.cloud.tools.jib`), the build produces a container image without a Dockerfile — push to ACR via `jib` task. diff --git a/.github/agents/azure-deploy/prepare/references/mcp-tools.md b/.github/agents/azure-deploy/prepare/references/mcp-tools.md deleted file mode 100644 index 8520ccc..0000000 --- a/.github/agents/azure-deploy/prepare/references/mcp-tools.md +++ /dev/null @@ -1,87 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Prepare Phase — MCP Tools - -Phase-exclusive tool parameters for the prepare phase. For shared tools (`subscription_list`, `group_list`, `get_azure_bestpractices`, `extension_cli_install`), see [mcp-tool-reference.md](../../references/mcp-tool-reference.md). - -> **Troubleshooting:** If a tool call fails with unknown parameter or missing command errors, consult the official docs: - ---- - -## `mcp_azure_mcp_pricing` (hierarchical) - -| Sub-command | Required Params | Optional Params | Read-Only | -|-------------|----------------|-----------------|-----------| -| `pricing_get` | *(none)* | `service` (service name, e.g. "Azure App Service"), `sku` (`armSkuName` value), `region` (ARM region name), `currency` (e.g. "USD"), `filter` (OData filter for retail prices API) | ✅ | - -**Usage:** Query Azure retail pricing. Use `service` + `region` for broad queries, `sku` for exact SKU lookup (when `armSkuName` is populated — e.g., Redis Cache, App Service Premium). Use `filter` for advanced OData queries against the retail prices API. See [pricing-guide.md](pricing-guide.md) for per-service filter strings and meter name mappings. - -## `mcp_azure_mcp_quota` (hierarchical) - -> ⛔ **Do NOT use for AppOnboard quota checks.** Use `az rest` with the Quota REST API instead — see [sku-quota-validation.md](sku-quota-validation.md). The MCP quota tool returns misleading "No Limit" values for unsupported resource types — "No Limit" means the quota API doesn't support that resource type, NOT unlimited capacity. - -| Command | Required Params | Optional Params | Read-Only | -|---------|----------------|-----------------|-----------| -| `quota_usage_check` | `region`, `resource-types` (ARM type, e.g. `Microsoft.Compute/virtualMachines`) | `subscription` | ✅ | -| `quota_region_availability_list` | `resource-types` (ARM type) | `subscription` | ✅ | - -## `mcp_azure_mcp_cloudarchitect` (hierarchical) - -| Sub-command | Required Params | Optional Params | Read-Only | -|-------------|----------------|-----------------|-----------| -| `cloudarchitect_design` | *(none)* | `question`, `question-number`, `total-questions`, `answer`, `next-question-needed` (bool), `confidence-score` (0.0–1.0), `state` (JSON — tracks architectureComponents, architectureTiers, requirements{explicit,implicit,assumed}, confidenceFactors) | ✅ | - -**Usage:** Multi-turn conversational tool. For single-shot use, populate `state` with known requirements, set `confidence-score` ≥ 0.7 and `next-question-needed: false` to get a direct recommendation without follow-ups. - -## `mcp_azure_mcp_wellarchitectedframework` (hierarchical) - -| Sub-command | Required Params | Optional Params | Read-Only | -|-------------|----------------|-----------------|-----------| -| `wellarchitectedframework_serviceguide_get` | *(none)* | `service` (case-insensitive, hyphens/underscores/spaces OK, e.g. "cosmos-db", "App Service", "cosmosdb") | ✅ | - -**Usage:** Omit `service` to list all supported services. Provide `service` to get per-service guidance across all 5 WAF pillars. - -## `mcp_azure_mcp_advisor` (hierarchical) - -| Sub-command | Required Params | Optional Params | Read-Only | -|-------------|----------------|-----------------|-----------| -| `advisor_recommendation_list` | *(none)* | `subscription`, `resource-group` | ✅ | - -## `mcp_azure_mcp_group_resource_list` - -| Required | Optional | Read-Only | -|----------|----------|-----------| -| `resource-group` | `subscription`, `tenant` | ✅ | - -Returns: resource names, IDs, types, locations within the group. - -## `mcp_azure_mcp_policy` (hierarchical) - -| Sub-command | Required Params | Optional Params | Read-Only | -|-------------|----------------|-----------------|-----------| -| *(subscription scope)* | *(none)* | `subscription` | ✅ | - -**Usage:** Bulk fetch subscription policy constraints — blocked resource types, required tags, allowed regions. Use in prepare Step 2 alongside `advisor_recommendation_list` to surface governance constraints early. - ---- - -## Phase 2 Tool Map - -| Tool | Sub-command | AppOnboard Step | Purpose | -|------|-----------|----------|---------| -| `mcp_azure_mcp_cloudarchitect` | `cloudarchitect_design` | Step 3 | Architecture validation. Single-shot: populate `state` from `context.json`, set `confidence-score: 0.8` | -| `mcp_azure_mcp_wellarchitectedframework` | `wellarchitectedframework_serviceguide_get` | Step 4 | Per-service WAF guidance. Call with `service: "{service-name}"` for each planned service | -| `mcp_azure_mcp_advisor` | `advisor_recommendation_list` | Step 2 | Get existing subscription recommendations alongside policy query | -| `mcp_azure_mcp_policy` | *(hierarchical)* | Step 2 | Subscription policy constraints — blocked types, required tags, allowed regions | -| `mcp_azure_mcp_pricing` | `pricing_get` | Step 6 | Cost estimation. Read [pricing-guide.md](pricing-guide.md) first | -| `mcp_azure_mcp_quota` | ⛔ Do NOT use | Step 5 | ⛔ Read [sku-quota-validation.md](sku-quota-validation.md) and use `az rest` with Quota REST API instead | -| `mcp_azure_mcp_subscription_list` | *(flat)* | Step 1 | Resolve target subscription if not in context | -| `mcp_azure_mcp_group_list` | *(flat)* | Step 7 | List existing RGs for reuse/conflict detection | -| `mcp_azure_mcp_group_resource_list` | *(flat)* | Step 7 | Detect naming collisions in target RG | - ---- - -## Tool Pitfalls - -- **`mcp_azure_mcp_pricing` → `pricing_get`:** Use `--sku` when querying by `armSkuName` (e.g., Redis Cache, App Service Premium). For services without `armSkuName`, use `filter` and `meterName` matching instead. Omitting all filter params returns too many results. diff --git a/.github/agents/azure-deploy/prepare/references/naming-patterns.md b/.github/agents/azure-deploy/prepare/references/naming-patterns.md deleted file mode 100644 index b140a3c..0000000 --- a/.github/agents/azure-deploy/prepare/references/naming-patterns.md +++ /dev/null @@ -1,64 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Naming Patterns - -Per-resource naming rules for AppOnboard-generated Azure resources. Apply in Step 7 (Generate naming). - -> **Reference:** [Azure naming conventions](https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-naming) — link, don't duplicate. Check docs for updates. - -## Default Pattern - -``` -{abbreviation}-{resourcePrefix} -``` - -Where `resourcePrefix` = `{project}-{env}-{suffix}` (generated once in prepare Step 7, stored in `prepare-plan.json.naming.resourcePrefix`). - -- `{project}` — app name from `context.json.app.name` (lowercase, alphanumeric + hyphens) -- `{env}` — `dev` / `staging` / `prod` (from intent or default `dev`) -- `{suffix}` — first 4 chars of session UUID (e.g., `a1d5`). Generated once, stored in `naming.suffix` -- `{abbreviation}` — short service prefix from the table below (e.g., `app`, `st`, `rg`) - -**Examples with `resourcePrefix = myapp-dev-a1d5`:** -- Resource Group: `rg-myapp-dev-a1d5` -- App Service: `app-myapp-dev-a1d5` -- Storage: `stmyappdeva1d5` (alphanumeric only) - -> ⛔ **No redundancy.** The resource name is `{abbr}-{resourcePrefix}` — NOT `{project}-{abbr}-{project}-{env}-{suffix}`. The project name appears ONCE in the prefix. -> -> ⛔ **Scaffold reads naming from the plan — does NOT generate or modify resource names.** - -Override via `context.json.overrides[]` with `key: "naming.pattern"`. - -## Per-Resource Rules - -| Resource | Abbreviation | Max Length | Allowed Chars | Globally Unique? | Example | -|----------|-------------|------------|---------------|------------------|---------| -| Resource Group | `rg` | 90 | Alphanumeric, hyphens, underscores, periods, parens | No (but include suffix to avoid cross-session collisions) | `rg-myapp-dev-a1d5` | -| App Service | `app` | 60 | Alphanumeric, hyphens | **Yes** | `app-myapp-dev-a1d5` | -| Container App | `ca` | 32 | Lowercase alphanumeric, hyphens | No (within env) | `ca-myapp-dev-a1d5` | -| Container Registry | `cr` | 50 | **Alphanumeric only** | **Yes** | `crmyappdeva1d5` | -| Azure SQL Server | `sql` | 63 | Lowercase alphanumeric, hyphens | **Yes** | `sql-myapp-dev-a1d5` | -| Cosmos DB | `cosmos` | 44 | Lowercase alphanumeric, hyphens | **Yes** | `cosmos-myapp-dev-a1d5` | -| Storage Account | `st` | 24 | **Lowercase alphanumeric only** | **Yes** | `stmyappdeva1d5` | -| Log Analytics | `log` | 63 | Alphanumeric, hyphens | No (within RG) | `log-myapp-dev-a1d5` | -| App Insights | `appi` | 260 | Most chars | No (within RG) | `appi-myapp-dev-a1d5` | -| Service Bus | `sb` | 50 | Alphanumeric, hyphens | **Yes** | `sb-myapp-dev-a1d5` | -| Functions | `func` | 60 | Alphanumeric, hyphens | **Yes** | `func-myapp-dev-a1d5` | -| Static Web Apps | `swa` | 40 | Alphanumeric, hyphens | No (suffix still required — Rule 1) | `swa-myapp-dev-a1d5` | -| Redis Cache | `redis` | 63 | Alphanumeric, hyphens | **Yes** | `redis-myapp-dev-a1d5` | - -> **CAF deviation:** Azure Cloud Adoption Framework uses `sbns` for Service Bus namespace. AppOnboard uses `sb` for brevity — either is acceptable. - -## Rules - -1. **ALL resources get the `{suffix}`** — including resource groups. This prevents cross-session naming collisions when the same app is deployed multiple times. The suffix is a 4-char random string generated once per session. -2. **Container Registry + Storage Account:** strip hyphens (alphanumeric only). ⛔ **Mechanical transform — do NOT re-derive char-by-char:** `('cr' + resourcePrefix).replace(/-/g,'').toLowerCase()`, truncate to ≤50 (ACR) / ≤24 (Storage). Worked example: `resourcePrefix = bezkoder-dev-18a3` → `cr` + `bezkoderdev18a3` → `crbezkoderdev18a3`. Copy the pattern; do not spell out the concatenation. -3. **Validate length** after substitution — Storage Account and Container App (24 / 32 chars, alphanumeric) are the tightest constraints. Budget for `{project}`: `24 - len(abbreviation) - len(env) - len(suffix) - 3 - 2` (separators + healing reserve). For `st` + `dev` (Storage strips hyphens): ~10 chars max. The 2-char reserve ensures room for a healing suffix (e.g., `edd6` → `edd602`) on region fallback. **Truncation when over budget:** - 1. Truncate at the nearest hyphen boundary within budget (e.g., `broken-web-app` at 10 → `broken-web`) - 2. If no hyphen within budget: hard truncate, no trailing hyphen - 3. Verify truncated name doesn't collide with reserved words (Rule 4) - 4. Recompute ALL resource names with the truncated project — do NOT mix long and short names -4. **Never use reserved words** as resource names (`admin`, `login`, `root`, `test`) -5. Populate `naming.resources[]` in `prepare-plan.json` with concrete names after validation diff --git a/.github/agents/azure-deploy/prepare/references/prepare-schemas.ts b/.github/agents/azure-deploy/prepare/references/prepare-schemas.ts deleted file mode 100644 index 834a334..0000000 --- a/.github/agents/azure-deploy/prepare/references/prepare-schemas.ts +++ /dev/null @@ -1,201 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/** - * Prepare artifact schema — prepare-plan.json. - * Read by prepare instructions.md Step 9 (write prepare-plan.json). - */ - -// ─── Shared type (inlined from session-schemas.ts to avoid cross-ref) ──────── - -export interface PostDeployRecommendation { - title: string; - reason: string; - effort: "low" | "medium" | "high"; - services?: string[]; -} - -// ─── Healing types (prepare phase) ─────────────────────────────────────────── - -export type PrepareHealingTrigger = "quota" | "policy" | "rubric" | "region"; -export type PrepareIssueClassification = "AUTO_FIXABLE" | "NEEDS_USER_INPUT"; -export type PrepareHealingResult = "fixed" | "needs-input" | "still-failing"; - -export interface PrepareHealingIssue { - dimension: string; - detail: string; - classification: PrepareIssueClassification; -} - -export interface PrepareHealingFix { - service: string; - change: string; - reason: string; -} - -export interface PrepareHealingAttempt { - attempt: number; - trigger: PrepareHealingTrigger; - issues: PrepareHealingIssue[]; - fixes: PrepareHealingFix[]; - result: PrepareHealingResult; -} - -// ─── prepare-plan.json ─────────────────────────────────────────────────────── - -export interface PlannedService { - name: string; - sku: string; - purpose: string; - component: string; - region: string; - resourceName: string; - /** Exact engine version for managed database services (MySQL/PostgreSQL Flexible Server), - * sourced from the provider capabilities API during quota validation — NOT guessed. - * ARM rejects major-only strings (e.g. MySQL '8.0'); use the exact supported patch - * (e.g. '8.0.21'). Omit for non-DB services. */ - version?: string; -} - -export interface CostBreakdownItem { - service: string; - sku: string; - monthlyUsd: number; - note?: string; -} - -export interface CostEstimate { - monthlyUsd: number; - currency: "USD"; - breakdown: CostBreakdownItem[]; - disclaimer: string; -} - -export interface RejectedAlternative { - service: string; - reason: string; -} - -export interface NamingResource { - type: string; - name: string; -} - -export interface NamingConfig { - pattern: string; - /** The computed prefix used for all resource names: {project}-{env}-{suffix}. - * deploymentVariables.environmentName MUST equal this value. */ - resourcePrefix: string; - resources: NamingResource[]; -} - -export type QuotaSource = "cli" | "fallback"; - -export interface QuotaCheck { - resource: string; - region: string; - required: number; - available: number; - sufficient: boolean; - provider: string; - currentUsage: number; - currentLimit: number; - adjustable: boolean; - source: QuotaSource; -} - -export interface DeployStrategy { - /** Deploy pattern — see deploy-strategy.md for the three patterns. - * "oryx-auto": Oryx handles build + run (Pattern A — default for most apps). - * "startup-install": native module compilation at startup (Pattern B). - * "container-only": Dockerfile-based deploy via ACR (Pattern C). - * Omit when Oryx auto-build is sufficient and no special startup is needed. */ - codeDeployPattern?: "oryx-auto" | "startup-install" | "container-only"; - /** Startup command for native module compilation (goes into appCommandLine) */ - startupCommand?: string; - /** Required app settings for the deploy strategy (e.g., SCM_DO_BUILD_DURING_DEPLOYMENT) */ - requiredAppSettings?: Record; - /** Human-readable reason for choosing this strategy */ - reason: string; -} - -export interface InstrumentationConfig { - appInsightsEnabled: boolean; - reason: string; -} - -export interface DeploymentVariables { - /** MUST equal naming.resourcePrefix (e.g., "myapp-dev-a1d5"), NOT the env label ("dev") */ - environmentName: string; - location: string; - sessionId: string; - deployedBy: string; - /** Variable names that could not be resolved at prepare time — deploy reads, not asks */ - deferred?: string[]; -} - -export interface OfferRestriction { - /** Azure provider namespace (e.g., "Microsoft.DBforPostgreSQL") */ - provider: string; - /** Region checked */ - region: string; - /** Whether the offer is restricted in this region */ - restricted: boolean; - /** Reason string from capabilities API, if any */ - reason?: string; -} - -export interface QuotaValidation { - /** True when all planned services had quota confirmed */ - verified: boolean; - /** How quota was verified — persists in session artifact, survives context compaction */ - method?: "cli" | "what-if" | "unverifiable"; - /** The specific region that passed validation */ - verifiedRegion?: string; - /** The specific SKU that was validated (e.g., "B1", "S1") */ - verifiedSku?: string; - /** Regions checked during validation */ - checkedRegions?: string[]; - /** Resources that failed quota checks */ - failedResources?: string[]; - /** Reason when verified is false (e.g., "quota CLI unavailable", "all regions zero") */ - reason?: string; - /** Offer restriction results for database services (PostgreSQL, MySQL). - * Populated by prepare Step 5 when database services are in the plan. - * `LocationIsOfferRestricted` is NOT caught by what-if — this is the only pre-deploy check. */ - offerRestrictions?: readonly OfferRestriction[]; - /** Derived from offerRestrictions[]: true when every database service in services[] - * has at least one entry in offerRestrictions[]. Set this based on the array contents, - * not independently. Deploy gate halts if false/missing when services include PostgreSQL/MySQL. */ - offerRestrictionsVerified?: boolean; -} - -export interface PreparePlan { - services: PlannedService[]; - costEstimate: CostEstimate; - rejectedAlternatives: RejectedAlternative[]; - naming: NamingConfig; - quotas: readonly QuotaCheck[]; - assumptions: string[]; - /** IaC format for scaffold — "bicep" (default) or "terraform" */ - iacFormat: "bicep" | "terraform"; - /** Application database name the app expects to exist (from compose env such as - * MYSQL_DATABASE / MYSQLDB_DATABASE / POSTGRES_DB, a connection string, or ORM config). - * Scaffold emits a `flexibleServers/databases` child resource for this so the schema DB - * exists in IaC before the container starts; the conformance gate's DB-NAME-PRESENT check - * asserts it. Omit only when no managed database is in the plan. */ - appDbName?: string; - postDeployRecommendations?: PostDeployRecommendation[]; - /** Instrumentation decision — object with boolean + reason, NOT a bare boolean */ - instrumentation?: InstrumentationConfig; - /** Deployment variables resolved at prepare time — scaffold/deploy read, not re-ask */ - deploymentVariables?: DeploymentVariables; - /** Quota validation result from proactive capacity check */ - quotaValidation?: QuotaValidation; - /** Code deploy strategy for native module apps — see deploy-strategy.md. - * Only populated when Pattern B (startup-install) is needed. */ - deployStrategy?: DeployStrategy; - healingAttempts?: readonly PrepareHealingAttempt[]; -} diff --git a/.github/agents/azure-deploy/prepare/references/pricing-guide-services.md b/.github/agents/azure-deploy/prepare/references/pricing-guide-services.md deleted file mode 100644 index d4095da..0000000 --- a/.github/agents/azure-deploy/prepare/references/pricing-guide-services.md +++ /dev/null @@ -1,200 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# App Onboard Prepare — Per-Service Pricing Patterns - -Filter strings, meter names, and formulas per Azure service. For methodology and troubleshooting, see [pricing-guide.md](pricing-guide.md). - -### Container Apps - -No `armSkuName`. Filter: `serviceName eq 'Azure Container Apps' and armRegionName eq '{region}' and priceType eq 'Consumption' and isPrimaryMeterRegion eq true` - -**Key meters:** `Standard vCPU Active Usage` (1 Second), `Standard Memory Active Usage` (1 GiB Second), `Standard Requests` (1M). - -**Monthly (Consumption, 1 vCPU, 2 GiB, 8h active/day):** -`(vCPU_active_rate × 3600 × 8 × 30) + (memory_active_rate × 2 × 3600 × 8 × 30)` - -**Free grant:** 180K vCPU-sec + 360K GiB-sec + 2M requests/sub/month. Scale-to-zero = $0 idle. When `min_replicas >= 1`: add idle cost from idle meters. - ---- - -### Container Registry (ACR) - -Empty `armSkuName` — ⛔ do NOT filter by `sku` (returns `[]`). Filter: `serviceName eq 'Container Registry' and armRegionName eq '{region}' and priceType eq 'Consumption'`, then match `meterName`. - -| Tier | `meterName` | `unitOfMeasure` | Monthly | -|------|-------------|-----------------|---------| -| Basic | `Basic Registry Unit` | `1/Day` | `retailPrice × 30` (~$5) | -| Standard | `Standard Registry Unit` | `1/Day` | `retailPrice × 30` (~$20) | -| Premium | `Premium Registry Unit` | `1/Day` | `retailPrice × 30` (~$50) | - -⛔ **`1/Day` → × 30, NOT × 730.** The other ACR meters (`Task vCPU Duration` = `1 Second` build compute, `Data Stored` = `1 GB/Month`) are usage-based — exclude from the fixed monthly unless the app builds heavily. ACR Basic fixed cost ≈ **$5/mo**. - ---- - -### App Service - -**MCP call:** `pricing_get --service "Azure App Service" --region "{region}"` - -**Meter-to-SKU mapping:** - -| SKU | `meterName` | Notes | -|-----|-------------|-------| -| B1 | `B1` | Compute floor — no F1/Free (see [sku-matrix.md](sku-matrix.md)) | -| B2 | `B2` | | -| S1 | `S1 App` | | -| P1v3 | `P1 v3 App` | Linux/Windows have different `productName` | - -**Gotcha:** `armSkuName` empty for Basic/Standard. Match on `meterName`. - -**Monthly:** `retailPrice × 730` - ---- - -### Azure SQL Database - -**DTU model** — no `armSkuName`. Filter by `productName` + `meterName`. - -| SKU | `productName` contains | `meterName` (unit: 1/Day) | -|-----|----------------------|-------------| -| Basic (5 DTU) | `SQL Database Single Basic` | `B DTU` | -| S0 (10 DTU) | `SQL Database Single Standard` | `S0 DTUs` | -| S1 (20 DTU) | `SQL Database Single Standard` | `S1 DTUs` | - -**Monthly (DTU):** `retailPrice × 30` - -**vCore model:** `productName` filters — Serverless: `'SQL Database General Purpose - Serverless - Compute Gen5'`. Hyperscale: `'SQL Database Single/Elastic Pool Hyperscale'`. Monthly: `retailPrice × 730`. - ---- - -### Cosmos DB - -Filter: `serviceName eq 'Azure Cosmos DB' and armRegionName eq '{region}' and priceType eq 'Consumption' and isPrimaryMeterRegion eq true` - -| Model | Match on | Monthly formula | -|-------|----------|----------------| -| Provisioned | `meterName eq '100 RU/s'`, `productName eq 'Azure Cosmos DB'` | `(targetRU / 100) × retailPrice × 730` | -| Autoscale | `productName eq 'Azure Cosmos DB autoscale'`, `meterName` starts with `AP` | varies | -| Serverless | `productName eq 'Azure Cosmos DB serverless'`, `meterName eq '1M RUs'` | `retailPrice × estimatedMillionsOfRU` | - -> ⚠️ **Provisioned 100 RU/s gotcha:** The `isPrimaryMeterRegion eq true` entry has `retailPrice: 0` (free-tier placeholder). The real price (~$0.008/hr in eastus) has `isPrimaryMeterRegion: false`. Drop `isPrimaryMeterRegion` from the filter or match on `retailPrice > 0`. - -**Storage:** `meterName eq 'Data Stored'` with `productName eq 'Azure Cosmos DB'` (1 GB/Month). Note: this meter has `isPrimaryMeterRegion: false` — drop that filter or query without it. - -**Free tier:** 1,000 RU/s + 25 GB per account (one per subscription). - ---- - -### Storage - -No `armSkuName`. Filter by `meterName` + `productName`. - -**Filter (Hot LRS Blob):** -``` -serviceName eq 'Storage' and armRegionName eq '{region}' and meterName eq 'Hot LRS Data Stored' and productName eq 'Blob Storage' -``` - -**Meter-to-SKU mapping:** - -| SKU matrix tier | `meterName` | `productName` | -|----------------|-------------|---------------| -| Standard_LRS | `Hot LRS Data Stored` | `Blob Storage` or `General Block Blob v2` | -| Standard_GRS | `Hot GRS Data Stored` | `Blob Storage` or `General Block Blob v2` | -| Premium_LRS | `Premium LRS Data Stored` | `Premium Block Blob` | - -**Monthly:** `retailPrice × estimatedGB` - ---- - -### Service Bus - -**Filter:** -``` -serviceName eq 'Service Bus' and armRegionName eq '{region}' -``` - -> ⚠️ **Do NOT add `isPrimaryMeterRegion eq true`** — all Service Bus meters have `isPrimaryMeterRegion: false`. Adding that filter returns zero useful results. - -**Key meters:** - -| Tier | `meterName` | Unit | Notes | -|------|-------------|------|-------| -| Basic | `Basic Messaging Operations` | 1M | Usage-based only | -| Standard | `Standard Base Unit` | 1/Hour | Base cost (always-on) | -| Standard | `Standard Messaging Operations` | 1M | Tiered: first 13M free | -| Premium | `Premium Messaging Unit` | 1/Hour | Per messaging unit | - -**Monthly (Standard):** `baseUnitRate × 730` + operations beyond free tier. Standard tiered: first 13M free. - -**Gotcha:** Standard Base Unit has two entries — hourly and monthly. Use hourly × 730. - ---- - -### Redis Cache - -`armSkuName` IS populated — `pricing_get --sku` works. - -**MCP call:** `pricing_get --service "Redis Cache" --sku "{armSkuName}" --region "{region}"` - -**armSkuName pattern:** `Azure_Redis_Cache_{Tier}_{Size}_Cache` (e.g., `Azure_Redis_Cache_Basic_C0_Cache`, `Azure_Redis_Cache_Standard_C1_Cache`). Query the MCP tool for exact pricing. - -**Gotcha:** Basic tier entries have `isPrimaryMeterRegion: false`. The standard filter strips them out. Query by `armSkuName` directly or omit `isPrimaryMeterRegion` and match on `productName eq 'Azure Redis Cache Basic'`. - -**Monthly:** `retailPrice × 730` - ---- - -### Azure Database for PostgreSQL Flexible Server - -`armSkuName` IS populated — `pricing_get --sku` works. ⛔ **`skuName` is CASE-SENSITIVE:** `B1MS` works, `B1ms` returns 0 results. - -**MCP call:** `pricing_get --service "Azure Database for PostgreSQL" --sku "{armSkuName}" --region "{region}"` - -**armSkuName values:** ⛔ Case-sensitive. Small Burstable uses short names (`B1MS`, `B2S`); larger Burstable + GP/MO use `Standard_` prefix. - -| SKU | `armSkuName` | -|-----|-------------| -| Burstable B1ms | `B1MS` | -| Burstable B2s | `B2S` | -| Burstable B2ms | `Standard_B2ms` | -| General Purpose D2ds_v5 | `Standard_D2ds_v5` | - -**Storage:** Query separately — `meterName eq 'Storage Data Stored'` and `productName` containing `Flexible Server Storage`. Unit: 1 GiB/Month. Default 32 GiB included at ~$0.115/GiB/mo. - -**Monthly (compute):** `retailPrice × 730` -**Monthly (storage):** `storageRate × storageSizeGB` -**Total:** compute + storage (e.g., B1ms + 32 GiB ≈ $12.41 + $3.68 = ~$16.09) - ---- - -### Functions - -**Consumption plan:** Not in API. Free: 1M executions + 400K GB-seconds/month. - -**Flex/Premium** — filter: `serviceName eq 'Functions' and armRegionName eq '{region}' and priceType eq 'Consumption' and isPrimaryMeterRegion eq true` -- Flex: `On Demand Execution Time` (1 GB Second), `On Demand Total Executions` (10), `Always Ready Baseline` -- Premium: `Premium vCPU Duration` (1 Hour), `Premium Memory Duration` (1 GiB Hour). Monthly: `vCPU_rate × 730 + memory_rate × GiB × 730` - ---- - -### Static Web Apps - -**Not in the Retail Prices API.** Fixed pricing — query [azure.microsoft.com/pricing/details/app-service/static](https://azure.microsoft.com/en-us/pricing/details/app-service/static/): -- **Standard tier (floor — Free is never selected):** Flat monthly per app, unlimited bandwidth, 5 custom domains, SLA, private endpoints - ---- - -### Log Analytics / Application Insights - -**Filter:** -``` -serviceName eq 'Azure Monitor' and armRegionName eq '{region}' and priceType eq 'Consumption' and isPrimaryMeterRegion eq true -``` - -**Key meters:** -- `Platform Logs Data Processed` (unit: 1 GB) -- Log Analytics ingestion — match `meterName` containing `Data Ingestion` - -**Free grant:** First 5 GB/month of Log Analytics data ingestion. - -**Monthly:** `retailPrice × estimatedGB` (subtract 5 GB free grant). diff --git a/.github/agents/azure-deploy/prepare/references/pricing-guide.md b/.github/agents/azure-deploy/prepare/references/pricing-guide.md deleted file mode 100644 index d7ba6bf..0000000 --- a/.github/agents/azure-deploy/prepare/references/pricing-guide.md +++ /dev/null @@ -1,73 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# App Onboard Prepare — Pricing Guide - -> ⛔ **SELF-CHECK:** If `costEstimate.breakdown[]` has ANY `monthlyUsd > 0`, at least one `pricing_get` or `az rest` pricing API call MUST appear in this session. Estimating from memory or training data is NEVER acceptable — reference values in this file may be outdated. If API fails after 2 attempts, use the reference value WITH disclaimer: `"⚠️ Estimated from reference — live API unavailable."` - -## No Free-Tier Shortcut — Compute Always Has Cost - -> ⛔ **There is no $0 shortcut.** Compute has a floor (App Service **B1**, Static Web Apps **Standard**, Functions **Flex Consumption**) — F1/D1/Free/Consumption are never selected (see [sku-matrix.md](sku-matrix.md)). So any app with a compute component has a non-zero monthly cost and MUST get a live pricing call. Do NOT write a $0 estimate for a compute app. - -Some services can still fall within free **grant** limits (they are metered, not a fixed free SKU) — note the grant in the estimate but still price the paid meters: - -| Service | Free grant (still price paid usage above it) | -|---------|-----------------------------------------------| -| Functions (Flex Consumption) | First 250K executions + 100K GB-s/month | -| Cosmos DB | Free-tier account: 1000 RU/s + 25 GB (one per subscription) | -| Container Apps | First 180K vCPU-s + 360K GiB-s + 2M requests/month | -| Log Analytics | First 5 GB/month ingestion | - ---- - -## App Service Quick Reference - -| SKU | `skuName` | Monthly | -|-----|-----------|---------| -| B1 Linux | `B1` | ~$13.14 | - -> ⛔ **Case-sensitive:** use `B1` not `b1`. Use `skuName` (not `armSkuName`) for Basic/Standard. - ---- - -## How to Use - -1. Pick SKU from [sku-matrix.md](sku-matrix.md) based on budget intent. -2. Find service section in [pricing-guide-services.md](pricing-guide-services.md) for filter strings and formulas. -3. Call the pricing router — tool `mcp_azure_mcp_pricing` (VS Code) / `azure-pricing` (CLI) — with `intent`, `command: "pricing_get"`, and a `parameters` object (NOT `--flags`). Parallel OK. ⛔ At least one filter inside `parameters`: `sku`, `service`, `region`, `service-family`, or `filter`. ⛔ **`sku` matches `armSkuName`, not `skuName`** — use it ONLY when `armSkuName` is populated (App Service, MySQL/PostgreSQL, Redis). Empty-`armSkuName` services (ACR, Storage, Cosmos) return `[]` or 400 for `sku`/`service` — use a raw `filter` on `serviceName` + `meterName` instead. -4. Apply the monthly multiplier from each meter's `unitOfMeasure` (see § Monthly Multiplier) — NOT a per-service constant. -5. Cross-check returned `retailPrice` against planned SKU. - -## Monthly Multiplier — by Meter Unit - -⛔ **Read `unitOfMeasure` per price record — NEVER blanket `× 730`** (one service mixes units, e.g. ACR returns `1/Day` + `1 Second` + `1 GB/Month`): - -| `unitOfMeasure` | Monthly formula | -|-----------------|-----------------| -| `1 Hour` / `1/Hour` | `retailPrice × 730` | -| `1/Day` / `1 Day` | `retailPrice × 30` | -| `1 GB/Month` | `retailPrice × estimatedGB` (already monthly) | -| `1 Second` / `1 GiB Second` | `retailPrice × activeUnitsPerMonth` (usage-based) | -| `10K` / `1M` operations | `retailPrice × estimatedOps ÷ unitSize` | - -## Usage-Based Services - -When AI/OpenAI/LLM components detected: add `"💸 Usage-based — excluded from total"` to `costEstimate.breakdown[]` and `"⚠️🤖 AI inference costs excluded"` to `assumptions[]`. Surface at EVERY approval gate. - -## Service Pricing Reference - -See [pricing-guide-services.md](pricing-guide-services.md) for per-service filter strings, meter names, and monthly formulas. - -**Key rules:** -- `armSkuName` is empty for most PaaS — filter by `meterName` or `productName` -- Monthly multiplier: read `unitOfMeasure` (§ Monthly Multiplier) — never assume ×730 -- Functions Consumption / Static Web Apps: not in API -- Default `isPrimaryMeterRegion eq true` + `priceType eq 'Consumption'` — but check the service section in [pricing-guide-services.md](pricing-guide-services.md) first; some drop `isPrimaryMeterRegion` (Service Bus, Cosmos 100 RU/s, Redis Basic). -- Service names are case-sensitive - -## Troubleshooting - -When `pricing_get` returns empty: (1) Check `armSkuName` populated/case-sensitive? (2) HTTP fallback: `https://prices.azure.com/api/retail/prices?$filter={filter}`. (3) After 2 failures, use [pricing-guide-services.md](pricing-guide-services.md) with disclaimer. (4) Log to `costEstimate.apiFailures[]`. - -> ⛔ **Never claim user has free credits remaining.** Use: "Check balance at portal." - diff --git a/.github/agents/azure-deploy/prepare/references/service-mapping.md b/.github/agents/azure-deploy/prepare/references/service-mapping.md deleted file mode 100644 index b3ec9c9..0000000 --- a/.github/agents/azure-deploy/prepare/references/service-mapping.md +++ /dev/null @@ -1,130 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Service Mapping Tables - -Component→Azure service selection. Apply `context.json.intent` as modifiers, `context.json.overrides[]` as hard constraints, and policy constraints as filters. - -## Hosting - -> ⛔ **Existing framework beats generic component type.** If a component path contains a `host.json` file -> and an Azure Functions SDK or worker configuration, select Azure Functions for that component. Do not -> route it through the generic REST/GraphQL API row, merge it into a frontend service, or package it as an -> SWA-managed API. When a SPA and Functions component coexist, `services[]` must contain separate frontend -> and Azure Functions entries whose `component` values match the detected component names. - -> ⛔ **Implicit dependencies:** When selecting Container Apps and the component has a Dockerfile (or `hasDockerfile: true` in prereq), **ALWAYS include Container Registry (Basic)** in `services[]`. Container Apps requires ACR to host custom images — omitting it forces an imperative add during deploy, wasting a healing round. ACR Basic is $0.17/day (~$5/mo). - -| Component Type | Primary Service | Alternatives | Selection Signal | -|---------------|----------------|-------------|-----------------| -| SPA Frontend | Static Web Apps | Blob + CDN | React/Vue/Angular, no SSR | -| Existing Azure Functions project | Azure Functions (Flex Consumption) | Azure Functions Premium | `host.json` plus Functions SDK or worker configuration | -| SSR Web App | Container Apps | App Service, AKS | Next.js/Nuxt, server-rendered | -| REST/GraphQL API | Container Apps | App Service, Functions, AKS | Express/Fastify/Flask/FastAPI | -| Background Worker | Container Apps (scale-to-zero) | Functions, AKS | Celery/Bull/Agenda, no HTTP | -| Scheduled Task | Functions (Timer) | Container Apps Jobs | Cron patterns, periodic execution | -| Event Processor | Functions | Container Apps + KEDA | Event-driven, queue/topic consumer | -| Microservices (K8s) | AKS | Container Apps | kubectl/helm in repo, CRDs, service mesh | -| GPU/ML Workloads | AKS | Azure ML | GPU requirements, training workloads | - -**Stack shortcuts:** Containers (Docker, microservices) → Container Apps or AKS. Serverless (event-driven, variable traffic) → Functions. Traditional web (PaaS preference) → App Service. - -**Frontend plus Functions:** Default to a detached frontend host and a separately deployed Function App. -For Static Web Apps, configure the Function App CORS allowlist with the SWA origin. Do not change the -Function App's runtime, authentication provider, or app settings to `azureStaticWebApps`. Only link the -existing Function App as an SWA backend when `context.json.overrides[]` records explicit user approval for -`linkFunctionsToStaticWebApp`; linking does not remove the Function App service or change its deployment -channel. - -**AKS vs Container Apps:** Use Container Apps when scale-to-zero needed, no K8s expertise, or KEDA-driven event processing. Delegate AKS planning to the `azure-kubernetes` agent. - -**App Service vs Container Apps:** App Service preferred for single-process apps with no container orchestration (B1 floor, ~$13/mo — no free tier is offered). Container Apps preferred for REST/GraphQL APIs (scaffold generates Dockerfile if missing), scale-to-zero, multi-container/sidecar, or event-driven KEDA scaling. Budget affects SKU tier (Consumption vs Dedicated), not compute service type. Container Apps Consumption scales to zero (near-$0 idle) — the closest thing to free, and it supports managed identity. - -**Static Dockerfile sites (nginx/httpd serving HTML):** -- **Primary:** Static Web Apps **Standard** — global CDN, custom auth/CORS, managed-identity BYO backends. - > ⛔ **SWA region availability:** Validate via `az provider show --namespace Microsoft.Web --query "resourceTypes[?resourceType=='staticSites'].locations" -o tsv`. -- **Alternative:** App Service **B1** — ⛔ for static content use Windows B1 (IIS serves `index.html` natively) or Linux B1 (`linuxFxVersion: 'STATICSITE|1.0'`). ⛔ Do NOT use `NODE|*`/`PHP|*`/`PYTHON|*` for static sites — causes 504/503 cold start. -- **If Docker required:** Container Apps (scale-to-zero) or App Service B1+ (custom containers). - -**Plain HTML (no package manager, no Dockerfile):** Static Web Apps Standard preferred, or Windows App Service B1 (IIS serves natively; Linux: `linuxFxVersion: 'STATICSITE|1.0'`). - -## Data - -| Need | Primary Service | Alternatives | Selection Signal | -|------|----------------|-------------|-----------------| -| Relational | Azure SQL | PostgreSQL Flexible, MySQL Flexible | SQL schema, transactions, joins | -| Document/NoSQL | Cosmos DB | — | JSON docs, global distribution | -| Graph (Gremlin) | Cosmos DB (Gremlin API) | — | Graph traversal, relationships | - -> **Cosmos DB Serverless:** Each Serverless account supports ONE API type (SQL, MongoDB, Gremlin, Table, Cassandra). Multi-API apps require separate Serverless accounts. - -| Cache | Redis Cache | — | Session store, rate limiting | -| Files/Blobs | Blob Storage | Files Storage | File uploads, static assets | -| Search | AI Search | — | Full-text search requirements | -| MariaDB/MySQL | MySQL Flexible Server | — | `mariadb:*` or `mysql:*` in compose / connection config | -| Elasticsearch/OpenSearch | AI Search | Azure Monitor (for Kibana) | `elasticsearch:*` or `opensearch:*` in compose | -| S3-compatible / MinIO | Blob Storage | — | `minio/*` in compose, S3 SDK usage | - -## Integration - -| Need | Primary Service | Selection Signal | -|------|----------------|-----------------| -| Message Queue | Service Bus | Point-to-point, ordered, transactions | -| Pub/Sub | Event Grid | Event routing, fan-out | -| Streaming / Kafka | Event Hubs | High-throughput telemetry, logs. **Kafka protocol compatible** — apps using `kafka-clients`, Spring Kafka, `confluent-kafka-python` connect by changing bootstrap URL + SASL config only | -| Multi-step orchestration | Durable Functions + Durable Task Scheduler | DTS is the recommended managed backend | -| Low-code workflow | Logic Apps | Integration-heavy, visual designer | - -> **docker-compose → Azure mapping:** Map each `detectedServices[].type` to its PaaS equivalent using these tables. **Unmapped services** (SSH daemons, custom sidecars — no managed equivalent): offer a Linux VM (B1s ~$7/mo) as companion at the scaffold gate. Present as option, not forced. Write to `postDeployRecommendations[]`. - -## Supporting (always include) - -| Service | Purpose | -|---------|---------| -| Log Analytics | Centralized logging | -| Application Insights | Monitoring + APM | -| Managed Identity | Service-to-service auth (zero secrets) | - -## Specialized Routing - -Check before mapping — delegate to specialized agent if matched: - -| Signal | Delegate To | -|--------|-------------| -| Copilot SDK, `@github/copilot-sdk` | `azure-hosted-copilot-sdk` | -| Foundry agent, AI agent deployment | `microsoft-foundry` | - -> ⛔ **Non-Azure cloud SDK deps** (AWS/GCP/Firebase) are handled by prereq — the pipeline does NOT reach prepare with cloud SDK findings present. If you see cloud SDK findings here, prereq failed to stop — HALT and do NOT continue architecture planning. - -## Non-Azure Terraform Resource Mapping - -When `context.json.detectedInfraProvider.terraform` is `"gcp"` or `"aws"`, read existing `.tf` files and map cloud resources to Azure equivalents. These mappings provide architecture signal for service selection — use alongside component detection. - -### GCP → Azure - -| GCP Terraform Resource | Azure Equivalent | Notes | -|------------------------|-----------------|-------| -| `google_cloud_run_v2_service` | Container Apps | Map scaling, env vars, VPC config | -| `google_sql_database_instance` (POSTGRES) | PostgreSQL Flexible Server | Map tier, backup, maintenance config | -| `google_sql_database_instance` (MYSQL) | MySQL Flexible Server | Map tier, backup config | -| `google_artifact_registry_repository` | Container Registry (ACR) | Basic tier unless geo-replication needed | -| `google_pubsub_topic` / `google_pubsub_subscription` | Service Bus | Map topic/subscription model | -| `google_secret_manager_secret` | On-compute app secret (App Service app settings / CA native secrets) — ⛔ no Key Vault | App-internal secrets only; Azure resource auth uses managed identity | -| `google_firestore_database` | Cosmos DB (NoSQL API) | Map indexes, TTL config | -| `google_cloudfunctions2_function` | Azure Functions | Map triggers, runtime | -| `google_service_account` + `google_project_iam_member` | Managed Identity + RBAC | Map role bindings | - -Other GCP resources (storage, redis, compute network, VPC connector, cloud tasks) map 1:1 to their Azure equivalents (Blob Storage, Redis Cache, VNet, Queue Storage). - -### AWS → Azure - -| AWS Terraform Resource | Azure Equivalent | Notes | -|------------------------|-----------------|-------| -| `aws_ecs_service` / `aws_ecs_task_definition` | Container Apps | Map task def → container config | -| `aws_rds_instance` (postgres/mysql) | PostgreSQL/MySQL Flexible Server | Map instance class → SKU | -| `aws_lambda_function` | Azure Functions | Map runtime, handler, triggers | -| `aws_dynamodb_table` | Cosmos DB (NoSQL API) | Map capacity, indexes | -| `aws_sns_topic` | Service Bus or Event Grid | Map subscriptions | -| `aws_secretsmanager_secret` | On-compute app secret (App Service app settings / CA native secrets) — ⛔ no Key Vault | App-internal secrets only; Azure resource auth uses managed identity | - -Other AWS resources (S3, ECR, SQS, ElastiCache) map 1:1 to their Azure equivalents (Blob Storage, ACR, Queue Storage/Service Bus, Redis Cache). SQS → Service Bus if FIFO ordering needed. diff --git a/.github/agents/azure-deploy/prepare/references/sku-matrix.md b/.github/agents/azure-deploy/prepare/references/sku-matrix.md deleted file mode 100644 index 4b57889..0000000 --- a/.github/agents/azure-deploy/prepare/references/sku-matrix.md +++ /dev/null @@ -1,46 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# SKU Selection Matrix - -Select SKU based on `context.json.intent.budget`. Cross-reference with `intent.scale` for right-sizing. - -## Budget Tiers - -> ⛔ **Free/shared tiers (App Service F1/D1, Static Web Apps Free) are NOT offered.** Every app requires a managed identity for Azure resource auth, and the free-tier MI sidecar OOMs on Linux; free SWA also lacks CORS/custom-auth config these apps need. The compute floor is **B1 (App Service)** / **Standard (Static Web Apps)**. Never emit F1, D1, or Free. - -| Service | cost-optimized | balanced | performance | -|---------|---------------|----------|-------------| -| Container Apps | Consumption (scale-to-zero) | Consumption | Dedicated | -| App Service | B1 (Basic) | B1 / S1 | P1v3 | -| Azure SQL | Basic (5 DTU) | Standard (S0) | Premium / Serverless | -| Cosmos DB | Serverless | Provisioned (400 RU) | Provisioned (autoscale) | -| Storage | Standard_LRS | Standard_GRS | Premium_LRS | -| Static Web Apps | Standard | Standard | Standard | -| Service Bus | Basic | Standard | Premium | -| Redis Cache | Basic C0 | Standard C1 | Premium P1 | -| Functions | Flex Consumption | Flex Consumption | Premium EP1 | -| Log Analytics | PerGB2018 | PerGB2018 | PerGB2018 (dedicated cluster) | - -> ⚠️ **Log Analytics `Free` SKU is deprecated.** ARM rejects it with some API versions and retention settings. Always use `PerGB2018` — first 5 GB/month is free anyway. - -> ⛔ **Functions floor is Flex Consumption — NOT Consumption or Elastic Premium.** The Consumption and Elastic Premium plans back `AzureWebJobsStorage` with Azure Files, which does **not** support managed-identity connections and therefore **requires `allowSharedKeyAccess` on the storage account** — banned by the managed-identity-only rule (and the KV workaround for the Files connection string is also banned). **Flex Consumption** supports identity-based `AzureWebJobsStorage` (`AzureWebJobsStorage__accountName` + `AzureWebJobsStorage__credential=managedidentity`), so it is the only Functions plan compatible with `allowSharedKeyAccess: false`. Never select Consumption/Elastic Premium — this is the same floor-raise as App Service F1→B1. - -## Modifier Rules - -- `intent.scale = "Large"` (100K+ users) → bump minimum one tier above cost-optimized -- `intent.budget = "performance"` + `intent.scale = "Small"` → don't over-provision; use balanced tier -- Policy denies a SKU → fall back to next available tier (never below the B1 / SWA Standard floor), add to `assumptions[]` -- Cosmos DB Serverless: max 1K RU/s burst, no geo-replication — only for intermittent/dev workloads. Sustained read-heavy → Provisioned. - -## Default Behavior - -- Single-component app + cost-optimized → cheapest **viable** SKU at or above the floor (App Service B1, Static Web Apps Standard) -- Simple web app with no DB → ~$13–$20/month (B1 minimum — no $0 tier) -- Always output exact SKU codes: "App Service B1 (Basic)" not "cheapest tier" - -## Auto-Cheapest for Fast-Track - -⛔ **If `prereq-output.json.fastTrackEligible == true` AND `context.json.intent.budget` is unset, treat as `cost-optimized`.** Fast-track means single-component + no DB + no auth + no Dockerfile. Pick **App Service B1 (Basic)** for dynamic apps that need a runtime (Node.js/Python starter templates), or **Static Web Apps Standard** for pure static HTML/JS/CSS with no server-side runtime. The user still sees the SKU + monthly cost in the scaffold approval gate — they can override via "Edit plan". Do NOT ask the user about budget unless they mention cost first (per [intent-gathering.md](../../references/intent-gathering.md)). - -⛔ **There is no free promise — fast-track starts at the B1 / SWA Standard floor.** If the chosen floor SKU is unavailable during deploy prep (no B1 quota — see [sku-quota-validation.md](sku-quota-validation.md) § After Checking), fall back to the **cheapest AVAILABLE** tier at or above the floor. Record an `assumptions[]` note stating WHY the SKU differs, so the approval gate presents the resulting cost. diff --git a/.github/agents/azure-deploy/prepare/references/sku-quota-validation.md b/.github/agents/azure-deploy/prepare/references/sku-quota-validation.md deleted file mode 100644 index d880b75..0000000 --- a/.github/agents/azure-deploy/prepare/references/sku-quota-validation.md +++ /dev/null @@ -1,108 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# SKU Quota Validation Procedure - -Pre-deploy quota and offer restriction checks. Read during prepare Step 5 before deploying. - -For SKU selection (budget tiers, modifier rules, defaults), see [sku-matrix.md](sku-matrix.md). - -## Quota Validation Procedure - -> ⛔ **Use `az rest`, NOT `az quota list`.** The `az quota list` CLI extension triggers a full extension metadata scan on startup. If ANY installed extension has a permission error (common: `azure-devops` WinError 5 on Windows), the entire command fails. `az rest` is a built-in command that bypasses extension loading entirely and hits the same REST API. Quota increases are free — you only pay for resources actually used. - -> ⛔ **`what-if` does NOT catch App Service quota errors.** `az deployment sub what-if` returns `Succeeded` even when the target SKU has limit=0. The quota rejection only surfaces at actual `az deployment sub create` time. This means the prepare-phase quota check is the ONLY pre-deploy safety net — do not skip or shortcut it. - -> ⛔ **Do NOT use `az vm list-usage`, `az appservice list-locations`, or `mcp_azure_mcp_quota`** for quota checks. They return misleading data — see [Anti-Patterns](#anti-patterns) below. - -### Region Selection - -Build the scan list dynamically — do NOT hardcode a fixed set of regions: - -1. **User's preferred region** — read `context.json.azure.region` or `context.json.overrides[]` for a region preference. If stated, scan that region first. -2. **Nearest alternates** — add 3–4 regions geographically close to the user's preferred region from the global pool below. -3. **If no user preference** — default to a well-supported region (e.g., `eastus2`) and scan 4 alternates from the user's likely geography (infer from subscription tenant location or ask). - -**Global region pool:** `eastus2`, `eastus`, `westus2`, `centralus`, `westeurope`, `northeurope`, `australiaeast`, `japaneast`, `southeastasia`, `brazilsouth` - -> **Agent: adapt shell syntax to detected environment.** PowerShell shown below; use equivalent syntax on bash/zsh (e.g., `for region in eastus eastus2 ...; do ... done`). - -> ⛔ **PowerShell `?` in URLs:** When building `az rest` URLs with variable interpolation, PowerShell may strip `?` from `?api-version=`. Always use the URL **inline in double quotes** (as shown below), NOT via a `$url` variable. If you must use a variable, wrap the `?` with a backtick: `` `?api-version= ``. - -### Per-Provider Scripts - -Use `az rest` for all quota checks. Query BOTH limit AND usage (limit alone is insufficient). - -**App Service:** Query quota + usages endpoints per region: -```powershell -$sub = '{subscriptionId}'; $sku = '{sku}' -@('{userRegion}','{alt1}','{alt2}','{alt3}') | ForEach-Object { - $limit = az rest --method get --url "https://management.azure.com/subscriptions/$sub/providers/Microsoft.Web/locations/$_/providers/Microsoft.Quota/quotas/$sku?api-version=2023-02-01" --query "properties.limit.value" -o tsv 2>$null - $used = az rest --method get --url "https://management.azure.com/subscriptions/$sub/providers/Microsoft.Web/locations/$_/providers/Microsoft.Quota/usages/$sku?api-version=2023-02-01" --query "properties.usages.value" -o tsv 2>$null - # limit=0 with used=-1 is the API's "SKU not offered here" sentinel — treat limit<=0 as BLOCKED and clamp negative usage so 0-(-1) does NOT become a false-positive 1. - $ln = if ($limit) { [int]$limit } else { $null }; $un = if ($used) { [int]$used } else { 0 } - $avail = if ($null -eq $ln) { 'unknown' } elseif ($ln -le 0) { 0 } else { $ln - [math]::Max(0, $un) } - Write-Host "$_ : $sku limit=$limit available=$avail" -} -``` - -**Container Apps:** `/usages` gives usage+limit in one call: -```powershell -az rest --method get --url "https://management.azure.com/subscriptions/$sub/providers/Microsoft.App/locations/{region}/usages?api-version=2024-03-01" --query "value[?name.value=='ManagedEnvironmentCount'].{used:currentValue, limit:limit}" -o json -``` - -**Static Web Apps:** No `Microsoft.Quota` provider. SWA **Standard** is the floor (Free is never selected — see [sku-matrix.md](sku-matrix.md)); Standard has no per-subscription app cap to check. No quota gate needed. - -**Storage** — default limit 250 accounts/region. Rarely exhausted — skip programmatic check unless the plan requires multiple storage accounts. - -### Interpret Results - -- `available > 0` → AVAILABLE. `available = 0` / `limit <= 0` → BLOCKED (a `limit=0`, `used=-1` response is the API sentinel for "SKU not offered in this region" — the script clamps it so it does not read as available). 404/empty → fallback candidate. -- `az rest` fails → `quotaValidation: { verified: false, method: "unverifiable" }`. - -### After Checking - -1. Only offer regions with **confirmed** capacity — "try anyway" on zero/unconfirmed quota is a known deploy failure. -1b. **Floor SKU missing in requested region but present elsewhere** → present BOTH, ranked by cost: (a) the floor SKU (B1 / SWA Standard / Flex Consumption) in the nearest confirmed region, (b) the cheapest available SKU IN the requested region. Both show monthly cost + an `assumptions[]` note. User picks — never silently relocate (region may be a data-residency/latency requirement). -2. **Floor SKU unavailable in ALL regions** → step down the fallback ladder to the **cheapest available** tier at or above the floor (never below B1 / SWA Standard / Flex Consumption; let live quota decide the exact SKU): - - No option at the floor → the cheapest available higher tier the app supports. Add an `assumptions[]` note stating why (e.g., "No B1 quota in {checkedRegions}; selected {sku}"). - - All tiers exhausted → **HALT**: specify region, switch compute type, request increase at portal, or cancel. -3. Write `prepare-plan.json.quotaValidation`: `{ verified: true, method: "cli", verifiedRegion, verifiedSku, checkedRegions[], failedResources[] }`. - -### Offer Restriction Check (Database Services) - -> ⛔ `what-if`/`validate` do NOT catch `LocationIsOfferRestricted`. Use capabilities API. - -| Provider | API Version | -|----------|-------------| -| PostgreSQL | `2022-12-01` | -| MySQL | `2023-12-30` | - -```powershell -$sub = '{subscriptionId}'; $provider = 'Microsoft.DBforPostgreSQL'; $apiVer = '2022-12-01' -@('{userRegion}','{alt1}','{alt2}','{alt3}') | ForEach-Object { - $result = az rest --method get --url "https://management.azure.com/subscriptions/$sub/providers/$provider/locations/$_/capabilities?api-version=$apiVer" --query "value[0].supportedFlexibleServerEditions[0].name" -o tsv 2>$null - if ($result) { Write-Host "$_ : $provider AVAILABLE ($result)" } else { Write-Host "$_ : $provider BLOCKED (offer restricted)" } -} -``` - -> For MySQL: change `$provider = 'Microsoft.DBforMySQL'` and `$apiVer = '2023-12-30'`. - -⛔ JMESPath MUST start with `value[0].`. URL MUST include `/locations/{region}/`. Empty/null response = BLOCKED. Write results to `quotaValidation.offerRestrictions[]`. - -> ⛔ **Select the engine version deterministically from the capabilities payload** — match the app's detected version, upgrading only to the nearest compatible release. The payload lists supported versions at `value[0].supportedFlexibleServerEditions[0].supportedServerVersions[].name` (e.g. MySQL: `5.7`, `8.0.21`, `8.4`, `9.5`). Using the **detected DB version passed by the caller** (from `context.json.detectedServices[]`): -> 1. If the **exact detected version** (or its exact patch) is in the supported list → use it. -> 2. Else use the **lowest supported version whose major ≥ the detected major** (detected `5.7`, supported `[5.7, 8.0.21, 8.4, 9.5]` → `8.0.21`). Picking the lowest compatible major — not the newest — avoids the 60+ minute provisioning hangs seen on brand-new majors (e.g. `9.x`) and keeps compatibility with the app's driver/ORM. -> Return it in the quota output's per-service `version` field; the orchestrator copies it to `prepare-plan.json.services[].version` at plan-write (exact patch required — see [prepare-schemas.ts](prepare-schemas.ts) `version`). Record the bump in `assumptions[]` if the detected version was upgraded. - -### Anti-Patterns - -⛔ `az quota list` (extension failures), `az vm list-usage` (wrong layer), `az appservice list-locations` (ignores quota), `mcp_azure_mcp_quota` (misleading), `what-if`/`validate` (false positives). - -## Deploy Gate Re-Validation - -If `quotaValidation.verified == false` at deploy gate: re-run Per-Provider Scripts above. Pass → update quotaValidation. Fail → present alternatives. `az rest` fails → warn and proceed. - -## Sub-Agent Delegation - -When delegating from prepare Step 5, provide: `subscriptionId`, SKU list from `prepare-plan.json.services[].sku`, preferred region + fallbacks, list of managed database services, and this file's content. See [subagent-quota.md](subagent-quota.md) for the template. diff --git a/.github/agents/azure-deploy/prepare/references/subagent-pricing.md b/.github/agents/azure-deploy/prepare/references/subagent-pricing.md deleted file mode 100644 index 8fa8400..0000000 --- a/.github/agents/azure-deploy/prepare/references/subagent-pricing.md +++ /dev/null @@ -1,56 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Subagent Template — Cost Estimation (Step 6) - -Estimate monthly costs for planned Azure services using Azure Retail Prices API. - -## References to Read Internally - -Read BOTH before making any pricing calls: -- [pricing-guide.md](pricing-guide.md) — methodology, free-grant handling, API patterns, troubleshooting -- [pricing-guide-services.md](pricing-guide-services.md) — per-service filter strings, meter names, formulas - -## Input (provided by caller) - -| Field | Required | -|-------|----------| -| `services[]` with service type and selected SKU per service | YES | -| `region` — deployment region | YES | -| Budget tier (free / balanced / performance) | YES | - -## Output - -Return JSON (≤500 tokens): -```json -{ - "costEstimate": { - "monthlyTotal": 42.50, - "currency": "USD", - "breakdown": [ - { "service": "App Service", "sku": "B1", "monthlyUsd": 13.14, "formula": "retailPrice × 730" }, - { "service": "PostgreSQL Flexible Server", "sku": "Standard_B1ms", "monthlyUsd": 24.82, "formula": "compute + storage" } - ], - "freeGrants": [ - { "service": "Container Apps", "grant": "180K vCPU-sec + 360K GiB-sec", "monthlySavings": 0 } - ], - "assumptions": [], - "disclaimer": "Estimate based on Azure Retail Prices API. Verify at azure.com/pricing." - } -} -``` - -## Rules - -- ⛔ **Do NOT invoke any other agents or hand off** — no external agent calls of any kind. You are a pricing subagent only. Use direct HTTP to `https://prices.azure.com/api/retail/prices` for price queries (MCP pricing was already attempted inline by the caller). -- ⛔ **No $0 shortcut — compute always has cost** (App Service B1 / SWA Standard / Functions Flex Consumption floor). Price every compute app via live API; note free **grants** (Cosmos free-tier account, Container Apps/Functions grants) but still price paid meters. Do NOT return $0 for a compute app. -- ⛔ `armSkuName` is case-sensitive — use `B1` not `b1` -- ⛔ For services with empty `armSkuName` (Container Apps, Functions Flex Consumption, ACR, Storage, Cosmos): use `filter`/`meterName` matching — a `sku` filter returns `[]` -- ⛔ **Monthly multiplier = each meter's `unitOfMeasure`** (`1 Hour`→×730, `1/Day`→×30 [ACR/registry, SQL DTU], `1 GB/Month`→×GB, `1 Second`→usage) — NEVER blanket ×730; applying ×730 to a `1/Day` meter overstates ~24× -- ⛔ Use direct HTTP to `https://prices.azure.com/api/retail/prices` for all price lookups. Do NOT use `mcp_azure_mcp_pricing` — it was already tried inline and failed. -- ⛔ Never hardcode dollar amounts — always query live prices -- ⛔ If pricing API returns 400: verify `--sku` included in the query - -## Token Budget - -≤500 tokens for cost estimate report. diff --git a/.github/agents/azure-deploy/prepare/references/subagent-quota.md b/.github/agents/azure-deploy/prepare/references/subagent-quota.md deleted file mode 100644 index ec9acc5..0000000 --- a/.github/agents/azure-deploy/prepare/references/subagent-quota.md +++ /dev/null @@ -1,74 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Subagent Template — Quota Validation (Step 5) - -Validate SKU quota and offer restrictions across candidate regions before presenting region choices. - -## Critical Rules - -- ⛔ **Do NOT invoke any other agents or hand off** — no external agent calls of any kind. You are a quota-check subagent only. -- **Read [`sku-quota-validation.md`](sku-quota-validation.md) before executing ANY quota or offer restriction check.** It contains the per-provider API patterns, anti-patterns to avoid, offer restriction checks for database services, region selection logic, and output schema. All procedures live there — follow them exactly. - -## Input (provided by caller) - -| Field | Required | -|-------|---------| -| `subscriptionId` | YES | -| SKU list from Step 4 (service type + SKU per service) | YES | -| Restricted-offer services (PostgreSQL, MySQL) | If present in plan | -| **Detected DB version per DB service** (from `context.json.detectedServices[]`, e.g. MySQL `5.7`) | ⛔ REQUIRED if a DB is in the plan — the version-selection algorithm in [`sku-quota-validation.md`](sku-quota-validation.md) needs it. | - -## Output - -Return JSON (≤500 tokens): -```jsonc -{ - "quotaResults": [ - { - "region": "{checked region}", - "services": [ - { "service": "{provider}", "sku": "{sku}", "limit": "{from API}", "used": "{from API}", "available": "{limit - used > 0}" }, - // if DB service: add "offerRestricted": "{from capabilities API}", "version": "{selected per sku-quota-validation.md — exact patch, never major-only '8.0'}" - ], - "allAvailable": "{true only if ALL services in this region have available=true}" - } - // one entry per checked region - ], - "recommendedRegion": "{first region where allAvailable=true}", - "checkedRegions": ["{all regions checked}"], - "offerRestrictions": [ - // one entry per DB service+region checked — derive from capabilities API response - { "provider": "{namespace}", "region": "{region}", "restricted": "{true if blocked}", "reason": "{from API or null}" } - ], - "offerRestrictionsVerified": "{true only if every DB service from input has ≥1 entry in offerRestrictions[]}" -} -``` - -⛔ **Caller:** copy each DB service's returned `version` into `prepare-plan.json.services[].version` at plan-write — scaffold needs the exact patch (ARM rejects major-only `'8.0'`). - -## Workflow - -1. Read [`sku-quota-validation.md`](sku-quota-validation.md) -2. Run the per-provider quota checks for every SKU across all candidate regions -3. If restricted-offer services are in the input, run the offer restriction check for each database service in each candidate region per `sku-quota-validation.md` § Offer Restriction Check -4. Return results per the Output schema above (≤500 tokens) - -## Anti-Patterns (from sku-quota-validation.md — repeated here as guardrails) - -- ⛔ `az vm list-usage` — wrong provider, misleading data -- ⛔ `az appservice list-locations` — lists locations, NOT quota -- ⛔ `az appservice list-usages` — wrong scope -- ⛔ `mcp_azure_mcp_quota` — unreliable for App Service -- ⛔ `az quota list` — extension loading fails on Windows -- ⛔ Use `az rest` for ALL quota checks - -## Rules - -- ⛔ Paid ≠ unlimited — B1, Flex Consumption, Serverless all have per-subscription, per-region quotas (F1/Free are never selected — floor is B1) -- ⛔ Check BOTH limit AND current usage — `limit=1, usage=1` means FULL -- ⛔ For PostgreSQL/MySQL: run offer restriction check per `sku-quota-validation.md` § Offer Restriction Check - -## Token Budget - -≤500 tokens for quota results report. diff --git a/.github/agents/azure-deploy/prepare/references/validation-rubric.md b/.github/agents/azure-deploy/prepare/references/validation-rubric.md deleted file mode 100644 index 5176a88..0000000 --- a/.github/agents/azure-deploy/prepare/references/validation-rubric.md +++ /dev/null @@ -1,26 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Validation Rubric - -Run all 4 dimensions before writing `prepare-plan.json`. All must pass — a failure in any dimension triggers the [Error Handling](../instructions.md#error-handling) procedures. - -## Dimensions - -| Dimension | Pass Criteria | -|-----------|---------------| -| **Goal Alignment** | Every `context.json.intent` field reflected in service/SKU choice. No orphaned or omitted components: every detected deployable component maps to its own compute service or an explicitly approved shared host. Every detected Azure Functions component maps to Azure Functions, even when paired with a Static Web Apps frontend. `overrides[]` honored. | -| **WAF Alignment** | **Cost:** SKU matches budget; alternatives document cost tradeoffs. **Reliability:** Production plans include zone-redundant SKUs, GRS storage. **Security:** Managed identity everywhere; app-internal secrets stored on-compute (⛔ no Key Vault); private endpoints where budget allows. **Ops:** Log Analytics + App Insights included. **Performance:** SKU right-sized to scale — no over/under-provisioning. See [Azure WAF Service Guides](https://learn.microsoft.com/en-us/azure/well-architected/service-guides/) for per-service alignment. | -| **Dependency Completeness** | Every service has its dependencies present (Container Apps → Log Analytics; App Service → App Insights for monitoring; all services → managed identity + RBAC/data-plane role for auth — databases are Entra-only, no connection-string password). Cross-service references consistent (App Insights → Log Analytics workspace). | -| **Deployment Viability** | SKUs exist in target region (validated by quota/region check). No policy-blocked resources. Resource names conform to Azure naming rules. Quota sufficient or flagged with remediation. No free/shared SKUs (compute floor is B1 / SWA Standard / Functions Flex Consumption). | - -## Applying - -- **During plan creation (steps 3–7):** Use Goal Alignment and WAF Alignment as selection criteria. Use Dependency Completeness as cross-check after mapping. -- **Before writing (step 10):** Run all 4 as validation pass. Deployment Viability catches issues that surface after quota/naming. -- **On failure:** Fix inline via the [Error Handling](../instructions.md#error-handling) procedures. Document tradeoffs (e.g., WAF Reliability vs cost-optimized budget) in `assumptions[]`. - -## References - -- [Azure Well-Architected Framework](https://learn.microsoft.com/en-us/azure/well-architected/) — pillar definitions and tradeoff guidance -- [WAF Service Guides](https://learn.microsoft.com/en-us/azure/well-architected/service-guides/) — per-service WAF alignment checklists diff --git a/.github/agents/azure-deploy/prereq/instructions.md b/.github/agents/azure-deploy/prereq/instructions.md deleted file mode 100644 index 2e3873b..0000000 --- a/.github/agents/azure-deploy/prereq/instructions.md +++ /dev/null @@ -1,131 +0,0 @@ ---- -name: azure-app-onboard-prereq -description: "Assess whether source code is ready to deploy to Azure — the check BEFORE infrastructure work. Evaluates build health, app completeness, dependencies and local services, stack compatibility, and deployment feasibility. Answers questions about what your app needs before it can be deployed — frameworks, dependencies, and configuration. Checks whether dependencies are compatible and identifies deployment blockers and unsupported frameworks. WHEN: \"evaluate my repo\", \"is my app ready to deploy\", \"what does my app need to deploy\", \"what do I need before deploying\", \"does my app need\", \"can I ship this to Azure\", \"scan my repo for issues\", \"is this app deployable\", \"check if my app is ready for Azure\", \"do I need a Dockerfile\", \"what's blocking my deployment\", \"are there any blockers\", \"are my dependencies compatible\", \"does Azure support my framework\", \"what needs to change before deploying\", \"check my app configuration\"." -license: MIT -metadata: - author: Microsoft - version: "1.2.1" ---- - - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - - -# Azure App Onboard Prereq — Repository Evaluation - -Evaluate a user's repository for build health, app completeness, and Azure deployment feasibility — before infrastructure planning. Produces per-component verdicts (PASS/WARN/FAIL) consumed by downstream phases. - -> **Orchestrator relationship:** Called by the deploy agent's [`instructions.md`](../instructions.md) at Step 3. Return control to `instructions.md` (continue at Step 4) after writing artifacts — do NOT invoke downstream phases directly. - -Phase 1 of 4 in AppOnboard pipeline. Session: `.copilot-azure/sessions/{session-id}/`. Reads `context.json`. Writes `components[]`, `repo{}`, `detectedInfra[]`. Produces `prereq-output.json`. Schema: [`prereq-schemas.ts`](references/prereq-schemas.ts) — `PrereqOutput`, `BuildRequirements`. Direct entry supported. - -## When NOT to Use - -| Signal | Redirect | -|--------|----------| -| Validate infrastructure (Bicep/TF/azure.yaml) | **azure-validate** | -| Generate IaC | **azure-prepare** | -| End-to-end idea-to-production | **azure-app-onboard** | -| Run `azd up` or deploy | **azure-deploy** | - -## Rules - -> ⛔ **ABSOLUTE PROHIBITION — `npm install`, `npm test`, `npx jest`, `pytest`, and ALL install/build/test commands are NEVER allowed.** -> Under NO circumstances may you run `npm install`, `npm test`, `npx jest`, `pip install`, `pytest`, `dotnet build`, `dotnet restore`, `dotnet test`, `go mod download`, `cargo build`, or ANY package-manager install, build, or test command during the prereq phase. Do NOT run test suites to verify code — check for test config files statically instead. The prereq phase is read-only evaluation + static-only verification. -> **ONLY exception — two sanctioned contexts, both consent-gated:** (a) code the agent **modified** during migration/remediation (see [remediation-protocol.md](references/remediation-protocol.md) step 6), or (b) code the agent **wrote** from scratch on the zero-code path (see [zero-code-path.md](references/zero-code-path.md)). In either case, install/build/test runs ONLY via the user-confirmed build-validation gate ([build-check.md](references/build-check.md) Step 3), after the user answers that specific per-command consent prompt. General prior consent never counts. - -1. ⛔ **Full pipeline (Steps 1–8), no exceptions.** All prompts → Step 1 directly. Answer specific questions AS PART OF findings (Step 5), not before. -2. ⛔ **No sub-agents for evaluation.** 3-axis evaluation is inline. **Exception**: zero-code-path scaffolding (Step 2). -3. Code/destructive modifications require `ask_user`. Max 3 questions before results. Direct entry: don't repeat orchestrator's intent questions. - -## MCP Tools - -| Tool | Purpose | -|------|---------| -| `mcp_azure_mcp_get_azure_bestpractices` | Validate detected stack patterns against Azure best practices | -| `mcp_azure_mcp_extension_cli_install` | Check/install required CLI tools (az, azd, func) | - -## Workflow - -### Step 1: Session Check - -**Orchestrator entry:** Session exists — read `context.json`, proceed to Step 2. - -**Direct entry:** Check `.copilot-azure/sessions/active-session.json`: -- **Exists** → ⛔ read [session-protocol.md](references/session-protocol.md) for resume/fresh gate. Do NOT proceed until user answers. -- **Missing** → create session: generate UUID, `New-Item -ItemType Directory -Path ".copilot-azure/sessions/{uuid}" -Force`, write `context.json` + `active-session.json` via `create` tool. - -Then: `az account show` → merge `{id, name, tenantId}` into `context.json.azure`. ⛔ Session MUST exist on disk before any scanning. - -### Step 2: Scan Workspace - -Scan for project files. Detect components, `repo{}`, `detectedInfra[]`, `detectedServices[]`. Classify Terraform providers. Check CLI availability. Stack detection conflicts: user explicit statement wins (write to `context.json`, mark scan as override); scan-only → confirm with user; multiple stacks → show all and ask (see [component-mapping.md](references/component-mapping.md)); no code → [zero-code-path.md](references/zero-code-path.md). - -> ⛔ **Preserve Azure Functions service roots.** A directory with `host.json` plus an Azure Functions SDK or -> worker signal (`@azure/functions`, `Microsoft.NET.Sdk.Functions`, Functions Python decorators, or a -> `FUNCTIONS_WORKER_RUNTIME` setting) is an Azure Functions component. Record its exact path and identify its -> framework as Azure Functions in both `context.json.components[]` and `prereq-output.json.components[]`. -> Do not classify HTTP-triggered Functions as a generic REST API. When a SPA and Functions root both exist, -> record two components even if the frontend calls the backend through `/api`. - -> If no project files, no Dockerfile, AND no index.html → ⛔ read [zero-code-path.md](references/zero-code-path.md). - -> ⛔ **Cloud SDK early gate.** Grep for `aws-sdk|@aws-sdk|boto3|google-cloud|@google-cloud|firebase`. If functional deps found → read [cloud-sdk-migration.md](references/cloud-sdk-migration.md), then `ask_user`: **"Redirect to Azure Cloud Migrate"** (set `routeToSkill: "azure-cloud-migrate"`) · **"Continue evaluation anyway"** (finish readiness eval + SDK→Azure mapping, then STOP at Step 8 — no plan until the deps are swapped) · **"Cancel"**. - -### Step 3: Per-Component Evaluation - -| Sub-step | Action | Reference | -|----------|--------|-----------| -| 3.1 | **Build check** | ⛔ **You MUST read [build-check.md](references/build-check.md)** | -| 3.2 | **Completeness check** | ⛔ **You MUST read [completeness-check.md](references/completeness-check.md)** | -| 3.3 | **Deployability check** | ⛔ **You MUST read [deployability-check.md](references/deployability-check.md)** | -| 3.3a | **Component mapping** (conditional) | Read [component-mapping.md](references/component-mapping.md) ONLY IF >1 project manifest found (monorepo) | - -Populate `buildRequirements` per component after evaluation. Verdict propagation, tier rules, and f1Viable aggregation are in [readiness-gate.md](references/readiness-gate.md) and the individual check references. - -### Step 4: Write Artifacts + Readiness Gate - -⛔ Verify `context.json` exists on disk. Read [readiness-gate.md](references/readiness-gate.md) (verdicts, tiers, batch-then-approve, fast-track) then [prereq-artifacts.md](references/prereq-artifacts.md) (write procedures, schemas). - -### Step 5: Present Findings - -Per [readiness-gate.md § Present Findings](references/readiness-gate.md) — show verdicts grouped by severity before proceeding. - -### Step 6: Remediation (conditional) - -⛔ **You MUST read [remediation-protocol.md](references/remediation-protocol.md)** IF any ❌ FAIL verdict, 🔧 Recommended Fix, or ⚠️ WARN with `fixPhase: "prereq"` exists. Contains remediation loop, static verification, re-eval mandate, post-remediation artifact updates, and the build-validation consent gate. If all verdicts are ✅ PASS or ⚠️ WARN without `fixPhase: "prereq"`, skip to Step 7. - -### Step 7: Write Final State - -`completedPhases` already has `"prereq"` + `currentPhase: null` (from Step 4). Then: - -> ⛔ **Write `lastScanCommit`.** Run `git rev-parse HEAD` and store the full 40-character SHA as `context.json.repo.lastScanCommit`. Required — staleness guard in Step 1 compares to HEAD on resume to detect changes. - -### Step 8: Route - -⛔ **Mandatory — do NOT skip this step.** - -> **Routing fields:** All routing writes `routeToSkill` and `routeReason` to `context.json`. - -> **Post-remediation context:** If Step 6 ran, lead the routing prompt with: "Remediation complete — {N} issues fixed, your app is now {overallHealth}." - -> ⛔ **Evaluate rows top to bottom — first match wins.** - -| # | Condition | Action | -|---|-----------|--------| -| 1 | `routeToSkill` set (any entry) | `ask_user`: "Redirect to {routeToSkill}" / "Not now". ⛔ Pipeline stops — do NOT proceed to architecture planning. | -| 2 | `cloudSdkFindings[]` non-empty (user chose "Continue evaluation anyway") | Present the cloud-SDK → Azure swap mapping as 🔶 blockers, then `ask_user` with this exact prompt: **"🔶 Cloud SDK migration required — these dependencies must be swapped before this app can deploy to Azure. (Redirect to azure-cloud-migrate / Stop — swap manually and re-run)"** — Redirect sets `routeToSkill: "azure-cloud-migrate"`, Stop halts. ⛔ Pipeline stops — do NOT proceed to architecture planning, and do NOT offer a "continue to prepare" option; the app can't deploy until the deps are swapped. | -| 3 | Orchestrator + no `routeToSkill` | Tell the user: "✅ Your app has been evaluated and is ready — let's plan your Azure deployment." Then return to the deploy agent's [`instructions.md`](../instructions.md) and continue at Step 4 (Gather intent). ⛔ Do NOT stop, do NOT wait for user input, do NOT narrate internal handoffs. The user already consented to the full pipeline at scope triage. | -| 4 | Direct + ready/readyWithCaveats + no Azure infra | `ask_user`: "Deploy to Azure (full pipeline)" → invoke `azure-app-onboard` / "Not now" | -| 5 | Direct + ready/readyWithCaveats + existing Azure infra | `ask_user`: "Start fresh" → invoke `azure-app-onboard` / "Use existing infra" → invoke `azure-prepare` / "Not now" | -| 6 | Direct + blocked | Report blocker summary + "Fix and re-run." | - -Severity tiers (🛑🔶❌🔧⚠️✅) are defined in [readiness-gate.md](references/readiness-gate.md). - -## Outputs - -| Artifact | Location | Consumer | -|----------|----------|----------| -| Session context | `context.json` → `components[]`, `repo{}`, `detectedInfra[]`, `detectedServices[]` | All downstream phases | -| Prereq output | `prereq-output.json` | prepare phase (via `azure-app-onboard`) | -| Readiness report | `.copilot-azure/sessions/{uuid}/readiness-report.md` | User (offline reference) | \ No newline at end of file diff --git a/.github/agents/azure-deploy/prereq/references/build-check.md b/.github/agents/azure-deploy/prereq/references/build-check.md deleted file mode 100644 index 3570f1f..0000000 --- a/.github/agents/azure-deploy/prereq/references/build-check.md +++ /dev/null @@ -1,97 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Build Check - -> ⛔ **No build/install/test commands during this check.** Use static analysis only. - -Detect the project's language/framework stack and assess build health. Static detection (manifest reading) is **default**. Build execution is optional, requires user confirmation. - -> **Dynamic detection:** Agent determines build commands from the project manifest (e.g., `package.json` `scripts` for the project-specific build script name). Table below is guidance, not a fixed lookup. - -## Step 1: Detect Stack - -Scan the workspace for project files to determine the technology stack. - -| File | Language/Framework | -|------|-------------------| -| `package.json` | Node.js | -| `package.json` + `tsconfig.json` | TypeScript | -| `requirements.txt` / `pyproject.toml` | Python | -| `*.csproj` / `*.sln` | .NET | -| `pom.xml` | Java (Maven) | -| `build.gradle` / `build.gradle.kts` | Java/Kotlin (Gradle) | -| `go.mod` | Go | -| `Cargo.toml` | Rust | -| `Gemfile` | Ruby | -| `composer.json` | PHP | -| `docker-compose.yml` / `compose.yml` | (dependency source — parsed during deployability check) | -| `build.gradle` + `com.google.cloud.tools.jib` | Java (Jib) | - -**Modern package manager lockfiles:** `uv.lock` → uv, `bun.lock` / `bun.lockb` → bun. - -> ⚠️ If **no project file** is found, check for a `Dockerfile`. If a Dockerfile exists, note container-dependent build in findings. - -### Multi-Project Detection - -Search recursively for project manifests, skip `node_modules`/`.git`/`dist`/`build`/`vendor`/`.venv`/`__pycache__`/`.terraform`/`bin`/`obj`. Each directory with a project file = one component. - -## Step 2: Static Detection (Default) - -Read project manifests to infer build health **without executing any commands**. - -**Check for:** missing dependencies (imports not in manifest), version conflicts, obvious misconfigurations (missing `main`/`start` script), lock file presence. - -> **Package manager detection:** `package-lock.json` → npm, `yarn.lock` → yarn, `pnpm-lock.yaml` → pnpm. Default to npm if no lockfile. - -### Import → Manifest Cross-Check - -Scan source files for imports not in the dependency manifest. This catches pre-existing repo bugs that cause build failures on Azure. - -**Node.js/TypeScript:** Scan `*.ts`, `*.tsx`, `*.js`, `*.jsx`, `*.mjs` for `import` / `require` statements. Extract package name (first path segment, `@scope/name` for scoped). Check against `dependencies` + `devDependencies`. Skip Node.js built-ins and relative paths. - -**Python:** Scan `*.py` for `import {pkg}` / `from {pkg} import`, cross-reference against `requirements.txt` / `pyproject.toml`. Skip stdlib. - -| File location | Severity | -|---|---| -| Build-time config (`next.config.*`, `webpack.config.*`, `vite.config.*`, `babel.config.*`, `postcss.config.*`, `tailwind.config.*`) | ❌ FAIL — build crashes at config load | -| Entry point / source — **package absent from manifest** | ❌ FAIL — `MODULE_NOT_FOUND` at runtime | -| Entry point / source — **version mismatch** | 🔧 Recommended Fix | -| Test files only | ⚠️ WARN | - -**Scope:** Always scan ALL config files. For `src/`/`app/`/`pages/`/`lib/`, limit to 20 source files per component. Do NOT scan `node_modules/`, `.venv/`, `dist/`, `build/`. - -**When ❌ FAIL or 🔧 Fix found:** Include in batch-then-approve: "Add `{package}` to `package.json` dependencies." - -| Outcome | Verdict | -|---------|---------| -| No issues found | ✅ PASS | -| Warnings in manifest | ⚠️ WARN | -| Obvious errors | ❌ FAIL | -| No build system detected | ⚠️ WARN | -| Only Dockerfile found | ⚠️ WARN | - -**Dependency vintage check:** ALL pinned deps 5+ years old AND ecosystem has known breaking changes (e.g., `werkzeug.contrib.*` removed, `flask.ext.*` removed, `itsdangerous<1.0` API changed) → ❌ FAIL. Grep for imports from removed modules. - -**Transitive dependency check (post-migration):** After upgrading dependencies during 🔶 Major Migration, run install **through the build-validation gate (Step 3) — the migration-intent choice does NOT authorize install; the user must answer that specific per-command consent prompt first** to catch transitive deps. Also run entry-point import to catch import-time validation errors (e.g., WTForms `Email()` requires `email-validator`). - -**SKU sizing signal:** Check `f1Viable` per [dependency-compatibility.md § SKU Sizing Signals](dependency-compatibility.md). F1 is never selected (floor is B1); a vintage ❌ FAIL requiring 🔶 Major Migration (>5 files) sets `f1Viable: false` as a signal to size **up** from B1. - -## Step 3: Build Execution (Optional — User-Confirmed) - -⛔ **Build-validation gate.** Before running ANY install/build/test command, ask via `ask_user`: "I'd like to run `{command}` to verify the build. Run it? (Yes / Skip)". A general prior consent (e.g., "fix my issues", "yes", "go ahead", "fix them") does NOT constitute consent — the user must answer THIS specific question with the exact command named. If they say Skip, continue with static-only verdicts. - -Read manifest to determine actual build script. Install deps first. Capture output. Timeout 5 min. - -| Outcome | Verdict | -|---------|---------| -| Exit code 0 | ✅ PASS | -| Succeeds with warnings | ⚠️ WARN | -| Exit code ≠ 0 | ❌ FAIL | -| Timeout >5 min | ⚠️ WARN | - -## Native Module Detection - -See [dependency-compatibility.md § Native Module Detection](dependency-compatibility.md) for the canonical detection procedure, edge cases, and package table. Results go to `buildRequirements.hasNativeModules`. - -> ⛔ **Prebuild-install exception:** Packages with `prebuild-install` (without `node-gyp`) in lockfile use prebuilt binaries → `hasNativeModules: false`. Key examples: `better-sqlite3` v12+ = prebuilt, `sharp` v0.33+ = prebuilt, `bcrypt` = always native, `canvas` = always native. Check the lockfile — do not rely on package name alone. diff --git a/.github/agents/azure-deploy/prereq/references/cloud-sdk-migration.md b/.github/agents/azure-deploy/prereq/references/cloud-sdk-migration.md deleted file mode 100644 index 53fc262..0000000 --- a/.github/agents/azure-deploy/prereq/references/cloud-sdk-migration.md +++ /dev/null @@ -1,32 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Non-Azure Cloud Service Dependencies - -> Detected during Step 2 manifest scan. The `ask_user` redirect gate is handled in instructions.md Step 2 — this file defines the classification rules. - -Functional cloud SDK deps are 🔶 `CLOUD_SDK_MIGRATION`. Populate `prereq-output.json.cloudSdkFindings[]`. - -| Found Dependency | Azure Equivalent | -|-----------------|-----------------| -| AWS DynamoDB (`AWSSDK.DynamoDBv2`, `@aws-sdk/client-dynamodb`), GCP Firestore | Cosmos DB (NoSQL API) | -| AWS Cognito (`AWSSDK.CognitoIdentityProvider`, `amazon-cognito-identity-js`), Firebase auth (`firebase-admin`) | Entra ID / Entra External ID | -| AWS S3, GCP Cloud Storage (`@google-cloud/storage`), MinIO | Azure Blob Storage | -| AWS Lambda, GCP Cloud Functions (handler signatures) | Azure Functions (rewrite handlers) | -| AWS SQS (`@aws-sdk/client-sqs`, `AWSSDK.SQS`), GCP Cloud Tasks (`google-cloud-tasks`) | Queue Storage / Service Bus | -| AWS SNS (`@aws-sdk/client-sns`, `AWSSDK.SimpleNotificationService`), GCP Pub/Sub (`google-cloud-pubsub`) | Service Bus / Event Grid | -| Firebase (`firebase`, `firebase-admin`) full stack | Entra ID + Cosmos DB + Functions | - -> **Observability carve-out:** Observability deps are ⚠️ WARN (app runs without them), NOT 🔶. Use this table to distinguish: -> -> | Package | Classification | Why | -> |---------|---------------|-----| -> | `@google-cloud/opentelemetry-*` | ⚠️ WARN | Telemetry — app works without it | -> | `@google-cloud/logging` | ⚠️ WARN | Logging — app works without it | -> | `@google-cloud/monitoring` | ⚠️ WARN | Monitoring — app works without it | -> | `aws-xray-sdk`, `aws-rum-web` | ⚠️ WARN | Tracing/RUM — app works without it | -> | `google-cloud-tasks` | 🔶 CLOUD_SDK_MIGRATION | Functional — app breaks without it | -> | `google-cloud-pubsub` | 🔶 CLOUD_SDK_MIGRATION | Functional — app breaks without it | -> | `google-cloud-storage` | 🔶 CLOUD_SDK_MIGRATION | Functional — app breaks without it | -> | `@aws-sdk/client-dynamodb`, `boto3` (DynamoDB) | 🔶 CLOUD_SDK_MIGRATION | Functional — app breaks without it | -> | `@aws-sdk/client-sqs`, `@aws-sdk/client-sns` | 🔶 CLOUD_SDK_MIGRATION | Functional — app breaks without it | diff --git a/.github/agents/azure-deploy/prereq/references/completeness-check.md b/.github/agents/azure-deploy/prereq/references/completeness-check.md deleted file mode 100644 index 5eb7267..0000000 --- a/.github/agents/azure-deploy/prereq/references/completeness-check.md +++ /dev/null @@ -1,99 +0,0 @@ - -> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required. - -# Completeness Check - -> ⛔ **No build/install/test commands during this check.** Use static analysis only. - -Verify repository has required components for a deployable app. - -### 1. Entry Point - -Verify main/index file exists for each component. - -| Stack | Expected Entry Point | -|-------|---------------------| -| Node.js | `main` or `start` script in `package.json` | -| Python | `app.py`, `main.py`, `manage.py`, or entry in `pyproject.toml` | -| .NET | `Program.cs` or `Startup.cs` with `Exe` | -| Java | Class with `public static void main` or `@SpringBootApplication` | -| Go | `main.go` in `package main` | -| Static | `index.html` at root or in output folder | - -| Outcome | Verdict | -|---------|---------| -| Entry point found and file exists on disk | ✅ PASS | -| Ambiguous (multiple candidates) | ⚠️ WARN | -| No entry point | ❌ FAIL | -| Entry point declared but file missing | ❌ FAIL — `MODULE_NOT_FOUND` | - -### 2. Dependency Manifest - -| Outcome | Verdict | -|---------|---------| -| Manifest found with dependencies | ✅ PASS | -| Manifest exists but empty deps | ⚠️ WARN | -| No manifest found | ❌ FAIL (unless static site) | - -> ⛔ **Oryx reads manifests ONLY at repo root.** Subdirectory manifests → ⚠️ WARN (🔧 Fix): create root wrapper (Python: `-r {subdir}/requirements.txt`, Node: workspaces). Add to batch-then-approve. - -### 3. Configuration - -| Outcome | Verdict | -|---------|---------| -| Config properly externalized | ✅ PASS | -| Hardcoded values but no secrets | ⚠️ WARN | -| Hardcoded secrets in source (no env var fallback) | ❌ FAIL | -| Env var + hardcoded fallback default | ⚠️ WARN | -| `.env` with placeholder values + runtime validation | ✅ PASS | - -> **Compose file credentials:** Literal `*PASSWORD=` with NO `${VAR}` → ❌ FAIL. `${VAR:-default}` → ⚠️ WARN. `${VAR}` only → ✅ PASS. ⛔ Do NOT downgrade based on filename ("dev-only") — treat every compose file as potentially production-bound. - -### 4. Documentation - -README with build/run instructions → ✅ PASS. Sparse/no README → ⚠️ WARN. - -### 5. Listening Port - -Web apps must bind a port. Detect via `app.listen`, `PORT` env var, framework port config, `WebApplication.CreateBuilder()` (implicit 5000/5001 .NET 6+). - -| Outcome | Verdict | -|---------|---------| -| Port binding detected | ✅ PASS | -| Non-web (CLI, worker, function) | ✅ PASS — N/A | -| Web app with no port binding | ❌ FAIL | - -### 6. Static Asset Integrity - -Parse `href`/`src` from HTML tags. Check relative to HTML file directory. Ignore external URLs. - -| Outcome | Verdict | -|---------|---------| -| All referenced assets found | ✅ PASS | -| Broken favicon only | ⚠️ WARN | -| Broken ``, `