──────
Build and Push Docker Images / detect-changes (push) Successful in 11s
Build and Push Docker Images / build-auth-api (push) Successful in 1m0s
Build and Push Docker Images / build-employee-api (push) Successful in 1m2s
Build and Push Docker Images / build-transit-api (push) Successful in 1m1s
Build and Push Docker Images / build-error-log-api (push) Successful in 1m5s
Build and Push Docker Images / build-admin-ui (push) Successful in 1m13s
Build and Push Docker Images / build-monitoring-ui (push) Successful in 1m15s
Build and Push Docker Images / deploy-test-node (push) Successful in 3s
Build and Push Docker Images / detect-changes (push) Successful in 11s
Build and Push Docker Images / build-auth-api (push) Successful in 1m0s
Build and Push Docker Images / build-employee-api (push) Successful in 1m2s
Build and Push Docker Images / build-transit-api (push) Successful in 1m1s
Build and Push Docker Images / build-error-log-api (push) Successful in 1m5s
Build and Push Docker Images / build-admin-ui (push) Successful in 1m13s
Build and Push Docker Images / build-monitoring-ui (push) Successful in 1m15s
Build and Push Docker Images / deploy-test-node (push) Successful in 3s
### Summary of Changes
1. Documentation Consolidation:
• Relocated the docs/ folder and all repository .md files (README.md, DEPLOYMENT.md, DOCKER_README.md,
DOMAINS_README.md, GETTING_STARTED.md, CI_CD_PORTAINER_GUIDE.md, ADMIN_DEPLOYMENT_SUMMARY.md,
SECURITY_PRIVATE_FILES.md, handoff.md, azure-project-plan.md, plus service-specific READMEs) into .
• Updated document links in README.md.
2. AI Agent Project Map:
• Created PROJECT_MAP.md in the project root containing complete architecture diagrams, service specs, ports, DB
schemas, environment variables, and developer workflows.
• Added PROJECT_MAP.md to .gitignore.
3. Monitoring UI & Infrastructure Integration:
• Added monitoring_ui service to docker-compose.yml (port 5175).
• Configured Gitea workflow docker-build.yml for monitoring_ui builds.
• Added CORS middleware headers in index.js.
• Added nginx.conf copy step in Dockerfile.
──────
### Recommended Commit Message
docs: consolidate project documentation into monitoring_ui and add agent project map
- Move all markdown documentation files and docs/ folder into monitoring_ui/docs/
- Update documentation hub index (monitoring_ui/docs/README.md) with relative paths
- Generate comprehensive AI agent reference map (PROJECT_MAP.md) in repository root
- Add PROJECT_MAP.md and monitoring_ui build output/env overrides to .gitignore
- Integrate monitoring_ui service into docker-compose.yml and Gitea CI workflow
- Configure CORS handling in employee_api for monitoring dashboard access
──────
### Alternative Short Commit Message
refactor(docs): move docs to monitoring_ui and create AI agent PROJECT_MAP.md
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **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 Gates — Steps 6 & 8
|
||||
|
||||
> **Gate summary:** AppOnboard has **2 approval gates**: (1) **Scaffold Gate** (orchestrator Step 6) — approve architecture plan before generating IaC, (2) **Deploy Gate** (orchestrator Step 8 / deploy/instructions.md Step 4) — approve cost + resource summary before `az deployment`. Both are mandatory and SEPARATE — scaffold approval does NOT imply deploy approval.
|
||||
|
||||
## Scaffold Approval Gate (Step 6)
|
||||
|
||||
Display the architecture plan for user approval BEFORE generating any files:
|
||||
|
||||
> ⛔ **Open the plan view FIRST.** Call `open_deploy_plan_view` before displaying the gate text — it renders the `prepare-plan.json` you just wrote (services, SKUs, region, cost) in a side-by-side view. Call it once per gate; it is a display action only and never replaces the chat approval prompt below.
|
||||
|
||||
> ⛔ **Record the CLI prerequisites through the MCP tool — not in chat.** The plan view has a prerequisites section for the two CLIs this stage depends on, and it is populated **only** by `record_deploy_prerequisites`. Before (or right alongside) `open_deploy_plan_view`:
|
||||
>
|
||||
> 1. Probe each CLI with its version command in the user's own default shell — **azd** → `azd version`, **az** → `az version`.
|
||||
> 2. Call `record_deploy_prerequisites` with one entry per tool: `installed: true` when the command returned a version, otherwise `installed: false`. Include the detected `version` when you have it.
|
||||
> 3. Do **not** pass install links or display names — the view resolves those from its own catalog.
|
||||
>
|
||||
> Example: `record_deploy_prerequisites({ tools: [{ id: "azd", installed: true, version: "1.9.2" }, { id: "az", installed: false }] })`
|
||||
>
|
||||
> Checking the CLIs by hand and describing the result in chat does **not** satisfy this — the user's plan view stays empty. Never write these into `prepare-plan.json`; that is the vendored pipeline's artifact. If the tool is not directly listed, load it first per "Azure Resources MCP Tools" in [`azure-deploy.agent.md`](../../azure-deploy.agent.md) — do **not** conclude it is unavailable.
|
||||
|
||||
> ⛔ **Resource group edit is MANDATORY in the gate display.** Show this exact block:
|
||||
> ```
|
||||
> 🏢 **Subscription:** {subscriptionName} (`{subscriptionId}`)
|
||||
> 📦 **Resource Group:** {rg-name} ({region})
|
||||
> Want a different name or region? Say "Edit plan".
|
||||
> ```
|
||||
> ⛔ **Gate MUST show Subscription (name + ID), Resource Group, and Region** as standalone lines above the service table — see [pipeline-rules.md § Approval gates](pipeline-rules.md). Do NOT omit or bury in a table.
|
||||
|
||||
Also display: services + SKUs + region + resource names + monthly cost estimate + files to generate. **Check `context.json.overrides[]` for `iacFormat`** — if Terraform, display "Terraform templates (`infra/*.tf`)"; if Bicep (default), display "Bicep templates (`infra/main.bicep`)". Show resource names so the user sees what will be created.
|
||||
|
||||
> ⛔ **Surface plan assumptions.** If `prepare-plan.json.assumptions[]` is present (e.g., the B1-floor SKU was unavailable and a higher tier was selected), display each note prefixed with ⚠️ ABOVE the approval prompt — the user MUST see WHY the cost or SKU differs from the default. Do not bury or omit them.
|
||||
|
||||
Verify file list against target SKU. ⛔ **Exclude `azure.yaml` from file list** (see pipeline-rules.md).
|
||||
|
||||
> ⛔ **Container Apps code deploy preview (when plan includes Container Apps).** After the service table, preview the deploy path: build via ACR, replace placeholder images, redeploy. If `buildRequirements.hasBuildKitSyntax == true`, note ACR-compatible versions will be created.
|
||||
|
||||
> ⛔ **Data-loss warnings at the gate.** If `prereq-output.json.warnings[]` contains any data-loss findings (SQLite on App Service, in-memory sessions, local file storage), surface them prominently with ⛔ formatting ABOVE the approval prompt. The user must see these before approving — do not bury them in a table or omit them.
|
||||
|
||||
> ⛔ **Database network access (when plan includes PostgreSQL/MySQL).** Show a **Database access** line above the approval prompt: the default `AllowAllAzureServicesAndResourcesWithinAzureIps` (`0.0.0.0`) lets the app connect but opens the server to **all** Azure services. ⛔ **Render the exposure as its own bold sentence so a fast "Yes" is still an informed "Yes":** **"Proceeding opens the database to all Azure services — not just this app."** Offer the alternative: *"For private networking (VNet integration + private endpoint), say **'Private access'** — AppOnboard does not build private networking itself, so it will hand off to `azure-enterprise-infra-planner`, which then owns the secure networking design **and** its deployment. AppOnboard stops here — it does not resume afterward."* If the user proceeds (Yes), that counts as consent to the `0.0.0.0` rule.
|
||||
|
||||
> ⛔ **Private networking redirect.** If the user chooses **Private access** (here or during Edit plan): set `context.json.routeToSkill: "azure-enterprise-infra-planner"` and `routeReason: "private-networking-requested"`, then **HALT** — do NOT generate IaC. Tell the user: *"AppOnboard can't generate private networking (VNet + private endpoint). Handing off to azure-enterprise-infra-planner, which will design the secure topology and deploy it from here — it takes over the rest of the onboarding. Your AppOnboard session is saved for reference."* Then hand off to the `azure-enterprise-infra-planner` agent. This mirrors the prereq `routeToSkill` halt (instructions.md Step 3) but fires at the Scaffold Gate.
|
||||
|
||||
> ⛔ **Pick the prompt variant FIRST (based on whether the plan has a database), then use it verbatim — do NOT paraphrase or reword:**
|
||||
> - **Plan includes PostgreSQL/MySQL** → **"✅ Ready to proceed with scaffolding? (Yes / Edit plan / Private access / Cancel)"** — the `ask_user` choices MUST be exactly: `Yes`, `Edit plan`, `Private access`, `Cancel`.
|
||||
> - **No database in plan** → **"✅ Ready to proceed with scaffolding? (Yes / Edit plan / Cancel)"** — the `ask_user` choices MUST be exactly: `Yes`, `Edit plan`, `Cancel`.
|
||||
>
|
||||
> The `ask_user` choices MUST match the prompt text exactly — never name an option in one and omit it from the other (e.g. `Private access` must be a selectable choice, not something the user has to type).
|
||||
|
||||
> ⛔ **RESPONSE BOUNDARY — MANDATORY.** The approval gate MUST be the LAST content in your response. Do NOT generate any files, write any IaC, create any Dockerfiles, or execute any commands in the same response as the gate. Your next action MUST be reading the user's reply. If the user has not yet responded, WAIT — do not proceed.
|
||||
|
||||
Three options:
|
||||
- **Yes** → proceed to Step 7 (scaffold only — NOT deploy)
|
||||
- **Edit plan** → ask what to change → write to `context.json.overrides[]` → re-run Step 5 → show updated gate
|
||||
- **Cancel** → preserve session artifacts for later resumption, stop
|
||||
|
||||
## Deploy Approval Gate (Step 8)
|
||||
|
||||
This is a SEPARATE gate from Step 6.
|
||||
|
||||
> ⛔ **Context refresh:** Before presenting this gate, re-read this instructions.md Steps 8-9 if you have not read them in the last 5 turns. Scaffold reference loading (Steps 6-7) consumes significant context — deploy rules may have been evicted.
|
||||
|
||||
Display:
|
||||
- Validation status from `scaffold-manifest.json.validationResult`
|
||||
- Self-review summary (count of VERIFIED/PLAUSIBLE/FLAGGED findings)
|
||||
- Resource group name + region
|
||||
- Services + SKUs + estimated cost
|
||||
- End with **"🚀 Ready to deploy? (Yes / Run manually / Edit plan / Cancel)"**
|
||||
|
||||
> ⛔ **After deploy approval:** Your NEXT action MUST be: read `deploy/instructions.md`, then read `.copilot-azure/sessions/{id}/deploy-checklist.md`. Do NOT hand off to any other deploy workflow — this agent uses its own embedded deploy phase.
|
||||
|
||||
If "Run manually" is selected → point to [deploy-checklist-template.md § Deployment Summary](../deploy/references/deploy-checklist-template.md) for manual execution steps.
|
||||
|
||||
If validation failed or any FLAGGED findings exist at L1 (Security) or L3 (Hallucination), block **Yes** until resolved.
|
||||
|
||||
Only after user approves: proceed to deploy phase (Step 9). `context.json` already has `currentPhase: "deploy"` from the post-scaffold checkpoint (main instructions.md Step 7).
|
||||
|
||||
> ⛔ **Before entering deploy:** ⛔ Read [`deploy/instructions.md`](../deploy/instructions.md) before any deployment action. After mid-session compaction, re-read `deploy/instructions.md` Steps 4-8. You MUST write `deploy-result.json`.
|
||||
|
||||
> ⛔ Deploy via `az deployment sub create` (see [pipeline-rules.md](pipeline-rules.md)). AppOnboard-generated `azure.yaml` found → never delete/overwrite; move to `.copilot-azure/sessions/<id>/replaced-files/` (mirror path).
|
||||
|
||||
> ⛔ **Container Apps code deploy is NOT optional.** After IaC placeholder deploys, complete: `az acr build` → update image params → redeploy → health check. Do NOT present manual CLI "Next Steps" for core deploy tasks. If `hasBuildKitSyntax`, create `Dockerfile.azure` first.
|
||||
|
||||
> ⛔ **Phase exit:** `deploy-result.json` MUST be written before proceeding to Step 9. See deploy/instructions.md for the full exit gate checklist.
|
||||
@@ -0,0 +1,87 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **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.
|
||||
|
||||
# azd Template Routing
|
||||
|
||||
When prereq detects an existing azd template, AppOnboard routes to `azure-prepare` instead of continuing the greenfield pipeline. AppOnboard is a greenfield deployment agent — repos with existing Azure IaC belong to the prepare → validate → deploy pipeline.
|
||||
|
||||
## Detection
|
||||
|
||||
Before the scope triage question (Step 2), do a quick file-system check (workspace root + `infra/` only — never scan `.copilot-azure/`). Route if ALL of these are true:
|
||||
|
||||
| Condition | Where to check |
|
||||
|-----------|----------------|
|
||||
| `azure.yaml` exists in workspace root | File system scan |
|
||||
| `*.bicep` or `*.tf` files exist in `infra/` | File system scan |
|
||||
| `azure.yaml` has `services:` with at least 1 entry | Read `azure.yaml` in workspace root |
|
||||
|
||||
If only `azure.yaml` is present without IaC files (partial azd setup), continue AppOnboard pipeline — the repo needs IaC generated.
|
||||
|
||||
## Gate — presented as the scope triage question
|
||||
|
||||
When an azd template is detected, the scope triage question is replaced with an azd-aware version (see [intent-gathering.md § Scope triage](intent-gathering.md)). Present:
|
||||
|
||||
```
|
||||
📦 **Existing Azure deployment setup detected**
|
||||
|
||||
Your repo already has:
|
||||
- `azure.yaml` — Azure Developer CLI configuration
|
||||
- `infra/` — {Bicep|Terraform} infrastructure templates
|
||||
{list any other detected infra: Dockerfiles, CI/CD workflows}
|
||||
|
||||
This is a complete azd template — it already defines how to build and deploy your app.
|
||||
|
||||
**How would you like to proceed?**
|
||||
|
||||
1. **Deploy with existing setup** — I'll hand off to `azure-prepare`, which works with azd templates natively. It will analyze your IaC, plan the deployment, and walk you through `azd up`.
|
||||
|
||||
2. **Start fresh** — Ignore the existing IaC and build new infrastructure from scratch using AppOnboard's greenfield pipeline. Your existing files won't be modified.
|
||||
|
||||
3. **Just scan for readiness** — Keep the prereq results (your app is {ready|needs fixes}) and stop here.
|
||||
```
|
||||
|
||||
## Routing protocol
|
||||
|
||||
**Option 1 — Deploy with existing setup:**
|
||||
|
||||
1. Write routing state to `context.json`:
|
||||
```json
|
||||
{
|
||||
"routeToSkill": "azure-prepare",
|
||||
"routeReason": "existing-azd-template",
|
||||
"completedPhases": [],
|
||||
"currentPhase": null,
|
||||
"statusSummary": "Routed to azure-prepare — existing azd template detected, prereq scan skipped (not needed for existing IaC)"
|
||||
}
|
||||
```
|
||||
2. Tell the user:
|
||||
```
|
||||
✅ Your app is healthy — prereq scan found no blockers.
|
||||
|
||||
Since your repo has a complete azd setup, I'm handing off to **azure-prepare** — it's purpose-built
|
||||
for repos with existing IaC and works natively with `azd up`.
|
||||
```
|
||||
3. **Invoke azure-prepare directly:** Hand off to the `azure-prepare` agent, then follow its workflow using the user's original prompt from `context.json.intent.userPrompt`. This is the same pattern as prereq invocation in Step 3 — AppOnboard hands off and the agent follows its instructions. Do NOT ask the user to type a command.
|
||||
4. **STOP the AppOnboard pipeline.** Do NOT continue to Step 5 (plan architecture). Do NOT generate IaC. Do NOT run `azd up` yourself. azure-prepare owns the rest of the conversation.
|
||||
|
||||
**Option 2 — Start fresh:**
|
||||
|
||||
1. Write override to `context.json.overrides[]`: `{ "key": "ignoreExistingInfra", "value": "true", "reason": "User chose greenfield over existing azd template" }`
|
||||
2. If `infra/` directory exists, rename it to `infra.bak/` (single folder rename). This preserves the user's existing IaC as a backup before scaffold writes new files.
|
||||
3. Continue AppOnboard pipeline from Step 5 (plan architecture).
|
||||
4. Scaffold Step 3 is skipped (override exists) — the backup was already done here.
|
||||
|
||||
**Option 3 — Just scan:**
|
||||
|
||||
1. Update `context.json.statusSummary` to reflect the scan-only outcome.
|
||||
2. Present prereq results summary. STOP.
|
||||
|
||||
## Edge cases
|
||||
|
||||
| Scenario | Handling |
|
||||
|----------|----------|
|
||||
| `azure.yaml` exists but `infra/` is empty | NOT an azd template — continue AppOnboard (repo needs IaC) |
|
||||
| `azure.yaml` exists with `infra.provider: terraform` | Route same as Bicep — azure-prepare handles both |
|
||||
| User chose "Start fresh" then hits scaffold guard | Scaffold guard bypassed via `ignoreExistingInfra` override |
|
||||
| Prereq found blockers AND repo has azure.yaml | Present blockers first (prereq triage), then present azd gate. Blockers take priority. |
|
||||
| `azure.yaml`/`infra/` looks like it might be an AppOnboard leftover | It's ours only if **any** `.copilot-azure/sessions/*/scaffold-manifest.json` `files[]` lists it (checking every session, not just the active one, catches leftovers from a prior abandoned run); otherwise treat as the user's → route → STOP. ⛔ Never decide by git commit status; never delete/overwrite — move to `.copilot-azure/sessions/<id>/replaced-files/` (mirror path). |
|
||||
@@ -0,0 +1,140 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **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.
|
||||
|
||||
# Handoff Protocol — Step 9
|
||||
|
||||
Offer next steps: CI/CD setup, monitoring, domain config, **🗑️ resource cleanup**, agent-based suggestions. Session artifacts remain for deferred pickup.
|
||||
|
||||
> ⛔ **Handoff MUST include ALL FOUR sections: (1) Deployment Identity, (2) Cleanup Commands, (3) Redeploy Command, (4) Post-Deploy Recommendations.** Missing any section = incomplete handoff. Do NOT skip cleanup even if deployment failed. Do NOT skip identity even if no resources were created. Do NOT skip recommendations even if the list is empty (print "No post-deploy recommendations.").
|
||||
|
||||
## Deployment Identity
|
||||
|
||||
> ⛔ **Start handoff with deployment identity.** First lines of the handoff response MUST be:
|
||||
> ```
|
||||
> 🏢 Subscription: {context.json.azure.subscriptionName} ({context.json.azure.subscriptionId})
|
||||
> 📁 Resource Group: {context.json.azure.resourceGroup}
|
||||
> 🌍 Region: {context.json.azure.region}
|
||||
> 🔗 Portal: https://portal.azure.com/#@/resource/subscriptions/{subId}/resourceGroups/{rgName}/overview
|
||||
> ```
|
||||
> Source: `context.json.azure`. This is the user's quickest path to finding their resources after the session ends.
|
||||
|
||||
See [deploy-checklist-template.md § Deployment Summary](../deploy/references/deploy-checklist-template.md) for the full deployment summary format.
|
||||
|
||||
## Artifact Self-Check
|
||||
|
||||
> ⛔ **Artifact self-check — MANDATORY before handoff.** Verify these exist before presenting cleanup or next steps:
|
||||
> 1. `deploy-result.json` in session folder — if missing, read [`deploy-schemas.ts`](../deploy/references/deploy-schemas.ts) and write it NOW with status, endpoints, health, `createdResources[]`, `orphanedResourceGroups[]`
|
||||
> 2. `deploy-result.json.createdResources[]` populated — the deterministic before/after `resources.list()` diff from `capture_deployment_inventory`. If empty/missing, run `capture_deployment_inventory` (`phase: "capture"`) NOW and write its output into `deploy-result.json` so the cleanup section below is derived from data, not memory.
|
||||
> 3. Portal deployment link printed in chat — if missing, generate from `$resId` pattern (see deploy/instructions.md Step 6) and print now
|
||||
> 4. `deployment-summary.md` in session folder — if missing, `create` it NOW with the same content you are about to present in chat (status, subscription, RG, region, services table, endpoints, cleanup commands). One `create` call — do NOT skip.
|
||||
|
||||
## Show the Deployment Results View
|
||||
|
||||
> ⛔ **After the artifact self-check passes, call `open_deploy_result_view` — exactly once, before presenting the chat handoff.** This opens the Deployment Results webview, which renders `deploy-result.json` as a structured report: status, endpoints with health, provisioned resources, recovery attempts, and the cleanup command.
|
||||
>
|
||||
> - Call it only after `deploy-result.json` is **finalized** (`status` is `succeeded` or `failed`, never `in-progress`). The view reads the file from disk, so calling it early shows a half-written report.
|
||||
> - Call it on failure too — the view surfaces which resources and endpoints failed, which is exactly what the user needs to debug.
|
||||
> - The view **supplements** the chat handoff; it does not replace it. Still present all four handoff sections below.
|
||||
> - If the tool reports that no result file was found, the artifact was written somewhere unexpected — re-check the session folder path and fix the artifact, then call the tool again.
|
||||
|
||||
## Show the Deployment Results View
|
||||
|
||||
> ⛔ **After the artifact self-check passes, call `open_deploy_result_view` — exactly once, before presenting the chat handoff.** This opens the Deployment Results webview, which renders `deploy-result.json` as a structured report: status, endpoints with health, provisioned resources, recovery attempts, and the cleanup command.
|
||||
>
|
||||
> - Call it only after `deploy-result.json` is **finalized** (`status` is `succeeded` or `failed`, never `in-progress`). The view reads the file from disk, so calling it early shows a half-written report.
|
||||
> - Call it on failure too — the view surfaces which resources and endpoints failed, which is exactly what the user needs to debug.
|
||||
> - The view **supplements** the chat handoff; it does not replace it. Still present all four handoff sections below.
|
||||
> - If the tool reports that no result file was found, the artifact was written somewhere unexpected — re-check the session folder path and fix the artifact, then call the tool again.
|
||||
|
||||
## Post-Deploy Recommendations
|
||||
|
||||
> ⛔ **`postDeployRecommendations[]` MUST be surfaced — not silently dropped.** Read `prepare-plan.json.postDeployRecommendations[]` (already merged with prereq findings by the prepare phase). Present EACH entry as a numbered recommendation with this format:
|
||||
> ```
|
||||
> 📋 Post-deploy recommendations:
|
||||
> 1. **{title}** ({effort} effort) — {reason}. Services: {services[]}
|
||||
> 2. ...
|
||||
> ```
|
||||
> This section MUST appear BEFORE the agent-based suggestions and AFTER the cleanup commands. If `postDeployRecommendations[]` is empty, print "No post-deploy recommendations." Do NOT skip this section — findings buried in JSON artifacts are easily dropped from the handoff if not explicitly surfaced here.
|
||||
|
||||
## Cleanup Commands
|
||||
|
||||
> ⛔ **Cleanup commands are MANDATORY — always print BOTH.** Every handoff must include these two cleanup blocks, regardless of whether healing occurred or orphans exist.
|
||||
|
||||
**Primary cleanup (always print):**
|
||||
```
|
||||
🗑️ Delete this deployment's resources:
|
||||
→ az group delete -n {rg} --yes --no-wait
|
||||
```
|
||||
|
||||
**Tag-based bulk cleanup (always print — catches orphans from healing):**
|
||||
```
|
||||
🏷️ Delete ALL resources from this AppOnboard session:
|
||||
→ az group list --tag app-onboard-session-id={sessionId} --query "[].name" -o tsv | ForEach-Object { az group delete -n $_ --yes --no-wait }
|
||||
```
|
||||
This catches orphaned RGs from region fallback or naming conflict healing. For Terraform: `cd infra && terraform destroy`.
|
||||
|
||||
**Deterministic orphan cleanup (from `deploy-result.json.createdResources[]`):**
|
||||
|
||||
> ⛔ **Prefer the inventory over memory.** `deploy-result.json.createdResources[]` and `orphanedResourceGroups[]` are computed by `capture_deployment_inventory` from a before/after `resources.list()` diff, so they catch orphans the tag filter misses (imperative `az create` fallbacks and resources that never received the session tag). If they are empty/missing, run `capture_deployment_inventory` (`phase: "capture"`) and write its output into `deploy-result.json` before writing this section.
|
||||
|
||||
**If `deploy-result.json.orphanedResourceGroups[]` is non-empty**, list each explicitly with delete commands. For orphans with a `subscription` field, include `--subscription {subscription}`.
|
||||
|
||||
⛔ **Do not present unattributed resources as safe to delete.** The two classifications carry different confidence and MUST be printed as two separate lists:
|
||||
|
||||
- **`classification: "failed"`** — confirmed to belong to this deployment. Print the exact `az resource delete --ids {id}` command under a heading like `🗑️ Failed resources from this deployment (safe to delete):`.
|
||||
- **`classification: "orphaned"`** — appeared during the deploy window but no tracked deployment reported it. It may be a healing stray, or it may belong to someone else on a shared subscription. Print it under `⚠️ Resources that appeared during this deploy but could not be matched to it — review before deleting:` with the resource ID and portal link, and **without** a ready-to-run delete command.
|
||||
|
||||
⛔ **If `deploy-result.json.inventoryUnverified` is `true`**, the deployment's ARM operations could not be read and nothing could be attributed. Print neither list. Instead say the created-resource inventory could not be verified (include `inventoryUnverifiedReason`; for `"forbidden"` explain the account lacks `Microsoft.Resources/deployments/operations/read`) and point the user at the resource group in the portal. Still print the primary and tag-based cleanup blocks above.
|
||||
|
||||
## Redeploy Command
|
||||
|
||||
> ⛔ **Redeploy command is MANDATORY.** The full AppOnboard pipeline (prereq → prepare → scaffold → deploy) is a one-time setup. After that, the user only needs to rebuild and push code. Without this command, they'd have to reverse-engineer the deploy steps from session artifacts or re-run the entire pipeline. Give them the shortcut.
|
||||
|
||||
**Derive from what you ran in deploy Step 6b (code deploy).** Do NOT hardcode per service type — echo back the actual command(s) you executed to deploy code. The command varies by compute type, registry name, Dockerfile path, image tag, app name, etc. — all of which you already know from this session.
|
||||
|
||||
```
|
||||
🔄 Redeploy (after code changes):
|
||||
→ {code deploy command(s) from Step 6b}
|
||||
```
|
||||
|
||||
Include this in the chat handoff AND in `deployment-summary.md`.
|
||||
|
||||
**If `deploy-result.json.healingAttempts[]` is non-empty**, surface: "⚕️ Deployment required {N} healing attempts" with per-attempt error/action/outcome and planLevelChange details.
|
||||
|
||||
**Cross-session cleanup (optional — show if user asks):**
|
||||
```
|
||||
🔍 Find ALL AppOnboard resources across all sessions:
|
||||
→ az group list --tag app-onboard-skill=true -o table
|
||||
```
|
||||
|
||||
## Agent-Based Next Steps
|
||||
|
||||
> ⛔ **Agent-based next steps are MANDATORY.** Always suggest at minimum `azure-compliance` and `azure-resource-visualizer`. Evaluate every condition below.
|
||||
|
||||
> ⛔ **Suggest, don't self-execute.** Present these as optional suggestions. The deploy agent must NEVER perform post-deploy infra changes itself — zone redundancy, private endpoints, SKU upgrades, and resource re-creation are imperative mutations the deploy phase must not make. If the user explicitly opts into one, route to that agent as a new, scoped task; otherwise the pipeline ends at handoff.
|
||||
|
||||
| Condition | Suggest |
|
||||
|-----------|--------|
|
||||
| Always | **`azure-compliance`** — "Run a compliance scan on your deployed resources" |
|
||||
| Always | **`azure-resource-visualizer`** — "Generate an architecture diagram of your resource group" |
|
||||
| Reliability/HA gaps (single-zone, no failover, prod workload) or user asks to "harden" / "make production-ready" | **`azure-reliability`** — "Assess & improve reliability: zone redundancy, multi-region failover, health probes" |
|
||||
| Cost-sensitive workload or user asks about spend | **`azure-cost`** — "Review and optimize your Azure spend" |
|
||||
| Health check failed / `healthStatus: "degraded"/"unreachable"` | **`azure-diagnostics`** — "Troubleshoot your deployment" |
|
||||
| Auth/OAuth/MSAL detected in intent or prereq | **`entra-app-registration`** — "Set up app registration for your auth flow" |
|
||||
| `postDeployRecommendations[]` has upgrade suggestions | **`azure-upgrade`** — "Upgrade your runtime or framework" |
|
||||
| Storage-heavy patterns | **`azure-storage`** — "Optimize your storage configuration" |
|
||||
| `postDeployRecommendations[]` mentions RBAC/role | **`azure-rbac`** — "Configure least-privilege role assignments" |
|
||||
|
||||
## Completion
|
||||
|
||||
> ⛔ **End the handoff with an explicit completion line — the LAST thing you emit.** It marks the pipeline done (so you stop working) and tells the user the core task succeeded and anything more is their choice. Frame it as closure WITH an open door — never a hard stop.
|
||||
>
|
||||
> ⛔ **Use this EXACT sentence verbatim — do NOT paraphrase, reword, or reorder it:** "The onboarding pipeline is finished." Only `{primary endpoint URL}` varies. This exact phrase is a required completion marker.
|
||||
|
||||
```
|
||||
✅ **Deployment complete — your app is live at {primary endpoint URL}.** The onboarding pipeline is
|
||||
finished. The next steps above are optional — tell me which you'd like and I'll route you to the
|
||||
right agent.
|
||||
```
|
||||
|
||||
> ⛔ **The recommendations above are OPTIONAL — do NOT autonomously execute them.** Present them and let the user choose; if they pick one, route to the matching agent (it makes the change properly). Don't turn a general acknowledgment into a batch of unsolicited hardening. Beyond this point the session is the user's — they may continue, invoke another agent, or run their own commands; that's expected and no longer part of this pipeline.
|
||||
@@ -0,0 +1,45 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **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.
|
||||
|
||||
# IaC Resources — Official Documentation & Tools
|
||||
|
||||
Look up when stuck after 3 tries, edge cases, or validating generated code against ground truth.
|
||||
|
||||
## Bicep
|
||||
|
||||
| Resource | URL | Use When |
|
||||
|----------|-----|----------|
|
||||
| Bicep Documentation | https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/ | Syntax, file structure, deployment scopes, install |
|
||||
| Azure Resource Reference | https://learn.microsoft.com/en-us/azure/templates/ | Resource properties, API versions, schema per type |
|
||||
|
||||
## Terraform
|
||||
|
||||
| Resource | URL | Use When |
|
||||
|----------|-----|----------|
|
||||
| Terraform Registry — azurerm | https://registry.terraform.io/providers/hashicorp/azurerm/latest | Resource type properties, argument reference, import blocks |
|
||||
| Terraform Registry — azapi | https://registry.terraform.io/providers/azure/azapi/latest | Preview resources not yet in azurerm; maps to ARM REST APIs |
|
||||
|
||||
## Validation Tools
|
||||
|
||||
| Tool | Format | Purpose |
|
||||
|------|--------|---------|
|
||||
| `bicep build` | Bicep | Syntax + schema validation |
|
||||
| `az deployment group create --what-if` | Bicep | ARM-level dry run with change preview |
|
||||
| `terraform validate` | Terraform | Syntax + schema validation |
|
||||
| `terraform plan` | Terraform | Provider-level dry run |
|
||||
|
||||
## Deploy Troubleshooting
|
||||
|
||||
> ⛔ **Primary lookup path:** Call `mcp_azure_mcp_documentation` with the error message first. Use the table below as fallback when MCP is unavailable or returns no results.
|
||||
>
|
||||
> **On repeat failures (same error 2+ consecutive attempts):** `fetch_webpage` the matching URL below with the error message as query. Apply the documented fix — do not retry the same approach.
|
||||
|
||||
| Resource | URL | Use When |
|
||||
|----------|-----|----------|
|
||||
| Common ARM Deployment Errors | https://learn.microsoft.com/en-us/azure/azure-resource-manager/troubleshooting/common-deployment-errors | `InvalidTemplateDeployment`, `SkuNotAvailable`, `QuotaExceeded`, any ARM error code |
|
||||
| App Service Troubleshooting | https://learn.microsoft.com/en-us/troubleshoot/azure/app-service/ | Startup crashes, Kudu/Oryx build failures, health probe issues |
|
||||
| App Service Zip Deploy Guide | https://learn.microsoft.com/en-us/azure/app-service/deploy-zip | Zip deploy, SCM_DO_BUILD_DURING_DEPLOYMENT, Kudu publish API |
|
||||
| Container Apps Troubleshooting | https://learn.microsoft.com/en-us/azure/container-apps/troubleshooting | Revision failures, ingress errors, secret resolution, image pull failures |
|
||||
| Quota Increase Portal | https://portal.azure.com/#blade/Microsoft_Azure_Capacity/QuotaMenuBlade/myQuotas | Direct link for quota increase requests |
|
||||
|
||||
> **Source:** Official Microsoft Learn, HashiCorp Developer, and Azure documentation.
|
||||
@@ -0,0 +1,52 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **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.
|
||||
|
||||
# Intent Gathering
|
||||
|
||||
## Scope Triage (Step 2 — before prereq)
|
||||
|
||||
> ⛔ **Scope triage — BEFORE prereq.** Check for azd template markers (`azure.yaml` + IaC in `infra/`).
|
||||
>
|
||||
> **Skip triage when:**
|
||||
> - User explicitly asks for cost estimates, service recommendations, or code analysis
|
||||
> - Empty workspace (prereq’s zero-code-path handles it)
|
||||
> - Code exists but no infra files (no `.bicep`, `.tf`, `azure.yaml`, or `infra/` dir) — full pipeline is the only sensible path
|
||||
> - Intentionally vulnerable app signals (🛑 detection) — proceed to prereq directly
|
||||
>
|
||||
> ### If azd template detected
|
||||
>
|
||||
> ⛔ **You MUST read [`azd-template-routing.md`](azd-template-routing.md)** for detection criteria, gate presentation, and routing protocol.
|
||||
>
|
||||
> ### If NO azd template BUT infra files present
|
||||
>
|
||||
> Ask ONE `ask_user` question:
|
||||
> 1. **Yes — analyze and deploy end-to-end** (Recommended)
|
||||
> 2. **Just scaffold Bicep/Terraform**
|
||||
> 3. **Just deploy it** (I have IaC ready)
|
||||
> 4. **Other**
|
||||
>
|
||||
> Option 1 / vague → full pipeline (Step 3). Any other → hand off to the `azure-prepare` agent.
|
||||
|
||||
## After Prereq Returns (Step 4 — scan-informed intent gathering)
|
||||
|
||||
Prereq has written `prereq-output.json` and `context.json.components[]` — this is the authoritative source for all downstream phases (prepare and scaffold consume `context.json`, not `prereq-output.json`).
|
||||
|
||||
Present the detected component boundaries before asking questions. Include each deployable component's path,
|
||||
framework, and intended Azure compute model. For a SPA plus Azure Functions project, state both mappings
|
||||
explicitly: the frontend remains its own hosting service and the backend remains a separate Function App.
|
||||
Do not infer an SWA-managed API merely because the frontend uses `/api` routes.
|
||||
|
||||
Confirm the Azure target ("☁️ **Azure target**: {subscriptionName} ({subscriptionId})"). If the user wants a different subscription, write to `context.json.overrides[]`.
|
||||
|
||||
**Present scan results first, then ask only what prereq didn't answer** (≤2 if mostly covered, ≤4 if gaps):
|
||||
|
||||
| # | Topic | Ask if... |
|
||||
|---|-------|----------|
|
||||
| 1 | App purpose | Not obvious from `detectedStack` + `components[]` |
|
||||
| 3 | Data/storage | Prereq didn't detect DB/compose |
|
||||
| 4 | Auth approach | No MSAL/passport/auth library detected |
|
||||
| 5 | Scale | Always — prereq doesn't know traffic expectations |
|
||||
|
||||
⛔ Do NOT ask about stack/language (always answered by prereq) or budget (only if user mentioned cost). User corrections → `context.json.overrides[]`. Stop when covered or user says "just go."
|
||||
|
||||
**Update intent:** Merge scan results into `context.json.intent`. Set `refinedFromScan: true` and populate `scanDiscoveredFacts[]`.
|
||||
@@ -0,0 +1,82 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **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.
|
||||
|
||||
# MCP Tool Reference — Shared Index
|
||||
|
||||
Shared tools used across multiple AppOnboard phases, plus the cross-cutting Phase→Tool Map. For phase-specific tools with full parameter tables, see the per-phase references linked below.
|
||||
|
||||
> **Troubleshooting:** If a tool call fails with unknown parameter or missing command errors, consult the official docs for current parameter names and allowed values: <https://learn.microsoft.com/en-us/azure/developer/azure-mcp-server/tools/>
|
||||
|
||||
## Per-Phase Tool References
|
||||
|
||||
| Phase | File | Exclusive Tools |
|
||||
|-------|------|-----------------|
|
||||
| Prereq | *(shared tools only — see table below)* | *(shared only)* |
|
||||
| Prepare | [mcp-tools.md](../prepare/references/mcp-tools.md) | pricing, quota, cloudarchitect, WAF, advisor, group_resource_list, policy |
|
||||
| Scaffold | [mcp-tools.md](../scaffold/references/mcp-tools.md) | bicepschema, all mcp_bicep_*, deploy (iac_rules/pipeline/plan), terraform best practices |
|
||||
| Deploy | [mcp-tools.md](../deploy/references/mcp-tools.md) | resourcehealth, monitor, appservice, deploy (app_logs/arch_diagram), role |
|
||||
|
||||
---
|
||||
|
||||
## Global Parameters (all tools)
|
||||
|
||||
Every Azure MCP tool accepts these optional parameters in addition to its own:
|
||||
|
||||
| Parameter | Description |
|
||||
|-----------|-------------|
|
||||
| `subscription` | Azure subscription ID or display name. Defaults to `az account show` default. |
|
||||
| `tenant` | Entra ID tenant GUID or name. Uses default tenant if omitted. |
|
||||
| `resource-group` | Resource group name. Required for most resource-specific operations. |
|
||||
|
||||
> **Additional global params** (not commonly needed by AppOnboard): `authentication-method` (credential\|key\|connectionString), `max-retries` (default 3), `retry-delay` (default 2s), `retry-delay-maximum` (default 10s), `retry-mode` (fixed\|exponential), `retry-network-timeout` (default 100s). See [official docs](https://learn.microsoft.com/en-us/azure/developer/azure-mcp-server/tools/) for details.
|
||||
|
||||
---
|
||||
|
||||
## Shared Tools (used by 2+ phases)
|
||||
|
||||
### `mcp_azure_mcp_subscription_list`
|
||||
|
||||
| Required | Optional | Read-Only |
|
||||
|----------|----------|-----------|
|
||||
| *(none)* | `tenant` | ✅ |
|
||||
|
||||
Returns: `subscriptionId`, `displayName`, `state`, `tenantId`, `isDefault`. Use `isDefault: true` as default subscription.
|
||||
|
||||
Used by: **prepare** (Step 1), **deploy** (Step 1)
|
||||
|
||||
### `mcp_azure_mcp_group_list`
|
||||
|
||||
| Required | Optional | Read-Only |
|
||||
|----------|----------|-----------|
|
||||
| *(none)* | `subscription`, `tenant` | ✅ |
|
||||
|
||||
Returns: resource group names and IDs as JSON array.
|
||||
|
||||
Used by: **prepare** (Step 7), **deploy** (Step 3)
|
||||
|
||||
### `mcp_azure_mcp_extension_cli_install`
|
||||
|
||||
| Required | Optional | Read-Only |
|
||||
|----------|----------|-----------|
|
||||
| `cli-type` (az\|azd\|func) | `tenant` | ✅ |
|
||||
|
||||
Returns: installation instructions for the specified CLI tool.
|
||||
|
||||
Used by: **prereq** (Step 2), **deploy** (Step 3)
|
||||
|
||||
### `mcp_azure_mcp_get_azure_bestpractices` (hierarchical)
|
||||
|
||||
| Sub-command | Required Params | Optional Params | Read-Only |
|
||||
|-------------|----------------|-----------------|-----------|
|
||||
| `get_azure_bestpractices_get` | `resource` (general\|azurefunctions\|static-web-app\|coding-agent), `action` (all\|code-generation\|deployment) | — | ✅ |
|
||||
| `get_azure_bestpractices_ai_app` | *(none)* | — | ✅ |
|
||||
|
||||
**Usage:** `resource` + `action` are both required for `_get`. For `static-web-app` and `coding-agent`, only `action: "all"` is supported.
|
||||
|
||||
Used by: **prereq** (Step 3), **scaffold** (Step 5)
|
||||
|
||||
---
|
||||
|
||||
## Tool Pitfalls
|
||||
|
||||
- **`mcp_azure_mcp_subscription_list` is slow at scale:** Returns ALL subscriptions across ALL tenants (238+ in large orgs), causing lengthy picker detours. Use `az account show` for the active subscription (<1 second). Reserve `subscription_list` for prepare Step 1 when the user explicitly wants a different subscription.
|
||||
@@ -0,0 +1,32 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **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.
|
||||
|
||||
# Pipeline Rules — Runtime Reference
|
||||
|
||||
Known platform bugs and deploy timing. Read before deploy completion or on scaffold/deploy error.
|
||||
|
||||
For core pipeline rules (approval gates, phase lifecycle, session artifacts, security baseline), see [pipeline-rules.md](pipeline-rules.md).
|
||||
|
||||
## Known Platform Bugs
|
||||
|
||||
| Bug | Symptom | Workaround |
|
||||
|-----|---------|------------|
|
||||
| SWA CLI Windows path with spaces | `StaticSitesClient.exe` fails when workspace path contains spaces | Use a short, space-free temp path on FIRST attempt (e.g., `C:\temp\swadeploy` on Windows, `/tmp/swa-deploy` on macOS/Linux) — do NOT wait for retry. Copy app content to the temp path before running `swa deploy`. |
|
||||
| `az deployment sub validate` HTTP stream | "HTTP response stream consumed" error (Azure CLI bug, also affects `create` in CLI 2.75.0+) | `validate`: use `az deployment sub what-if` instead. `create`: use `az rest --method PUT` on deployment URI as fallback. |
|
||||
| `az quota list` extension error | `PermissionError: [WinError 5]` blocks extension commands | Use `az rest` instead (see [sku-quota-validation.md](../prepare/references/sku-quota-validation.md)) |
|
||||
| Managed identity sidecar OOM on free-tier Linux | `503 Service Unavailable` with `Microsoft.Azure.WebSites.DataProtection` or `/msi/token` timeout | This is exactly why free tier is not used. Managed identity is REQUIRED and works on B1+; the compute floor is B1. Never provision F1/D1 (the sidecar OOMs there). |
|
||||
| `Compress-Archive` path flattening | PowerShell's `Compress-Archive -Path $files.FullName` uses absolute paths, flattening directory structure | Use `System.IO.Compression.ZipFile` with relative paths instead |
|
||||
| AADSTS530084 (Terraform) | Token protection conditional access policy breaks `azurerm` provider auth; regular `az` CLI commands work fine | Re-scaffold as Bicep |
|
||||
| Secret values with shell special chars | App-internal secrets containing `$`, `` ` ``, `!`, `'`, `"` break when passed as inline CLI args (`--parameters key=val`) | **Pass app-internal secrets via `main.parameters.json` / `terraform.tfvars`, or as an `@secure()` param from a variable reloaded from `deploy-secrets.env`** — never inline. Prefer URL-safe generation (no `/+=` and no shell-special chars) so the value is safe everywhere. No Key Vault. |
|
||||
| `az acr task logs` encoding crash | `UnicodeEncodeError` on Windows from Unicode chars in build logs | Use `--no-format` + strip non-ASCII, or REST API `listLogSasUrl` |
|
||||
| `create` tool nested path failure | `"Parent directory does not exist"` when creating files in `.copilot-azure/sessions/{id}/` | Run `New-Item -ItemType Directory -Path {parent} -Force` before `create`. Platform tool limitation — agent always recovers. |
|
||||
| Windows PowerShell `az rest` 415 error | `az rest --method put --body '{json}'` returns `415 Unsupported Media Type` on Windows PowerShell | Add `--headers "Content-Type=application/json"` to every `az rest --method put` call |
|
||||
|
||||
## Deploy Timing
|
||||
|
||||
- **B1 App Service cold-start:** After IaC creation, a newly created B1 plan needs ~30–90 seconds before the Kudu sidecar is ready. Deploying code immediately after `az deployment group create` returns can cause a 504 Gateway Timeout. Wait for sidecar readiness before code deployment.
|
||||
- **Identity tag resolution:** Resolve `deployed-by` once at session start via `az ad signed-in-user show`. Without this, resources may receive inconsistent tag values (display name, UPN, or hardcoded strings) across different runs.
|
||||
|
||||
## Shell Rules
|
||||
|
||||
⛔ Shell variables do NOT persist between tool calls. Generate secrets inline within the command that consumes them — never store in `$env:` for later use. Use synchronous shells for all deploy-phase operations.
|
||||
@@ -0,0 +1,91 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **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.
|
||||
|
||||
# Pipeline Rules — Reference
|
||||
|
||||
Cross-cutting rules enforced across all workflow steps. Referenced from [instructions.md](../instructions.md) `## Pipeline Rules`.
|
||||
|
||||
## Approval gates
|
||||
|
||||
⛔ **Two separate approval gates are required — never merge them.**
|
||||
|
||||
1. **Scaffold gate (Step 6):** "✅ Ready to proceed with scaffolding? (Yes / Edit plan / Cancel)" — approves IaC generation only. ⛔ When the plan includes PostgreSQL/MySQL, add `Private access` as a selectable choice — see approval-gates.md for the exact variant.
|
||||
2. **Deploy gate (Step 8):** "🚀 Ready to deploy? (Yes / Run manually / Edit plan / Cancel)" — approves resource provisioning.
|
||||
|
||||
The scaffold gate does NOT grant deploy permission. After scaffold completes, you MUST present the deploy gate as a SEPARATE response. Never go from scaffold approval directly to `az group create` or `az deployment`.
|
||||
|
||||
⛔ **BOTH gates MUST show Subscription (name + ID), Resource Group, and Region** as standalone lines above the service table — users must see WHERE resources will be created before approving.
|
||||
|
||||
⛔ **NEVER create, write, or modify infrastructure files before the user explicitly says "Yes" to the scaffold gate.** No exceptions — not for simple apps, trivial plans, free-tier deployments, or single-component repos.
|
||||
|
||||
⛔ **Modifying existing IaC files (not AppOnboard-generated) requires explicit user approval.** Present: "I need to modify {file}: {change description}. Approve? (Yes / Edit / Cancel)". This applies to repos with existing Bicep/Terraform where AppOnboard adjusts SKUs, regions, or settings.
|
||||
|
||||
Each gate is the LAST content in its response — do NOT continue past a gate in the same turn.
|
||||
|
||||
> ❌ BAD: Writes Bicep without approval · scaffolds and deploys in same response · skips deploy gate after scaffold approval
|
||||
> ✅ GOOD: Shows plan → user says Yes → scaffold → show validation summary → deploy gate → user says Yes → deploy
|
||||
|
||||
## Phase lifecycle
|
||||
|
||||
Update `context.json` at phase boundaries — combine `completedPhases` update with `currentPhase` for the next phase in a single write. Write the phase artifact before marking complete. The orchestrator instructions.md specifies exact write points (after prereq, after scaffold, after deploy).
|
||||
|
||||
`currentPhase` must NEVER appear in `completedPhases` — if invariant violated, halt and report.
|
||||
|
||||
`context.json` is NOT write-once — each phase boundary MUST update it on completion:
|
||||
- Write `intent` after Step 2
|
||||
- `components` after Step 3
|
||||
- `azure.resourceGroup` after Step 7 (also written to `deploy-result.json.resourceGroupName`)
|
||||
- Push to `completedPhases` at phase boundaries
|
||||
- Update `statusSummary` at every phase exit — 1-line description. Templates:
|
||||
- prereq: `"{N} components, stack: {detectedStack}, health: {overallHealth}"`
|
||||
- prepare: `"{N} services, ~${monthlyUsd}/mo, region: {region}"` (if `quotaValidation.checkedRegions` >1, append fallback reason: `"region: westus2 (eastus quota full)"`)
|
||||
- scaffold: `"{N} files, self-review: {VERIFIED|FLAGGED count}"`
|
||||
- deploy: `"{healthStatus}, RG: {resourceGroupName}"`
|
||||
- cancel: `"Paused at {phase} — {reason}"`
|
||||
|
||||
## Session artifacts
|
||||
|
||||
**Session file writes: `New-Item -ItemType Directory` for directories, `create` tool for file content.** Create session directory via `New-Item -ItemType Directory -Path ".copilot-azure/sessions/{uuid}" -Force`, then `create` tool for all JSON/md content. Never use `Out-File`, `Set-Content`, or shell commands for file content.
|
||||
|
||||
## Phase transition rule
|
||||
|
||||
> ⛔ **Before executing the FIRST command of any new phase, re-read that phase instructions.md.** After prereq → read `prepare/instructions.md`. After prepare → `scaffold/instructions.md`. After scaffold gate → `deploy/instructions.md`. This applies at EVERY transition.
|
||||
|
||||
## Post-compaction recovery
|
||||
|
||||
> ⛔ After ANY compaction, re-read current phase instructions.md + this file. Check `scaffold-manifest.json` and `completedPhases` exist if mid-scaffold/deploy.
|
||||
|
||||
Begin responses with: "Started session at `.copilot-azure/sessions/{uuid}/`" or "Resuming session from [date] — {statusSummary}".
|
||||
|
||||
⛔ **Session immutability:** NEVER write to any session folder other than the active session (the one `.copilot-azure/sessions/active-session.json` points to). Old sessions are read-only — no updates, no backfills, no status changes.
|
||||
|
||||
**Session TTL:** 7 days. Non-active sessions where `context.json.lastModifiedUtc` is >7 days ago are deleted on next invocation. The active session is never pruned.
|
||||
|
||||
## fastTrackEligible
|
||||
|
||||
Set by prereq: (1) auto-approves readiness gate, (2) simplifies prepare Step 3 alternatives. Does NOT skip phases, reads, gates, self-review, validation, or preflight.
|
||||
|
||||
## Deploy as-is
|
||||
|
||||
⛔ Do NOT refactor or upgrade working application code. Deploy what works. Fixing broken code IS allowed (build errors, missing deps) through the approval gate. Upgrade suggestions → `prepare-plan.json.postDeployRecommendations[]`. Infrastructure changes = allowed; code rewrites = forbidden; Azure compatibility changes (TLS, SSL, port) = allowed when detected by prereq AND approved. Never prompt for secrets — databases/caches/storage use managed identity (no passwords exist); auto-generate only app-internal secrets (e.g. `SECRET_KEY`) and store them on the compute resource (App Service app settings / Container Apps native secrets) via an `@secure()` param — no Key Vault.
|
||||
|
||||
## Known Platform Bugs
|
||||
|
||||
See [`pipeline-rules-runtime.md`](pipeline-rules-runtime.md) § Known Platform Bugs for the full bug table and workarounds.
|
||||
|
||||
## No top-level agent invocation
|
||||
|
||||
⛔ **NEVER call external agents** (`azure-validate`, `azure-deploy`, `azure-prepare`, etc.) during the AppOnboard pipeline. Only `azure-app-onboard-prereq` and `azure-app-onboard` orchestrator are allowed. Use direct CLI commands for validation and deployment.
|
||||
|
||||
## Structured sub-agent delegation
|
||||
|
||||
⛔ Use ONLY `subagent-*.md` templates — no ad-hoc prompts. Pass template content verbatim. Destructive commands (`az deployment`, `az webapp deploy`, `az acr build`) execute in main thread only.
|
||||
|
||||
## Security baseline
|
||||
|
||||
See [iac-generation-rules.md](../scaffold/references/iac-generation-rules.md) § Security Patterns and [bicep-patterns-security.md](../scaffold/references/bicep-patterns-security.md). Flag `AllowAzureServices` firewall rule as a security warning.
|
||||
|
||||
## azure.yaml prohibition
|
||||
|
||||
⛔ **NEVER generate `azure.yaml`. NEVER use `azd up`/`azd provision`/`azd deploy`.** AppOnboard deploys via `az deployment sub create` (Bicep) or `terraform apply` (Terraform). Repos with existing `azure.yaml` → route per [`azd-template-routing.md`](azd-template-routing.md).
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **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.
|
||||
|
||||
# Session Protocol — Step 1
|
||||
|
||||
## All Prompts Are Actionable
|
||||
|
||||
> ⛔ **ALL prompts that activate this agent are actionable — go directly to Step 1.** Do NOT answer the user's question, give an overview of capabilities, or describe what AppOnboard can do before starting the pipeline. "Can Azure figure out my app?" and "Deploy my app" are the same action: Step 1 → Step 2 → scan. The user's phrasing (question vs command) does NOT change the workflow.
|
||||
|
||||
## Session Check
|
||||
|
||||
Resolve active session via pointer file.
|
||||
|
||||
> ⛔ **YOU MUST CREATE A SESSION BEFORE DOING ANY WORK — INCLUDING SCANNING**
|
||||
>
|
||||
> 1. **STOP** — Do not answer the user's question, scan code, or plan architecture yet
|
||||
> 2. **CHECK** — Read `.copilot-azure/sessions/active-session.json`.
|
||||
> - ⛔ **First, ensure the repo's `.gitignore` contains `.copilot-azure/`** (append if missing, create the file if absent) — this runs on EVERY path below, BEFORE any branch writes a session file, since session artifacts may hold deploy secrets.
|
||||
> - **Pointer exists** → ⛔ **You MUST read [`session-schemas.ts`](session-schemas.ts)** to get the exact field names and types for `AppOnboardContext`. Do not guess field names. Then read the pointed-to session's `context.json`. Display: "Found session from [lastModifiedUtc] — {statusSummary}." ⛔ **You MUST ask the user via `ask_user`: "Resume this session or start fresh?" Do NOT auto-resume.** This gate is mandatory — stale sessions from prior tests cause the agent to skip phase instructions.md reads and miss artifact writes.
|
||||
> - Resume → ⛔ **Refresh the deployment inventory baseline before any more provisioning.** Preserve the current `deploy-result.json.createdResources[]` and `orphanedResourceGroups[]`, then call `capture_deployment_inventory` with `phase: "baseline"`, using `context.json.sessionId` and `context.json.azure.subscriptionId`. If the subscription is missing or auth must be refreshed, complete the Azure login gate below first, but do not run `az deployment`, `az group create`, `az webapp deploy`, `az acr build`, or any other command that can create resources until the baseline call succeeds. This new baseline starts a resumed inventory segment: union later capture output with the preserved inventory by normalized resource ID and case-insensitive resource-group name; never discard cleanup evidence from before the resume. Then ⛔ **read the phase instructions.md for the NEXT phase** (derive from `completedPhases`). E.g., prereq done → read `prepare/instructions.md`, then continue from that phase.
|
||||
> - ⛔ **If `context.json.routeToSkill` is set:** The previous session was halted for migration (e.g., `azure-cloud-migrate`). The code has likely changed since then. **Do NOT skip prereq** — start fresh: clear `routeToSkill`, `routeReason`, remove `"prereq"` from `completedPhases`, and re-run from Step 2. This ensures the migrated codebase gets a clean 3-axis evaluation.
|
||||
> - ⛔ **If `completedPhases` includes `"prereq"` (and no `routeToSkill`):** Prereq already wrote `prereq-output.json` and `context.json.components[]`. Proceed to Step 2 (scope triage) — prereq may have been invoked standalone, so the user still needs to confirm the full pipeline. Skip Step 3 (prereq invocation), then continue to Step 4 (scan-informed intent gathering).
|
||||
> - Start fresh → generate a new UUID via `[guid]::NewGuid().ToString()`, create a new session folder, update `active-session.json` to point to the new session. Old session folder is never touched again.
|
||||
> - **Pointer missing but session folders exist** → list folders under `.copilot-azure/sessions/`. If 1 folder: adopt it (read its `context.json`, write `active-session.json` pointing to it, show summary). If 2+: show a numbered list with `statusSummary` + `lastModifiedUtc` from each, ask user to pick one or start fresh. Write pointer for the chosen session.
|
||||
> - **No sessions at all** → generate a UUID by running `[guid]::NewGuid().ToString()` in the terminal. ⛔ **You MUST generate the UUID via a terminal command — do NOT hardcode a placeholder like `a1b2c3d4-e5f6-7890-abcd-ef1234567890`.** Create the session directory: `New-Item -ItemType Directory -Path ".copilot-azure/sessions/{uuid}" -Force`. Then write a **minimal** `context.json` using the `create` tool — only these 3 fields are known immediately: `{ "sessionId": "{uuid}", "createdUtc": "{ISO 8601 now}", "intent": { "userPrompt": "{user's first message verbatim}" } }`. Write `active-session.json` with `activeSessionId: {uuid}` using the `create` tool.
|
||||
> 3. **PRUNE** — After resolving the active session, check remaining session folders. Delete any where `context.json.lastModifiedUtc` is >7 days ago. **Never delete the active session** (the one `active-session.json` points to).
|
||||
> 4. **VERIFY** — Confirm `context.json` exists and is valid JSON. If missing or malformed, halt and retry creation — do NOT continue to Step 2 without a verified session.
|
||||
> 5. **CONFIRM** — Begin your first response with: "Started session at `.copilot-azure/sessions/{uuid}/`" (new) or "Resuming session from [date] — {statusSummary}" (existing)
|
||||
> 6. **THEN** proceed to Step 2
|
||||
>
|
||||
> ⛔ **Ordering: session FIRST, scanning SECOND.** If you scan the workspace or read project files before writing `context.json`, you have violated the session-first rule. The session must exist before ANY code analysis.
|
||||
>
|
||||
> ⛔ **Shell fallback:** If PowerShell/terminal hangs on first attempt (no output after 10s), use the `create` tool directly for session directory and file writes. Do NOT retry shell commands more than once.
|
||||
>
|
||||
> ⛔ **Path scoping: ALL `create` tool calls for session artifacts MUST target `.copilot-azure/sessions/{active-session-id}/`.** Writing to any other session folder is forbidden.
|
||||
|
||||
## CLI Availability
|
||||
|
||||
Call `mcp_azure_mcp_extension_cli_install` with `cli-type: "az"` to verify Azure CLI is available. If missing, surface installation instructions before proceeding. Downstream phases (prepare, deploy) require it. Fallback: skip if MCP tool unavailable.
|
||||
|
||||
## Azure Login Gate
|
||||
|
||||
**Azure login gate (mandatory):** Run `az account show --query "{id:id, name:name, tenantId:tenantId}" -o json` with a **15-second timeout** (PowerShell: `Start-Process` with `-Wait` or inline timeout; if command hangs beyond 15s, treat as failure). Also run `az ad signed-in-user show --query displayName -o tsv` (15-second timeout). After BOTH commands complete, merge ALL azure fields into `context.json.azure` in a **SINGLE update** — `subscriptionId`, `subscriptionName`, `tenantId`, and `userDisplayName`. Do NOT write separate updates for subscription and identity.
|
||||
|
||||
> ⛔ **If `az account show` fails or hangs:** ⛔ **You MUST read [`subscription-resolution.md`](subscription-resolution.md)** and follow its fallback procedure. Do NOT proceed to Step 2 without a resolved subscription. Do NOT leave `context.json.azure` empty and continue. Every downstream phase (prepare, scaffold validation, deploy) requires Azure auth — proceeding without it produces incomplete results.
|
||||
|
||||
## User Identity Detection
|
||||
|
||||
**User identity detection (for `deployed-by` tag):** Run `az ad signed-in-user show --query displayName -o tsv` (15-second timeout) alongside `az account show`. Fallback if `az ad` fails: use `az account show --query user.name -o tsv` (returns UPN/email). If both fail, leave empty — prepare phase will resolve. This value becomes the `deployed-by` tag on ALL resources — resolving it once here prevents inconsistent tag values across resources. **Merge into the SAME `context.json` update as the azure login gate — do NOT write separately.**
|
||||
|
||||
## Subscription Detection Method
|
||||
|
||||
> ⛔ **`az account show` is the ONLY subscription detection method in Step 1.** Do NOT call `mcp_azure_mcp_subscription_list` here — that tool returns ALL subscriptions across ALL tenants and causes a lengthy picker detour. `az account show` returns the CLI's active subscription in <1 second. MCP subscription list is reserved for prepare Step 1 when the user explicitly wants a different subscription.
|
||||
|
||||
## Artifact Locations
|
||||
|
||||
| Location | Artifacts |
|
||||
|----------|-----------|
|
||||
| `.copilot-azure/sessions/{uuid}/` | `context.json`, `prereq-output.json`, `prepare-plan.json`, `scaffold-manifest.json`, `deploy-result.json` |
|
||||
| `.copilot-azure/sessions/{uuid}/replaced-files/` | User files displaced by scaffold (existing IaC), stored at their original relative path (**mirror path** = same directory structure as the repo). Never overwritten or deleted — moved here so the original is preserved. |
|
||||
|
||||
## Phase-gated Reference Loading
|
||||
|
||||
> ⛔ **Phase-gated reference loading.** Do NOT pre-read reference files for downstream phases. Read each phase's references only when entering that step. Scaffold references (bicep-patterns, self-review) are irrelevant during deploy; prepare references (service-mapping, pricing-guide) are irrelevant during scaffold. Each phase instructions.md specifies its own required reads.
|
||||
@@ -0,0 +1,152 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* Context + shared TypeScript interfaces for AppOnboard session artifacts:
|
||||
* context.json, active-session.json, and shared types used across all phases.
|
||||
*
|
||||
* Per-phase schemas (each phase has its own in its references/ folder):
|
||||
* - azure-app-onboard-prereq/references/prereq-schemas.ts — prereq-output.json
|
||||
* - prepare/references/prepare-schemas.ts — prepare-plan.json
|
||||
* - scaffold/references/scaffold-schemas.ts — scaffold-manifest.json
|
||||
* - deploy/references/deploy-schemas.ts — deploy-result.json
|
||||
*
|
||||
* Source of truth for JSON artifacts in `.copilot-azure/sessions/{session-id}/`.
|
||||
*/
|
||||
|
||||
// ─── shared types (used across all phases) ───────────────────────────────────
|
||||
|
||||
export interface AppOnboardComponentStack {
|
||||
language: string;
|
||||
framework: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export type ReadinessStatus = "ready" | "fixesApplied" | "needsFixes" | "unknown";
|
||||
|
||||
export interface AppOnboardComponentReadiness {
|
||||
status: ReadinessStatus;
|
||||
fixes: string[];
|
||||
}
|
||||
|
||||
export type VerdictLevel = "PASS" | "WARN" | "FAIL" | "SKIPPED";
|
||||
|
||||
export interface AppOnboardComponentVerdicts {
|
||||
build: VerdictLevel;
|
||||
completeness: Exclude<VerdictLevel, "SKIPPED">;
|
||||
deployability: Exclude<VerdictLevel, "SKIPPED">;
|
||||
}
|
||||
|
||||
export interface AppOnboardComponentFinding {
|
||||
category: "build" | "completeness" | "deployability";
|
||||
verdict: VerdictLevel;
|
||||
summary: string;
|
||||
fix: string | null;
|
||||
}
|
||||
|
||||
export interface AppOnboardComponent {
|
||||
name: string;
|
||||
path: string;
|
||||
stack: AppOnboardComponentStack;
|
||||
readiness: AppOnboardComponentReadiness;
|
||||
verdicts?: AppOnboardComponentVerdicts;
|
||||
findings?: readonly AppOnboardComponentFinding[];
|
||||
}
|
||||
|
||||
export interface AppOnboardAzureTarget {
|
||||
subscriptionId: string;
|
||||
/** Display name of the subscription (from `az account show --query name`).
|
||||
* Shown at both approval gates so the user can verify the target. */
|
||||
subscriptionName: string;
|
||||
resourceGroup: string;
|
||||
region: string;
|
||||
}
|
||||
|
||||
export interface AppOnboardRepoInfo {
|
||||
remote: string | null;
|
||||
}
|
||||
|
||||
export interface AppOnboardOverride {
|
||||
key: string;
|
||||
value: string;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
export interface AppOnboardAppInfo {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface PostDeployRecommendation {
|
||||
title: string;
|
||||
reason: string;
|
||||
effort: "low" | "medium" | "high";
|
||||
services?: string[];
|
||||
}
|
||||
|
||||
export interface DetectedService {
|
||||
type: string;
|
||||
version?: string;
|
||||
source: "compose" | "config" | "code";
|
||||
}
|
||||
|
||||
// ─── context.json ─────────────────────────────────────────────────────────────
|
||||
|
||||
export interface AppOnboardIntent {
|
||||
userPrompt: string;
|
||||
description: string;
|
||||
users?: string;
|
||||
auth?: string;
|
||||
scale?: string;
|
||||
budget?: string;
|
||||
/** Set to true after prereq scan refines intent */
|
||||
refinedFromScan?: boolean;
|
||||
/** Facts discovered by the prereq scan */
|
||||
scanDiscoveredFacts?: string[];
|
||||
}
|
||||
|
||||
export type AppOnboardPhase = "info" | "prereq" | "prepare" | "scaffold" | "deploy" | "cicd" | "observe";
|
||||
|
||||
export interface AppOnboardContext {
|
||||
sessionId: string;
|
||||
createdUtc: string;
|
||||
lastModifiedUtc: string;
|
||||
currentPhase: AppOnboardPhase | null;
|
||||
completedPhases: readonly AppOnboardPhase[];
|
||||
/** Human-readable 1-line summary of where the session stands, updated at each phase exit.
|
||||
* Displayed in the session picker when the user resumes or switches sessions. */
|
||||
statusSummary?: string;
|
||||
intent: AppOnboardIntent;
|
||||
components: AppOnboardComponent[];
|
||||
azure: AppOnboardAzureTarget;
|
||||
repo: AppOnboardRepoInfo;
|
||||
app?: AppOnboardAppInfo;
|
||||
/** Infrastructure file types detected in repo: dockerfile, terraform, bicep, azure-yaml, github-actions */
|
||||
detectedInfra: readonly string[];
|
||||
/** Cloud provider targeted by detected IaC. Only populated when `.tf` or `.bicep` files found.
|
||||
* Used by scaffold to distinguish "existing Azure IaC" (halt) from "non-Azure IaC" (generate Azure TF alongside). */
|
||||
detectedInfraProvider?: {
|
||||
terraform?: "azure" | "gcp" | "aws" | "multi" | "unknown";
|
||||
};
|
||||
/** Service dependencies parsed from docker-compose, config files, or code imports */
|
||||
detectedServices: readonly DetectedService[];
|
||||
overrides: AppOnboardOverride[];
|
||||
/** Set when a phase routes the pipeline to another agent — e.g. "azure-cloud-migrate"
|
||||
* (non-Azure cloud SDK deps) or "azure-prepare" (existing azd template). Presence halts
|
||||
* the greenfield pipeline; the resume path in session-protocol.md clears it and re-runs prereq. */
|
||||
routeToSkill?: string;
|
||||
/** Human-readable reason paired with `routeToSkill` (e.g. "existing-azd-template"). */
|
||||
routeReason?: string;
|
||||
}
|
||||
|
||||
// ─── active-session.json ─────────────────────────────────────────────────────
|
||||
|
||||
/** Pointer file at `.copilot-azure/sessions/active-session.json`.
|
||||
* Avoids scanning all session folders on startup — read this one file
|
||||
* to find the active session, then read that session's context.json. */
|
||||
export interface ActiveSessionPointer {
|
||||
activeSessionId: string;
|
||||
}
|
||||
|
||||
// PrereqOutput, BuildRequirements → see azure-app-onboard-prereq/references/prereq-schemas.ts
|
||||
@@ -0,0 +1,17 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **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.
|
||||
|
||||
# Subscription Resolution — Defensive Fallback
|
||||
|
||||
The `azure-app-onboard` orchestrator resolves the subscription at Step 1 (login hard gate) and writes `subscriptionId`, `subscriptionName`, `tenantId` to `context.json.azure` before any phase runs. In normal operation, `context.json.azure.subscriptionId` is always set by the time prepare runs.
|
||||
|
||||
At prepare phase entry, verify `context.json.azure.subscriptionId` is set. If it is (expected path), use it — done.
|
||||
|
||||
If `context.json.azure` is somehow empty, resolve now rather than halting the flow:
|
||||
|
||||
1. **Check env vars** — if `AZURE_SUBSCRIPTION_ID` is set, use it directly (with `AZURE_TENANT_ID` if set). Write `subscriptionId`, `subscriptionName`, `tenantId` to `context.json.azure`, done.
|
||||
2. **Run `az account show`** — `az account show --query "{id:id, name:name, tenantId:tenantId}" -o json`. If it succeeds, **auto-select** — write `subscriptionId`, `subscriptionName`, `tenantId` to `context.json.azure`. Do NOT run `az account list` or present a picker.
|
||||
3. **Fallback: `mcp_azure_mcp_subscription_list` + picker** — only if `az account show` fails. Call `mcp_azure_mcp_subscription_list` to retrieve all subscriptions (returns `subscriptionId`, `displayName`, `isDefault`).
|
||||
- **1 subscription** → auto-select, no question. Write `subscriptionId`, `subscriptionName`, `tenantId` to `context.json.azure`.
|
||||
- **2+ subscriptions** → present a picker via `ask_user`: list each subscription as a choice `"{displayName} ({subscriptionId})"` with the default marked. The user selects one. Write `subscriptionId`, `subscriptionName`, `tenantId` to `context.json.azure`.
|
||||
4. **MCP tool fails** → attempt `az login` (interactive browser login). If that fails (no browser, remote session), fall back to `az login --use-device-code`. On success, retry from step 2. **Cap login at 3 attempts total** — if login still fails after the 3rd attempt, **HALT once** with a clear, actionable message: the exact `az login --tenant <tenant>` command to run, and that re-invoking the agent resumes this session (completed phases are preserved). Do NOT retry past 3 attempts, and do NOT proceed without a resolved subscription.
|
||||
Reference in New Issue
Block a user