diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.devcontainer/.gitattributes b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.devcontainer/.gitattributes deleted file mode 100644 index 43b1d6258..000000000 --- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.devcontainer/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -# Keep the post-create hook LF so bash inside the dev container doesn't choke on -# CRLF when the repo is checked out on Windows. -*.sh text eol=lf diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.devcontainer/devcontainer.json b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.devcontainer/devcontainer.json deleted file mode 100644 index f9d80e1ee..000000000 --- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.devcontainer/devcontainer.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "Foundry CLM Microhack", - "image": "mcr.microsoft.com/devcontainers/python:3.11-bookworm", - "features": { - "ghcr.io/devcontainers/features/azure-cli:1": {}, - "ghcr.io/azure/azure-dev/azd:0": {}, - "ghcr.io/devcontainers/features/node:1": { - "version": "20" - }, - "ghcr.io/devcontainers/features/github-cli:1": {} - }, - "postCreateCommand": "bash .devcontainer/post-create.sh", - "customizations": { - "vscode": { - "extensions": [ - "ms-python.python", - "ms-python.vscode-pylance", - "ms-azuretools.vscode-azureresourcegroups", - "ms-azuretools.vscode-bicep", - "teamsdevapp.ms-teams-vscode-extension", - "github.copilot", - "github.copilot-chat" - ], - "settings": { - "python.defaultInterpreterPath": "/usr/local/bin/python" - } - } - }, - "remoteEnv": { - "PYTHONPATH": "${containerWorkspaceFolder}/src" - } -} diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.devcontainer/post-create.sh b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.devcontainer/post-create.sh deleted file mode 100644 index 9e29a8141..000000000 --- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.devcontainer/post-create.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash -# ============================================================================= -# Dev container post-create hook (runs once after the container is built). -# -# 1. Install the Python dependencies (src/requirements.txt) — ESSENTIAL: -# every challenge needs these. -# 2. Best-effort install of the Microsoft ODBC Driver 18 for SQL Server so the -# OPTIONAL Azure SQL contract-status / renewal tool works out of the box. -# That tool's connection string uses "Driver={ODBC Driver 18 for SQL -# Server}" (see labautomation/infra/resources.bicep), so unixODBC alone is -# not enough. If this step fails (e.g. no network to packages.microsoft.com) -# the tool simply falls back to the bundled JSON corpus, so we NEVER fail -# the whole container build over an optional driver. -# ============================================================================= -set -euo pipefail - -echo "==> [1/2] Installing Python dependencies (src/requirements.txt)" -pip install --upgrade pip -pip install -r src/requirements.txt - -echo "==> [2/2] Installing Microsoft ODBC Driver 18 for SQL Server (optional Azure SQL tool)" -if command -v odbcinst >/dev/null 2>&1 && odbcinst -q -d 2>/dev/null | grep -q "ODBC Driver 18 for SQL Server"; then - echo " Driver already present — skipping." -else - # Debian 12 (bookworm) — matches the python:3.11-bookworm base image. - if ( - set -e - curl -sSL -O https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb - sudo dpkg -i packages-microsoft-prod.deb - rm -f packages-microsoft-prod.deb - sudo apt-get update - sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18 - ); then - echo " ODBC Driver 18 installed." - else - echo " WARN: msodbcsql18 install failed — the Azure SQL tool will use the JSON fallback." - fi -fi - -echo "==> post-create complete." diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.github/instructions/labautomation.instructions.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.github/instructions/labautomation.instructions.md index ad5afbf56..c54db8b4a 100644 --- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.github/instructions/labautomation.instructions.md +++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/.github/instructions/labautomation.instructions.md @@ -73,6 +73,35 @@ Write a hashtable to the output stream — the platform captures every one: Always return at minimum the resource group name so users can find their resources. +## shared-deploy-lab.ps1 — Once-Per-Subscription Hook + +Optional sibling of `deploy-lab.ps1`, picked up automatically by file name. The platform runs it +**once per subscription, before** the per-participant `deploy-lab.ps1` fan-out — the correct home for +one-off subscription prep the parallel lab runs would otherwise race on (**registering resource +providers**, shared hub resources). If it **throws**, the platform runs **no** `deploy-lab.ps1` for +that subscription, so keep provider registration best-effort (try/catch + WARN, never throw). + +Required parameter contract: + +```powershell +param( + [Parameter(Mandatory = $true)] + [string]$SubscriptionId, + + [Parameter(Mandatory = $true)] + [string[]]$PreferredLocation = @(), + + [Parameter(Mandatory = $false)] + [string[]]$AllowedEntraUserIds = @() +) +``` + +This hack uses it to register **`Microsoft.BotService`** (Challenge 5 Teams / M365 publish) once per +subscription — so participants never hit `MissingSubscriptionRegistration`, **without** switching +`deploymentType` to `resourcegroup-with-subscriptionowner` (which would make every participant an +Owner of the shared subscription). Do NOT register providers in `deploy-lab.ps1`: in a `resourcegroup` +lab that script runs with only subscription-**Reader**, and every parallel job would race. + ## lab-defaults.json — Required Shape ```json diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/README.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/README.md index 04660f1c4..4e6b4a17e 100644 --- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/README.md +++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/README.md @@ -6,7 +6,7 @@ Build a **multi-model, multi-agent** contract assistant on **Microsoft Foundry** **Foundry IQ**, traced and evaluated, exposed as an **MCP server**, and published to **Microsoft 365 Copilot & Teams**. -> A 4.5-hour microhack · 5 challenges (+ optional bonus) · code-first (Python) · GitHub Codespaces. +> A 4.5-hour microhack · 5 challenges (+ optional bonus) · code-first (Python) · runs locally in VS Code. ## Introduction @@ -264,7 +264,7 @@ challenges are a single story: | # | Challenge | Focus | Duration | |---|-----------|-------|----------| -| [1](challenges/challenge-01.md) | Resource deployment · Codespaces · `.env` · corpus seeding | Setup | 30 min | +| [1](challenges/challenge-01.md) | Resource deployment · local venv setup · `.env` · corpus seeding | Setup | 30 min | | [2](challenges/challenge-02.md) | Intake & Drafting agent + Foundry IQ + tools | Grounding · tools · guardrails | 60 min | | [3](challenges/challenge-03.md) | Observability, tracing & evaluation | Tracing · eval | 60 min | | [4](challenges/challenge-04.md) | Clause & Risk agent + Orchestrator + MCP server | Orchestration · MCP | 55 min | @@ -293,14 +293,19 @@ challenges are a single story: - An **Azure subscription** with rights to create a Foundry project and deploy GPT models (confirm availability in your target region via the model catalog). -- **GitHub account** (to open the repo in Codespaces). -- Basic Python. No local install needed — the devcontainer has everything. +- **GitHub account** (to clone the repo). +- **Python 3.11+**, **Git**, the **Azure CLI** (`az`), and the **Azure Developer CLI** (`azd`) installed locally. Basic Python knowledge; a virtual environment keeps the pinned deps isolated. - For Challenge 5: a Microsoft 365 tenant where you can sideload a Teams app (or a coach-provided one). ## Getting started -1. **Open this repo in Codespaces** (no fork needed — the optional Challenge 6 CI bonus is the only part that needs a fork) — **Code → Codespaces → Create codespace**. The devcontainer installs - Python 3.11, Azure CLI, `azd`, Node, and `src/requirements.txt` automatically. +1. **Clone this repo and open it in VS Code** (no fork needed — the optional Challenge 6 CI bonus is the only part that needs a fork). Create a virtual environment and install the dependencies: + ```bash + python -m venv .venv + source .venv/bin/activate # Windows (PowerShell): .venv\Scripts\Activate.ps1 + python -m pip install --upgrade pip + pip install -r src/requirements.txt + ``` 2. `az login` (and `azd auth login` if you use the `azd up` path) 3. Do **[Challenge 1](challenges/challenge-01.md)** to deploy resources and seed the corpus — provision with **`azd up`** (run from `src/`; Bicep in `labautomation/infra/`), the **`labautomation/deploy`** script, or the one-click @@ -317,7 +322,6 @@ challenges are a single story: ``` . -├── .devcontainer/ # Codespaces / Dev Containers definition ├── README.md # this file ├── challenges/ # challenge-01 … challenge-06 (one markdown brief per challenge) ├── walkthrough/ # challenge-0N/solution-0N.md — reference solution per challenge diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/challenges/challenge-01.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/challenges/challenge-01.md index c5627509e..2ae3edac0 100644 --- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/challenges/challenge-01.md +++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/challenges/challenge-01.md @@ -5,7 +5,7 @@ Welcome to your very first challenge! Here you lay the foundation for the whole microhack: you'll deploy the Azure resources, wire up your development environment, and seed the contract corpus the later challenges build on. By the end you'll have the full **Microsoft Foundry** environment -running in a **prebuilt dev container** — so the rest of the hack is pure agent-building. +wired up to **VS Code** on your own machine — so the rest of the hack is pure agent-building. If something isn't working as expected, please let your coach know. @@ -14,7 +14,8 @@ If something isn't working as expected, please let your coach know. > **📋 Prerequisites:** > - An **Azure subscription** your lab was provisioned in *(or, if self-hosting, one with rights to create a Foundry project and deploy GPT models)*. > - A **GitHub account** (to clone the repo). -> - **VS Code** with the **Dev Containers** extension and **Docker Desktop** — the repo's dev container has everything preinstalled *(or **GitHub Codespaces**, if you'd rather run in the browser)*. +> - **VS Code** with the **Python** extension. +> - Installed locally: **Python 3.11+**, **Git**, the **Azure CLI** (`az`), and the **Azure Developer CLI** (`azd`). *(Node 20+ only for the optional Teams publish in Challenge 4.)* > 🧩 **How to use this challenge:** for a MicroHack event your Azure resources are **provisioned for > you** — you just point your `.env` at them (Task 3) and **confirm you understand what got created**: @@ -30,8 +31,7 @@ If something isn't working as expected, please let your coach know. ## 🧭 Context -Everything runs inside the **dev container** in this repo (Python 3.11, Azure -CLI, `azd`, Node) — open it locally in **VS Code** (Dev Containers) or in **GitHub Codespaces**. For a MicroHack event the resources below are **already provisioned** into **one +You run everything from a local clone of this repo in **VS Code**, inside a Python **virtual environment** (Python 3.11+, plus the Azure CLI, `azd`, and — optionally — Node). For a MicroHack event the resources below are **already provisioned** into **one resource group** and their endpoints appear on your **lab dashboard**; you copy them into `.env` in Task 3. *(Self-hosting? One **`azd up`** — Bicep in [`infra/`](../labautomation/infra/) — provisions the same resource group and autofills `.env`.)* @@ -130,27 +130,48 @@ text at crawl time); regenerate the PDFs with `python src/scripts/make_corpus_pd - [ ] You can sign in to the [Azure Portal](https://portal.azure.com) with the account your lab was provisioned for (or, if self-hosting, one that can **create resources**). - [ ] *(Self-hosting only)* Your Azure subscription can deploy **GPT** models (ask your coach if unsure). -- [ ] You have ~30 minutes and a stable connection (provisioning takes 5–10 min on its own). -### Task 1 · Open the project in VS Code (~7 min) +### Task 1 · Clone the project and set up your environment (~7 min) -**No fork needed for the main hack (Challenges 1–5)** — the code you run lives in this repo. Clone it and open it in **VS -Code** using the **Dev Containers** extension (a prebuilt container with Python, Azure CLI, `azd`, and Node — no manual installs); because you work off the source repo, `git pull` +**No fork needed for the main hack (Challenges 1–5)** — the code you run lives in this repo. Clone it, open it in **VS +Code**, and create a Python **virtual environment** for the dependencies; because you work off the source repo, `git pull` always gets the latest fixes. *(The **one exception** is the optional **Challenge 6** CI bonus — it runs in **GitHub Actions**, so it needs **your own fork**; you'll create it there, not now.)* -1. Open the folder in **VS Code** (e.g. `code microhack-aiagents`). When VS - Code prompts **"Reopen in Container"**, click it — or run **Dev Containers: Reopen in Container** from - the Command Palette (**F1**). Requires the **Dev Containers** extension and **Docker Desktop**. -2. Wait for the container to build — it installs dependencies with `pip install -r src/requirements.txt` - automatically. When the terminal stops scrolling and shows a prompt, it's ready. +1. **Create and activate a virtual environment** in a **VS Code Terminal** (`` Ctrl+` ``), from the repo root: -✅ **You'll know it worked when:** a **VS Code** window opens (locally or in the browser) with a **Terminal** panel showing a -ready prompt. + ```bash + python -m venv .venv + ``` + + ```powershell + # Windows (PowerShell) + .venv\Scripts\Activate.ps1 + ``` + + ```bash + # macOS / Linux + source .venv/bin/activate + ``` + + Your prompt should now start with `(.venv)`. + +2. **Install the dependencies** into the venv: + + ```bash + python -m pip install --upgrade pip + pip install -r src/requirements.txt + ``` + + When the terminal stops scrolling and shows a prompt, it's ready. + +3. **Point VS Code at the venv:** open the Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P`) → **Python: Select Interpreter** → choose the interpreter under **`.venv`**. This makes the editor, terminal, and later tasks use the same environment. + +✅ **You'll know it worked when:** the terminal prompt shows **`(.venv)`**, `pip install` finishes with no red errors, and **Python: Select Interpreter** shows `.venv` selected. > [!NOTE] -> **Prefer the browser? Use GitHub Codespaces instead.** On the repo's GitHub page, click **`< > Code` → Codespaces → Create codespace on `main`** — the same dev container builds in the cloud, so you need no local Docker. +> **Why a virtual environment?** It isolates this hack's pinned dependencies from your system Python — and on macOS/Linux with Python 3.12+ it avoids the `externally-managed-environment` error that blocks a global `pip install`. The `.venv` folder lives at the repo root, is already git-ignored, and is where some later scripts (evaluation, red-team) expect it. > -> While it builds, skim the [scenario & architecture](../README.md#the-scenario--contoso-global) so the pieces you deploy here make sense. +> While `pip install` runs, skim the [scenario & architecture](../README.md#the-scenario--contoso-global) so the pieces you deploy here make sense. --- @@ -423,7 +444,7 @@ Requires the Azure CLI signed in (`az login`) **and** rights to grant admin cons Privileged Role Admin / Application Administrator — in your own sandbox tenant, that's you): ```bash -bash src/scripts/setup_sharepoint_app.sh # Codespaces / Linux / macOS +bash src/scripts/setup_sharepoint_app.sh # Linux / macOS / WSL # — or on Windows PowerShell — pwsh src/scripts/setup_sharepoint_app.ps1 ``` @@ -604,7 +625,7 @@ Smoke test: ✅ PASS | `Project can only be created under AIServices Kind account with allowProjectManagement set to true` | Fixed in the template (`account.properties.allowProjectManagement: true`). If you hit it, your checkout is behind — run `git pull` and redeploy. | | SharePoint: *"Tenant does not have a SPO license"*, or you can't grant the app's Graph **admin consent** (only Global Reader / **"Grant admin consent" greyed out**) | Only happens if you're **not** an admin of the tenant — in your own sandbox tenant the Path A script self-grants consent. If you hit it, it's **not** a failure: use the **local-PDF fallback (Path B)** — leave the `SHAREPOINT_*` values blank in `.env` and run `python src/scripts/seed_corpus.py`. It extracts `src/data/**/*.pdf` and populates `clm-corpus` directly (needs the Search Index Data Contributor role, granted during provisioning) — the **same index** the SharePoint path builds, so Challenges 2–6 are unaffected. See [Task 5, Path B](#task-5--seed-the-corpus). | | `account project create` unavailable | The CLI project command is preview. Create the project in the **Foundry portal**, then set `AZURE_AI_PROJECT_ENDPOINT` in `.env` manually (Overview → Endpoint). | -| `az login` in a dev container / Codespaces | Use `az login --use-device-code`. | +| `az login` doesn't open a browser (headless / remote terminal) | Use `az login --use-device-code`. | | Search / quota errors | Ensure the subscription has quota for Basic Search + the model SKUs; request quota if needed. | | `PermissionDenied` after deploy | RBAC can take 5–10 min to propagate. Wait, run `az login --use-device-code` again, and retry. | diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/challenges/challenge-04.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/challenges/challenge-04.md index ad3ec02ee..4070baf84 100644 --- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/challenges/challenge-04.md +++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/challenges/challenge-04.md @@ -279,10 +279,10 @@ from the **repo root**. The script **reads your `.env`** (the same one the agent nothing to fill in: ```bash -bash deploy/mcp-server/deploy.sh # Codespaces / Linux / macOS / Cloud Shell +bash deploy/mcp-server/deploy.sh # Linux / macOS / WSL / Cloud Shell ``` ```powershell -./deploy/mcp-server/deploy.ps1 # Windows PowerShell ONLY — not for Codespaces/bash +./deploy/mcp-server/deploy.ps1 # Windows PowerShell ONLY — not for bash ``` The script builds the image, creates the Container App with **external HTTPS ingress**, turns on a @@ -367,9 +367,13 @@ at your URL. | MCP tool call times out | Each call spins up + tears down a Foundry agent (a few seconds). Keep drafts short while testing. | | `orchestrator_mcp.py` finds no tools / hangs at startup | The stdio server failed to import. Confirm `python src/mcp_server/server.py` starts standalone; `MCPStdioTool` sets `PYTHONPATH=src`, so run from the repo root. | | Web search tool not attaching | Confirm `AZURE_BING_CONNECTION_NAME` matches a **project connection** for your Grounding with Bing Search resource; run `python src/kb_setup.py` — it prints whether the web-grounding tool built. | -| `deploy.sh` fails / `az containerapp up` errors | Ensure `az` ≥ 2.53 and the **containerapp** extension (`az extension add -n containerapp`), you're logged in (`az login`) and on the lab subscription (`az account set -s `), and you're running it from the **repo root** (the script builds from the `src/` context — `src/Dockerfile`, `src/requirements.txt`, app code). First run also registers the `Microsoft.App`/`Microsoft.OperationalInsights` providers — that can take a minute. | +| `deploy.ps1` / `deploy.sh` exits immediately with **`AZURE_AI_PROJECT_ENDPOINT is not set`** | It couldn't find a filled `.env`. The script looks for a **repo-root `.env`** first, then **`src/.env`**. If yours lives elsewhere, point at it explicitly — PowerShell: `.\deploy\mcp-server\deploy.ps1 -EnvFile .\src\.env`; bash: `ENV_FILE=src/.env bash deploy/mcp-server/deploy.sh` — or copy it to the repo root (`Copy-Item .\src\.env .\.env`). Then confirm the file has a real `AZURE_AI_PROJECT_ENDPOINT=https://…` value (not blank/placeholder). | +| `deploy.sh` fails / `az containerapp up` errors | Ensure `az` ≥ 2.53 and the **containerapp** extension (`az extension add -n containerapp`), you're logged in (`az login`) and on the lab subscription (`az account set -s `), and you're running it from the **repo root** (the script builds from the `src/` context — `src/Dockerfile`, `src/requirements.txt`, app code). The script also checks the `Microsoft.App`/`Microsoft.OperationalInsights` providers are registered — in a provisioned lab they already are, so if you see `az provider register … AuthorizationFailed` on an older download it's **harmless** (that's a subscription-scope action you don't need — the current script skips it when already registered). | +| `az containerapp up` → **`ManagedEnvironmentNotProvisioned`** (env `clm-mcp-env` "has not been provisioned successfully"), then `clm-mcp does not exist` / empty `principalId` | A **prior failed run** (e.g. the CLI-2.86.0 crash above, before it was upgraded) left the Container Apps environment `clm-mcp-env` stuck in a failed state, and `az containerapp up` reuses it by name. **Fix:** delete it and re-run — `az containerapp env delete -n clm-mcp-env -g --yes` then re-run the deploy (the current script auto-deletes a non-`Succeeded` env for you). If the *fresh* env also fails, it's usually **region capacity** — retry, or set `LOCATION`/`$env:LOCATION` to another region (e.g. `westeurope`). | +| `az role assignment create` → **`Role 'Azure AI User' doesn't exist.`** (last step, after the app is already created) | The built-in role **"Azure AI User" was renamed to "Foundry User"**, so the old *display name* no longer resolves — but the **role-definition GUID is unchanged**. Assign by GUID instead: `az role assignment create --assignee-object-id --assignee-principal-type ServicePrincipal --role 53ca6127-db72-4b80-b1b0-d745d6d5456d --scope `. The current script already does this (GUID first, then `Cognitive Services User` / `Cognitive Services OpenAI User` as fallbacks). Your app is **already deployed** at this point — this only grants its identity model access. | +| `az containerapp up` crashes with **`'NoneType' object has no attribute 'linux'`** (in `queue_acr_build`), then a cascade of `containerapp 'clm-mcp' does not exist` and `--assignee-object-id: expected one argument` | A **known Azure CLI bug** in **core 2.86.0** — its cloud image-build path resolves the OS/Architecture SDK models from the wrong package ([Azure/azure-cli#33369](https://github.com/Azure/azure-cli/issues/33369)). It is **not your lab or the script**. **Fix on your machine:** `az upgrade` (you need core **≥ 2.87.0**; the fix ships there) then `az extension update -n containerapp`, and re-run — the half-created ACR + Container Apps environment are reused. The follow-on `does not exist` / `expected one argument` lines are just cascade from the failed build and vanish once it succeeds. | | Foundry agent shows the MCP tool but tool calls fail / time out | Check the app is reachable: open `https://.azurecontainerapps.io/mcp` — it should respond (405/JSON, not a connection error). Confirm ingress is **external** (`az containerapp ingress show`), the URL **ends with `/mcp`**, and the Server URL in Foundry matches exactly. | -| Remote tools return `401/403` / "credential" errors from Foundry | The **container's managed identity** lacks a data-plane role on your Foundry account. Re-run the role step in `deploy.sh` (or assign **Azure AI User** on `FOUNDRY_ACCOUNT_ID`), then wait ~1 min for propagation. Verify with `az containerapp identity show` + `az role assignment list --assignee `. | +| Remote tools return `401/403` / "credential" errors from Foundry | The **container's managed identity** lacks a data-plane role on your Foundry account. Re-run the role step in `deploy.ps1`/`deploy.sh` (or assign the **Azure AI User / Foundry User** role — GUID `53ca6127-db72-4b80-b1b0-d745d6d5456d` — on `FOUNDRY_ACCOUNT_ID`), then wait ~1 min for propagation. Verify with `az containerapp identity show` + `az role assignment list --assignee `. | | `CLM_MCP_URL` run: connection refused / hangs | Confirm the app is running (`az containerapp show --query properties.runningStatus`) and the URL includes `/mcp`. If you added a key, set `CLM_MCP_KEY` too. Unset `CLM_MCP_URL` to fall back to the local stdio server. | ## 🔗 How this fits diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/challenges/challenge-05.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/challenges/challenge-05.md index 02f565588..8d03827fa 100644 --- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/challenges/challenge-05.md +++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/challenges/challenge-05.md @@ -37,7 +37,7 @@ This challenge is about **delivery** — taking the agent to where legal actuall | Service | What it is | Why it's here | |---|---|---| | **M365 Copilot & Teams** (channels) | The surfaces you publish your CLM agent (`clm-contract-agent`) to. From the Foundry portal you add the "Teams and Microsoft 365 Copilot" channel — **no conversational bot code** — and users chat with your grounded agent where they already work. | An agent legal never opens isn't used; meeting people in Teams is what makes it real. → [Agent Framework](https://learn.microsoft.com/agent-framework/overview/agent-framework-overview) | -| **Azure Bot Service** | The managed bot hosting + channel layer. Publishing a Foundry agent **auto-provisions an Azure Bot** that brokers messages between the channel and your agent (connectivity, auth, routing). First run: `az provider register --namespace Microsoft.BotService`. | The plumbing that connects Teams to your agent. | +| **Azure Bot Service** | The managed bot hosting + channel layer. Publishing a Foundry agent **auto-provisions an Azure Bot** that brokers messages between the channel and your agent (connectivity, auth, routing). The lab **pre-registers** its `Microsoft.BotService` provider for you (once per subscription), so you normally do nothing here. | The plumbing that connects Teams to your agent. | ## ✅ Tasks @@ -59,8 +59,16 @@ In the **[Foundry portal](https://ai.azure.com)**, open the **`clm-contract-agen Open the agent and select **Publish** (top of the page) → **Publish to Teams and Microsoft 365 Copilot** → **Continue**. This provisions an **Azure Bot Service** behind the scenes — no bot code. -> First time only: `az provider register --namespace Microsoft.BotService` (so the portal can create -> the bot). Leave the **Azure bot services** dropdown on *auto* — let Foundry provision a fresh, +> **Provider registration:** publishing needs the `Microsoft.BotService` resource provider +> registered on the **subscription**. The lab's shared deploy hook +> (`labautomation/shared-deploy-lab.ps1`) registers it **once per subscription, before any lab +> starts**, so this is already done for you. If the dropdown still errors with +> **`MissingSubscriptionRegistration` / `Microsoft.BotService`**, a **subscription Owner** +> registers it once — it's subscription-wide, so it unblocks every lab: +> `az provider register --namespace Microsoft.BotService`. In an RG-scoped lab you (RG-Owner) +> can't run this yourself — ask your coach / lab admin. +> +> Leave the **Azure bot services** dropdown on *auto* — let Foundry provision a fresh, > properly-wired bot. Re-publishing? **Delete any stale Azure Bot** from earlier attempts first, or > you'll hit an **App ID collision**. @@ -128,6 +136,7 @@ same grounded, cited answers you saw in the terminal in Challenges 2 & 4. | Symptom | Fix | |---------|-----| | Publish option missing | Ensure `Microsoft.BotService` is registered and you have rights to create an Azure Bot. | +| **`Azure bot services`** dropdown → **`MissingSubscriptionRegistration` / "subscription is not registered to use namespace 'Microsoft.BotService'"** (409) | The `Microsoft.BotService` provider isn't registered on the **subscription**. The lab's shared deploy hook (`shared-deploy-lab.ps1`) registers it once per subscription before the labs, but if it's still unregistered a **subscription Owner** runs it once (subscription-wide, unblocks all labs): `az provider register --namespace Microsoft.BotService`. In an RG-scoped lab you (RG-Owner) **can't** register it yourself — ask your coach / lab admin. Once it shows `Registered` (~1–2 min), reopen the publish dialog. | | Bot responds in Teams but not Copilot | Confirm the app is approved for M365 Copilot and the manifest scopes include it. | ## 🔗 How this fits diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/README.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/README.md index 08c8f4ad2..6903fde88 100644 --- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/README.md +++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/README.md @@ -21,13 +21,13 @@ use) for `AZURE_AI_PROJECT_ENDPOINT` + `MODEL_*`, then **auto-discovers** the resource group, Foundry account id and region from that endpoint. Usually just: ```bash -bash deploy/mcp-server/deploy.sh # Codespaces / Linux / macOS / Azure Cloud Shell +bash deploy/mcp-server/deploy.sh # Linux / macOS / WSL / Azure Cloud Shell ``` ```powershell -./deploy/mcp-server/deploy.ps1 # Windows PowerShell ONLY — not for Codespaces/bash +./deploy/mcp-server/deploy.ps1 # Windows PowerShell ONLY — not for bash ``` -> **Codespaces / Cloud Shell = a Linux `bash` shell.** Use the `bash deploy/mcp-server/deploy.sh` +> **Cloud Shell / WSL = a Linux `bash` shell.** Use the `bash deploy/mcp-server/deploy.sh` > line above — the `.ps1` is Windows PowerShell only and, run in bash, fails with > `bash: ./deploy/mcp-server/deploy.ps1: Permission denied`. diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/deploy.ps1 b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/deploy.ps1 index 7e6374334..56e3a0213 100644 --- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/deploy.ps1 +++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/deploy.ps1 @@ -25,10 +25,19 @@ param( [string]$Location = $env:LOCATION, [string]$ProjectEndpoint = $env:AZURE_AI_PROJECT_ENDPOINT, [string]$FoundryAccountId = $env:FOUNDRY_ACCOUNT_ID, - [string]$EnvFile = $(if ($env:ENV_FILE) { $env:ENV_FILE } else { ".env" }) + [string]$EnvFile = $env:ENV_FILE ) $ErrorActionPreference = "Stop" +# Zero-config: default to the repo-root .env, but fall back to src/.env - many +# participants copy src/.env.example in place and keep their .env there (it's the +# CWD their agents run from). An explicit -EnvFile / $env:ENV_FILE always wins. +if (-not $EnvFile) { + if (Test-Path ".env") { $EnvFile = ".env" } + elseif (Test-Path "src/.env") { $EnvFile = "src/.env" } + else { $EnvFile = ".env" } +} + # ---- Load repo-root .env (only fills values you haven't already set) --------- $envMap = @{} if (Test-Path $EnvFile) { @@ -49,7 +58,7 @@ function Get-Val([string]$explicit, [string]$key, [string]$default) { $AppName = if ($AppName) { $AppName } else { "clm-mcp" } $ProjectEndpoint = Get-Val $ProjectEndpoint "AZURE_AI_PROJECT_ENDPOINT" "" -if (-not $ProjectEndpoint) { throw "set AZURE_AI_PROJECT_ENDPOINT (in .env or as -ProjectEndpoint)" } +if (-not $ProjectEndpoint) { throw "AZURE_AI_PROJECT_ENDPOINT is not set. Fill it in your .env (repo-root .env or src/.env), pass -EnvFile (e.g. -EnvFile .\src\.env), or pass -ProjectEndpoint . See Challenge 1, Step 3." } $ModelOrchestrator = Get-Val "" "MODEL_ORCHESTRATOR" "gpt-5.4" $ModelDrafting = Get-Val "" "MODEL_DRAFTING" "gpt-5.4" $ModelClauseRisk = Get-Val "" "MODEL_CLAUSE_RISK" "gpt-5.6-sol" @@ -86,10 +95,33 @@ Write-Host " project endpoint = $ProjectEndpoint" Write-Host "==> Ensuring the containerapp CLI extension + providers are ready" az extension add --name containerapp --upgrade --only-show-errors 2>$null | Out-Null -az provider register --namespace Microsoft.App --wait 2>$null | Out-Null -az provider register --namespace Microsoft.OperationalInsights --wait 2>$null | Out-Null +# Provider registration is a SUBSCRIPTION-scope action. In a resource-group lab you +# only own the RG, so re-registering fails with AuthorizationFailed — but the platform +# already registered these when it provisioned the lab. Check first; only try to register +# if actually needed. EAP is relaxed here because PowerShell 5.1 treats az's stderr as a +# terminating error under $ErrorActionPreference='Stop' — these calls must stay best-effort. +$eapSaved = $ErrorActionPreference +$ErrorActionPreference = "Continue" +foreach ($ns in @("Microsoft.App", "Microsoft.OperationalInsights")) { + $state = az provider show --namespace $ns --query registrationState -o tsv 2>$null + if ($state -eq "Registered") { Write-Host " $ns already registered"; continue } + Write-Host " registering $ns (needs subscription rights; skipping if not allowed)" + az provider register --namespace $ns --only-show-errors 2>$null | Out-Null +} +$ErrorActionPreference = $eapSaved Write-Host "==> Building + deploying '$AppName' to Azure Container Apps (image builds in the cloud)" +# Self-heal: az containerapp up auto-names the env '-env' and REUSES it by name. +# A prior crashed/aborted run can leave that env stuck in a non-Succeeded state, which +# then fails every re-run with 'ManagedEnvironmentNotProvisioned'. Delete a bad one first. +$EnvName = "$AppName-env" +$envState = az containerapp env show -n $EnvName -g $ResourceGroup --query "properties.provisioningState" -o tsv 2>$null +if ($envState -and $envState -ne "Succeeded") { + Write-Host "!! Container Apps environment '$EnvName' is '$envState' (a prior failed run) - deleting it so it can be recreated cleanly" -ForegroundColor Yellow + az containerapp env delete -n $EnvName -g $ResourceGroup --yes 2>$null | Out-Null +} +$eapUp = $ErrorActionPreference +$ErrorActionPreference = 'Continue' # handle a cloud-build failure with a clear message, not a raw NativeCommandError az containerapp up ` --name $AppName ` --resource-group $ResourceGroup ` @@ -104,28 +136,50 @@ az containerapp up ` "MODEL_CLAUSE_RISK=$ModelClauseRisk" ` "MCP_TRANSPORT=streamable-http" ` "MCP_PORT=8000" +$upExit = $LASTEXITCODE +$ErrorActionPreference = $eapUp +if ($upExit -ne 0) { + Write-Host "" + Write-Host "!! 'az containerapp up' failed (exit $upExit) - stopping before the identity/role steps." -ForegroundColor Red + Write-Host " Read the FIRST error above; the two common ones are:" -ForegroundColor Red + Write-Host " - \"'NoneType' object has no attribute 'linux'\" (in queue_acr_build): Azure CLI 2.86.0 bug" -ForegroundColor Red + Write-Host " (Azure/azure-cli#33369). Fix: 'az upgrade' (need >= 2.87.0), reopen the shell, re-run." -ForegroundColor Red + Write-Host " - 'ManagedEnvironmentNotProvisioned': the env '$EnvName' is stuck from a prior run." -ForegroundColor Red + Write-Host " Fix: az containerapp env delete -n $EnvName -g $ResourceGroup --yes , then re-run." -ForegroundColor Red + Write-Host " Re-running is safe - the ACR image + a healthy env are reused." -ForegroundColor Red + exit 1 +} Write-Host "==> Enabling the app's system-assigned managed identity" az containerapp identity assign --name $AppName --resource-group $ResourceGroup --system-assigned | Out-Null $PrincipalId = az containerapp show -n $AppName -g $ResourceGroup --query identity.principalId -o tsv Write-Host " principalId = $PrincipalId" +if (-not $PrincipalId) { + Write-Host "!! Could not read the app's managed-identity principalId - the app may not have been created." -ForegroundColor Red + Write-Host " Skipping the role assignment. Re-run the deploy once the app exists." -ForegroundColor Red + exit 1 +} if ($FoundryAccountId) { Write-Host "==> Granting the identity access to your Foundry models" + # 53ca6127... is the built-in role "Azure AI User" (recently RENAMED to "Foundry User"). + # Assign it by role-definition GUID, because the display name "Azure AI User" no longer + # resolves ('az ... --role "Azure AI User"' fails with "Role ... doesn't exist"). Fall + # back to the older inference roles by name if the GUID ever isn't present in the tenant. $ok = $false - foreach ($role in @("Azure AI User", "Cognitive Services User")) { - az role assignment create --assignee-object-id $PrincipalId ` + foreach ($role in @("53ca6127-db72-4b80-b1b0-d745d6d5456d", "Cognitive Services User", "Cognitive Services OpenAI User")) { + $out = az role assignment create --assignee-object-id $PrincipalId ` --assignee-principal-type ServicePrincipal ` - --role $role --scope $FoundryAccountId 2>$null | Out-Null - if ($LASTEXITCODE -eq 0) { $ok = $true; break } + --role $role --scope $FoundryAccountId 2>&1 + if ($LASTEXITCODE -eq 0 -or ($out -match "already exists")) { $ok = $true; break } } if ($ok) { Write-Host " role assigned (identity propagation can take ~1 minute)" } - else { Write-Host "!! role assignment failed - grant 'Azure AI User' on $FoundryAccountId to $PrincipalId yourself" } + else { Write-Host "!! role assignment failed - grant 'Azure AI User' (role id 53ca6127-db72-4b80-b1b0-d745d6d5456d) on $FoundryAccountId to $PrincipalId yourself" -ForegroundColor Yellow } } else { Write-Host "!! FOUNDRY_ACCOUNT_ID not found - grant a data-plane role to the identity yourself:" Write-Host " az role assignment create --assignee-object-id $PrincipalId ``" Write-Host " --assignee-principal-type ServicePrincipal ``" - Write-Host " --role 'Azure AI User' --scope " + Write-Host " --role 53ca6127-db72-4b80-b1b0-d745d6d5456d --scope " } $Fqdn = az containerapp show -n $AppName -g $ResourceGroup --query properties.configuration.ingress.fqdn -o tsv diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/deploy.sh b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/deploy.sh index cd99c8f64..8474a1b88 100644 --- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/deploy.sh +++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/deploy/mcp-server/deploy.sh @@ -21,7 +21,14 @@ set -euo pipefail # ---- Load repo-root .env (only fills vars you haven't already set) ---------- -ENV_FILE="${ENV_FILE:-.env}" +# Zero-config: default to the repo-root .env, but fall back to src/.env - many +# participants copy src/.env.example in place and keep their .env there. An +# explicit ENV_FILE always wins. +if [[ -z "${ENV_FILE:-}" ]]; then + if [[ -f ".env" ]]; then ENV_FILE=".env" + elif [[ -f "src/.env" ]]; then ENV_FILE="src/.env" + else ENV_FILE=".env"; fi +fi if [[ -f "$ENV_FILE" ]]; then echo "==> Reading $ENV_FILE" while IFS= read -r line || [[ -n "$line" ]]; do @@ -37,7 +44,7 @@ fi # ---- Inputs (all overridable via env) --------------------------------------- APP_NAME="${APP_NAME:-clm-mcp}" -PROJECT_ENDPOINT="${AZURE_AI_PROJECT_ENDPOINT:?set AZURE_AI_PROJECT_ENDPOINT (in .env or env)}" +PROJECT_ENDPOINT="${AZURE_AI_PROJECT_ENDPOINT:?set AZURE_AI_PROJECT_ENDPOINT in your .env (repo-root .env or src/.env), via ENV_FILE=, or export it. See Challenge 1, Step 3}" MODEL_ORCHESTRATOR="${MODEL_ORCHESTRATOR:-gpt-5.4}" MODEL_DRAFTING="${MODEL_DRAFTING:-gpt-5.4}" MODEL_CLAUSE_RISK="${MODEL_CLAUSE_RISK:-gpt-5.6-sol}" @@ -77,10 +84,31 @@ echo " project endpoint = $PROJECT_ENDPOINT" echo "==> Ensuring the containerapp CLI extension + providers are ready" az extension add --name containerapp --upgrade --only-show-errors >/dev/null || true -az provider register --namespace Microsoft.App --wait >/dev/null || true -az provider register --namespace Microsoft.OperationalInsights --wait >/dev/null || true +# Provider registration is a SUBSCRIPTION-scope action. In a resource-group lab you +# only own the RG, so re-registering fails with AuthorizationFailed — but the platform +# already registered these when it provisioned the lab. Check first; only try to register +# if actually needed, and never let it abort the deploy. +for ns in Microsoft.App Microsoft.OperationalInsights; do + state=$(az provider show --namespace "$ns" --query registrationState -o tsv 2>/dev/null || true) + if [ "$state" = "Registered" ]; then + echo " $ns already registered" + else + echo " registering $ns (needs subscription rights; skipping if not allowed)" + az provider register --namespace "$ns" --only-show-errors >/dev/null 2>&1 || true + fi +done echo "==> Building + deploying '$APP_NAME' to Azure Container Apps (image builds in the cloud)" +# Self-heal: az containerapp up auto-names the env '-env' and REUSES it by name. +# A prior crashed/aborted run can leave that env stuck in a non-Succeeded state, which +# then fails every re-run with 'ManagedEnvironmentNotProvisioned'. Delete a bad one first. +ENV_NAME="${APP_NAME}-env" +env_state="$(az containerapp env show -n "$ENV_NAME" -g "$RESOURCE_GROUP" --query "properties.provisioningState" -o tsv 2>/dev/null || true)" +if [[ -n "$env_state" && "$env_state" != "Succeeded" ]]; then + echo "!! Container Apps environment '$ENV_NAME' is '$env_state' (a prior failed run) - deleting it so it can be recreated cleanly" >&2 + az containerapp env delete -n "$ENV_NAME" -g "$RESOURCE_GROUP" --yes >/dev/null 2>&1 || true +fi +set +e # handle a cloud-build failure with a clear message instead of a bare abort az containerapp up \ --name "$APP_NAME" \ --resource-group "$RESOURCE_GROUP" \ @@ -95,6 +123,19 @@ az containerapp up \ "MODEL_CLAUSE_RISK=$MODEL_CLAUSE_RISK" \ "MCP_TRANSPORT=streamable-http" \ "MCP_PORT=8000" +up_rc=$? +set -e +if [ "$up_rc" -ne 0 ]; then + echo "" >&2 + echo "!! 'az containerapp up' failed (exit $up_rc) - stopping before the identity/role steps." >&2 + echo " Read the FIRST error above; the two common ones are:" >&2 + echo " - \"'NoneType' object has no attribute 'linux'\" (in queue_acr_build): Azure CLI 2.86.0 bug" >&2 + echo " (Azure/azure-cli#33369). Fix: 'az upgrade' (need >= 2.87.0), reopen the shell, re-run." >&2 + echo " - 'ManagedEnvironmentNotProvisioned': the env '$ENV_NAME' is stuck from a prior run." >&2 + echo " Fix: az containerapp env delete -n $ENV_NAME -g $RESOURCE_GROUP --yes , then re-run." >&2 + echo " Re-running is safe - the ACR image + a healthy env are reused." >&2 + exit 1 +fi echo "==> Enabling the app's system-assigned managed identity" az containerapp identity assign \ @@ -102,21 +143,37 @@ az containerapp identity assign \ PRINCIPAL_ID="$(az containerapp show -n "$APP_NAME" -g "$RESOURCE_GROUP" \ --query identity.principalId -o tsv)" echo " principalId = $PRINCIPAL_ID" +if [[ -z "$PRINCIPAL_ID" ]]; then + echo "!! Could not read the app's managed-identity principalId - the app may not have been created." >&2 + echo " Skipping the role assignment. Re-run the deploy once the app exists." >&2 + exit 1 +fi if [[ -n "$FOUNDRY_ACCOUNT_ID" ]]; then echo "==> Granting the identity access to your Foundry models" - az role assignment create --assignee-object-id "$PRINCIPAL_ID" \ - --assignee-principal-type ServicePrincipal \ - --role "Azure AI User" --scope "$FOUNDRY_ACCOUNT_ID" >/dev/null \ - || az role assignment create --assignee-object-id "$PRINCIPAL_ID" \ - --assignee-principal-type ServicePrincipal \ - --role "Cognitive Services User" --scope "$FOUNDRY_ACCOUNT_ID" >/dev/null - echo " role assigned (identity propagation can take ~1 minute)" + # 53ca6127... is the built-in role "Azure AI User" (recently RENAMED to "Foundry User"). + # Assign by GUID because the display name "Azure AI User" no longer resolves. Fall back to + # the older inference roles by name. Wrapped in set +e so a failed try can't abort the run. + set +e + role_ok=0 + for role in "53ca6127-db72-4b80-b1b0-d745d6d5456d" "Cognitive Services User" "Cognitive Services OpenAI User"; do + out="$(az role assignment create --assignee-object-id "$PRINCIPAL_ID" \ + --assignee-principal-type ServicePrincipal \ + --role "$role" --scope "$FOUNDRY_ACCOUNT_ID" 2>&1)" + rc=$? + if [[ "$rc" -eq 0 || "$out" == *"already exists"* ]]; then role_ok=1; break; fi + done + set -e + if [[ "$role_ok" -eq 1 ]]; then + echo " role assigned (identity propagation can take ~1 minute)" + else + echo "!! role assignment failed - grant 'Azure AI User' (role id 53ca6127-db72-4b80-b1b0-d745d6d5456d) on $FOUNDRY_ACCOUNT_ID to $PRINCIPAL_ID yourself" >&2 + fi else echo "!! FOUNDRY_ACCOUNT_ID not set — grant a data-plane role to the identity yourself:" echo " az role assignment create --assignee-object-id $PRINCIPAL_ID \\" echo " --assignee-principal-type ServicePrincipal \\" - echo " --role 'Azure AI User' --scope " + echo " --role 53ca6127-db72-4b80-b1b0-d745d6d5456d --scope " fi FQDN="$(az containerapp show -n "$APP_NAME" -g "$RESOURCE_GROUP" \ diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/docs/coach-guide.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/docs/coach-guide.md index cb7a21364..fddd31e89 100644 --- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/docs/coach-guide.md +++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/docs/coach-guide.md @@ -34,7 +34,7 @@ tips. Participants never see this file; it's for the people running the room. - [ ] Team has an **Azure subscription** with Owner/Contributor + rights to create role assignments. - [ ] Region confirmed to offer all three model deployments. -- [ ] They can **open the repo in a Codespace** (no fork needed) or run the devcontainer locally. +- [ ] They can **clone the repo and create a Python virtual environment** (no fork needed) — `python -m venv .venv`, activate it, then `pip install -r src/requirements.txt`. - [ ] `Microsoft.BotService` provider registered (needed in Ch5): `az provider register --namespace Microsoft.BotService`. --- @@ -91,7 +91,7 @@ of the challenge, what "done" looks like, where teams get stuck, and the hint to or adjust the version in `deploy.sh`. **This is the single most common Ch1 blocker.** - *`account project create` unavailable* → the CLI project command is preview. Create the project in the **portal**, then set `AZURE_AI_PROJECT_ENDPOINT` in `.env` by hand. - - *`az login` in Codespaces* → must use `az login --use-device-code`. + - *`az login` with no browser (headless / remote terminal)* → use `az login --use-device-code`. - *RBAC not propagated* → role assignments can take a few minutes; a retry usually fixes "auth" errors right after `azd up`. - **Coach hint if stuck on region:** "Open the Foundry model catalog filtered to *your* subscription and diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/images/challenge-01/steps/02-create-codespace.png b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/images/challenge-01/steps/02-create-codespace.png deleted file mode 100644 index 49f5ec7fe..000000000 Binary files a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/images/challenge-01/steps/02-create-codespace.png and /dev/null differ diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/images/challenge-01/steps/03-codespace-ready.png b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/images/challenge-01/steps/03-codespace-ready.png deleted file mode 100644 index 37e34f3df..000000000 Binary files a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/images/challenge-01/steps/03-codespace-ready.png and /dev/null differ diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/images/challenge-01/steps/endpoint-azure-search.png b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/images/challenge-01/steps/endpoint-azure-search.png new file mode 100644 index 000000000..f9a6aeee2 Binary files /dev/null and b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/images/challenge-01/steps/endpoint-azure-search.png differ diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/images/challenge-01/steps/endpoint-foundry-project.png b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/images/challenge-01/steps/endpoint-foundry-project.png new file mode 100644 index 000000000..a3e074b5a Binary files /dev/null and b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/images/challenge-01/steps/endpoint-foundry-project.png differ diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/README.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/README.md index 11c961e5e..3938509fb 100644 --- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/README.md +++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/README.md @@ -18,7 +18,7 @@ team's environment by invoking **`deploy-lab.ps1`** with a fixed parameter contr | [`lab-defaults.json`](lab-defaults.json) | Platform config (`$schema`-validated): deployment type, region priority, per-user daily cost estimate. | `deploy-lab.ps1` is the **platform** path; `deploy.sh` / `deploy.ps1` below remain the -**local / Codespaces** path (they autofill `.env` via `az` after `az login`). Both provision the +**local** path (they autofill `.env` via `az` after `az login`). Both provision the same resources from `infra/`. **What `deploy-lab.ps1` does beyond a single deployment:** @@ -45,7 +45,7 @@ three GPT model deployments (`gpt-5.4`, `gpt-5.6-sol`, `gpt-5.4-nano`), Azure AI ## Scripts -Provisioning scripts in **this folder** (local / Codespaces path): +Provisioning scripts in **this folder** (local path): | Path | Role | |------|------| diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/deploy-lab.ps1 b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/deploy-lab.ps1 index fbb11b865..6dd72c140 100644 --- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/deploy-lab.ps1 +++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/deploy-lab.ps1 @@ -26,7 +26,7 @@ - Console output: [INFO]/[OK]/[WARN] progress plus @{ HackboxCredential = ... } records that surface every endpoint / model name to the team dashboard. - deploy.ps1 / deploy.sh remain the local/Codespaces path; this script is the platform path. + deploy.ps1 / deploy.sh remain the local path; this script is the platform path. #> param( [Parameter(Mandatory = $true)] @@ -108,6 +108,14 @@ if ($AllowedEntraUserIds.Count -eq 0) { Write-Host "[WARN] -AllowedEntraUserIds (comma-separate ids for a team lab)." } +# --- Resource providers ----------------------------------------------------- +# Subscription-scoped resource-provider registration (e.g. 'Microsoft.BotService' for the +# Challenge 5 Teams / M365 publish) is handled ONCE PER SUBSCRIPTION by shared-deploy-lab.ps1, +# which the platform runs before this per-participant fan-out. It does NOT belong here: in a +# 'resourcegroup' lab this script runs with only subscription-Reader (RG-Owner), so it could not +# register a provider anyway, and running it in every parallel lab job would just race. See +# labautomation/shared-deploy-lab.ps1. + $deployOutputs = $null $effectiveResourceGroup = $null $effectiveLocation = $null diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/deploy.ps1 b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/deploy.ps1 index deda71bb7..f5e585f80 100644 --- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/deploy.ps1 +++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/deploy.ps1 @@ -2,7 +2,7 @@ Challenge 1 — provision the Foundry CLM microhack resources and write .env (Windows). Usage: ./labautomation/deploy.ps1 [-WithSql] [-WithBing] Requires: az CLI (az login), rights to deploy GPT models. - The bash script (labautomation/deploy.sh) is the primary path for Codespaces. + The bash script (labautomation/deploy.sh) is the primary path for Linux / macOS / WSL. #> param([switch]$WithSql, [switch]$WithBing) $ErrorActionPreference = "Stop" diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/lab-defaults.json b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/lab-defaults.json index 866701b15..8a5534c49 100644 --- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/lab-defaults.json +++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/lab-defaults.json @@ -4,5 +4,6 @@ "deploymentType": "resourcegroup", "labsPerSubscription": 8, "preferredLocation": "swedencentral, norwayeast, spaincentral", - "estimatedDailyCostsUsd": 15.0 + "estimatedDailyCostsUsd": 15.0, + "estimatedSharedDeploymentDailyCostsUsd": 0.0 } diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/shared-deploy-lab.ps1 b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/shared-deploy-lab.ps1 new file mode 100644 index 000000000..726e29658 --- /dev/null +++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/labautomation/shared-deploy-lab.ps1 @@ -0,0 +1,76 @@ +<# + shared-deploy-lab.ps1 — EMEA MicroHack once-per-subscription hook for the Foundry CLM microhack. + + The microsoft/MicroHack platform runs THIS script exactly once per Azure subscription, in parallel + across subscriptions, BEFORE the per-participant deploy-lab.ps1 fan-out starts. It is the sanctioned + home for one-off subscription preparation that the many parallel deploy-lab.ps1 runs would otherwise + race on — the platform README names "registering resource providers" as the canonical example. + + Why the CLM hack needs it: + Challenge 5 publishes the Foundry agent to Teams / M365 Copilot, which auto-creates an Azure Bot. + That requires the 'Microsoft.BotService' resource provider registered at SUBSCRIPTION scope. The + platform pre-registers Microsoft.App / Microsoft.OperationalInsights but NOT Microsoft.BotService, + and in a 'resourcegroup' lab the participant holds only subscription-Reader (+ RG-Owner), so they + cannot self-register it (registration is a subscription-scope write — az provider register fails + with AuthorizationFailed for a Reader). Registering it here — once per subscription, ahead of the + labs — makes Challenge 5 "just work" for every participant WITHOUT granting anyone subscription + Owner. (Switching lab-defaults.json to 'resourcegroup-with-subscriptionowner' would instead make + every one of the up-to-8 participants sharing a subscription an Owner of it — they could then see + and delete each other's resource groups. This hook keeps that isolation intact.) + + Platform contract / guarantees (see labautomation/README.md): + - Picked up automatically by file name; parameters below are the shared-hook contract. + - Az context is already set to $SubscriptionId -> do NOT Connect-AzAccount. + - Az.Accounts / Az.Resources are already imported; the hook runs with subscription-level rights + sufficient to register providers / deploy shared resources. + - Runs to completion before the FIRST deploy-lab.ps1 in the subscription starts. + - If this hook THROWS, the platform runs NO deploy-lab.ps1 for the subscription and marks the + deployment failed. Registering Microsoft.BotService is therefore best-effort: a failure here + only affects Challenge 5, so it must WARN and continue — it must never abort provisioning of + Challenges 1-4 for the whole subscription. +#> +param( + [Parameter(Mandatory = $true)] + [string]$SubscriptionId, + + [Parameter(Mandatory = $true)] + [string[]]$PreferredLocation = @(), + + [Parameter(Mandatory = $false)] + [string[]]$AllowedEntraUserIds = @() +) + +$ErrorActionPreference = 'Stop' + +# Resource providers the CLM labs need that the platform does NOT pre-register. +# Microsoft.BotService — Challenge 5 "Publish to Teams / M365 Copilot" auto-creates an Azure Bot. +# Add more here if a future challenge needs another subscription-scoped provider. +$requiredProviders = @( + 'Microsoft.BotService' +) + +foreach ($provider in $requiredProviders) { + try { + $state = (Get-AzResourceProvider -ProviderNamespace $provider -ErrorAction SilentlyContinue | + Select-Object -First 1).RegistrationState + + if ($state -eq 'Registered') { + Write-Host "[OK] [$SubscriptionId] Resource provider '$provider' already registered." + continue + } + + Write-Host "[INFO] [$SubscriptionId] Registering resource provider '$provider' (state: '$state')..." + Register-AzResourceProvider -ProviderNamespace $provider -ErrorAction Stop | Out-Null + Write-Host "[OK] [$SubscriptionId] Registration submitted for '$provider' (async, idempotent, subscription-wide)." + } + catch { + # Best-effort: NEVER abort the whole subscription's provisioning over one provider registration. + # A missing Microsoft.BotService only blocks Challenge 5's Teams publish, not Challenges 1-4. + Write-Host "[WARN] [$SubscriptionId] Could not register '$provider': $_" + Write-Host "[WARN] [$SubscriptionId] Challenge 5 'Publish to Teams' may fail with MissingSubscriptionRegistration" + Write-Host "[WARN] [$SubscriptionId] until a subscription Owner runs once:" + Write-Host "[WARN] [$SubscriptionId] az provider register --namespace $provider --subscription $SubscriptionId" + } +} + +Write-Host "[OK] [$SubscriptionId] Shared subscription preparation complete (CLM microhack)." diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/README.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/README.md index fa6edc843..f134f7cbc 100644 --- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/README.md +++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/README.md @@ -1,7 +1,7 @@ # src — developer guide All the source code the participant runs during the microhack lives here. It runs -unchanged in a Codespace / devcontainer or locally. Shared config and Foundry client +unchanged locally inside a Python virtual environment. Shared config and Foundry client helpers live in [`clm_common/`](clm_common/); every entry-point script adds `src/` (and `src/agents/`) to `sys.path`, so run them from the **repo root**. diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/clm_common/__init__.py b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/clm_common/__init__.py index 28b29fff3..42e9bf8f1 100644 --- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/clm_common/__init__.py +++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/clm_common/__init__.py @@ -1,7 +1,7 @@ """Shared helpers for the Foundry CLM microhack (importable as `clm_common`).""" # Ensure UTF-8 stdout/stderr so emoji status markers (✓ ✅ 🔴) print on any -# console (Windows cp1252 included). Harmless on Linux/Codespaces where it's +# console (Windows cp1252 included). Harmless on Linux/macOS where it's # already UTF-8. Guarded so it never breaks import. import sys as _sys diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/scripts/make_step_placeholders.py b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/scripts/make_step_placeholders.py index b2dc3d2d8..d11b9523c 100644 --- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/scripts/make_step_placeholders.py +++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/src/scripts/make_step_placeholders.py @@ -22,10 +22,6 @@ # ---- Challenge 1 · Setup ------------------------------------------------ ("challenge-0", "01-fork", "GitHub · Fork the repo", "The GitHub 'Create a new fork' page for glejdis/microhack-aiagents with the green 'Create fork' button."), - ("challenge-0", "02-create-codespace", "GitHub · Create Codespace", - "Code button → Codespaces tab → 'Create codespace on main' green button."), - ("challenge-0", "03-codespace-ready", "Codespace · Ready", - "The VS Code-in-browser Codespace with a terminal open and 'pip install -r src/requirements.txt' finished."), ("challenge-0", "04-az-login-device", "Azure · Device-code login", "The https://microsoft.com/devicelogin page where you paste the code printed by 'az login --use-device-code'."), ("challenge-0", "05-azd-up-prompts", "azd up · Prompts", diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/walkthrough/challenge-01/solution-01.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/walkthrough/challenge-01/solution-01.md index f38d5040c..1449e8fa6 100644 --- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/walkthrough/challenge-01/solution-01.md +++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/walkthrough/challenge-01/solution-01.md @@ -8,7 +8,7 @@ contract corpus the later challenges ground on. ## Expected end state -- Codespace (or local devcontainer) built and dependencies installed. +- Repo cloned and opened in VS Code, a Python virtual environment (`.venv`) created, and dependencies from `src/requirements.txt` installed. - `az login --use-device-code` completed and the target subscription selected. - **MicroHack event:** resources are **already provisioned** — you point `.env` at your **lab-dashboard** values. **Self-hosting:** one `azd up` (Bicep in @@ -26,16 +26,19 @@ contract corpus the later challenges ground on. ## 🛠️ Task-by-task walkthrough -### Task 1 · Open the Codespace -No fork — the code lives in this repo. **`< > Code` ▸ Codespaces ▸ Create codespace on `main`**; the devcontainer builds and `pip install -r src/requirements.txt` runs automatically. *(Local alt: `git clone` then **Reopen in Container**.)* - -> 📸 **Screenshot slot:** creating the **Codespace**. -> -> Screenshot slot: create a Codespace +### Task 1 · Clone the project and set up your environment +No fork for the main hack — the code lives in this repo. Clone it, open it in **VS Code**, then in a terminal at the repo root create a virtual environment and install the dependencies: +```bash +python -m venv .venv +source .venv/bin/activate # Windows (PowerShell): .venv\Scripts\Activate.ps1 +python -m pip install --upgrade pip +pip install -r src/requirements.txt # prompt should now show (.venv) +``` +Then **Command Palette ▸ Python: Select Interpreter ▸ `.venv`** so the editor, terminal, and later scripts share one environment. The repo-root `.venv` is git-ignored and is where the Ch2 eval / Ch5 red-team scripts expect it; a venv also avoids the `externally-managed-environment` error on Python 3.12+. *(Challenge 6's CI bonus is the one exception — it runs in GitHub Actions and needs your own fork.)* ### Task 2 · Log in to Azure ```bash -az login --use-device-code # device code is required in Codespaces +az login --use-device-code # paste the code at microsoft.com/devicelogin az account set --subscription "" ``` @@ -57,6 +60,16 @@ cp src/.env.example .env | **AppInsightsConnectionString** | `APPLICATIONINSIGHTS_CONNECTION_STRING` | | **ModelOrchestrator / Drafting / ClauseRisk / Renewal** | `MODEL_ORCHESTRATOR` / `MODEL_DRAFTING` / `MODEL_CLAUSE_RISK` / `MODEL_RENEWAL` | +**Where to copy the two endpoints from** — if you'd rather read them straight off the portal than the dashboard, these are the two values you actually have to paste: + +**1 · `AZURE_AI_PROJECT_ENDPOINT`** — Foundry portal ([ai.azure.com](https://ai.azure.com)) → your **`clm-project`** → **Home**. Use the copy button on **Project endpoint** (`https://.services.ai.azure.com/api/projects/clm-project`) — **not** the *Azure OpenAI endpoint* next to it. + +Foundry portal Home with the Project endpoint field and its copy button highlighted + +**2 · `AZURE_SEARCH_ENDPOINT`** — Azure Portal → your resource group → the **Search service (Foundry IQ)** (`clmsearch****`) → **Overview** → copy the **Url** (`https://clmsearch****.search.windows.net`). + +Azure Portal Search service Overview with the Url endpoint highlighted + The model names + `AZURE_SEARCH_INDEX` (`clm-corpus`) / `AZURE_SEARCH_CONNECTION_NAME` (`clm-search`) already default in `src/.env.example`, so at minimum paste the two **endpoints** + the **App Insights** string. Leave `SHAREPOINT_*` and the Challenge 5 `MICROSOFT_APP_*` / `TEAMS_*` blank for now.
@@ -224,6 +237,6 @@ Smoke test: ✅ PASS | Symptom | Cause / fix | |---------|-------------| | A model isn't offered in your region | *(Self-host only — provisioned labs don't deploy.)* Pick a region with `gpt-5.4`, `gpt-5.6-sol`, and `gpt-5.4-nano`; verify in the Foundry model catalog. | -| `az login` fails / no browser in Codespaces | Use `az login --use-device-code` and paste the code at [microsoft.com/devicelogin](https://microsoft.com/devicelogin). | +| `az login` doesn't open a browser (headless / remote terminal) | Use `az login --use-device-code` and paste the code at [microsoft.com/devicelogin](https://microsoft.com/devicelogin). | | SharePoint (Path A): *"Tenant does not have a SPO license"* / **"Grant admin consent" greyed out** | You're not a tenant admin — expected. Use **Path B** (blank `SHAREPOINT_*`, run `python src/scripts/seed_corpus.py`); it builds the identical `clm-corpus` index. | | Corpus / index empty | Re-run `python src/scripts/seed_corpus.py` (idempotent); if a doc 403s, wait a minute for Search-role propagation and retry. | diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/walkthrough/challenge-04/solution-04.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/walkthrough/challenge-04/solution-04.md index 0ce50b6a5..a042c0f8c 100644 --- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/walkthrough/challenge-04/solution-04.md +++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/walkthrough/challenge-04/solution-04.md @@ -194,5 +194,10 @@ CLM_MCP_URL=https://…/mcp python src/orchestrator_mcp.py # drive the remote |---------|-------------| | `orchestrator_mcp.py` finds no tools / hangs | The stdio server failed to import — confirm `python src/mcp_server/server.py` starts standalone; run from repo root (`PYTHONPATH=src`). | | MCP server not listed in VS Code | VS Code reads `.vscode/mcp.json` only from the **root of the opened folder** — open the repo root (not `src/`) and confirm the file is at `/.vscode/mcp.json`. | -| Remote tools return `401/403` from Foundry | The Container App's **managed identity** needs a data-plane role (Azure AI User) on the Foundry account — `deploy.sh` sets it; allow ~1 min to propagate. | +| Remote tools return `401/403` from Foundry | The Container App's **managed identity** needs a data-plane role (**Azure AI User / Foundry User**, GUID `53ca6127-db72-4b80-b1b0-d745d6d5456d`) on the Foundry account — `deploy.ps1`/`deploy.sh` sets it; allow ~1 min to propagate. | +| `az provider register … AuthorizationFailed` (Microsoft.App) | Registering a provider is a **subscription-scope** action; in a resource-group lab you only own the RG. It's **harmless** — the platform already registered these providers, so the current script checks first and skips. On an older download, comment out the two `az provider register` lines and re-run, or ask a coach to register once at subscription scope. | +| `deploy.ps1`/`deploy.sh` exits with **`AZURE_AI_PROJECT_ENDPOINT is not set`** | It couldn't find your `.env`. The script checks repo-root `.env` then `src/.env`; if yours is elsewhere pass `-EnvFile .\src\.env` (PS) / `ENV_FILE=src/.env` (bash), or copy it to the repo root. Make sure the value isn't blank/placeholder. | +| `az containerapp up` → **`ManagedEnvironmentNotProvisioned`** (`clm-mcp-env` not provisioned), then `clm-mcp does not exist` / empty `principalId` | A prior failed run left the env `clm-mcp-env` stuck; `up` reuses it by name. Delete it and re-run: `az containerapp env delete -n clm-mcp-env -g --yes` (the current script auto-deletes a non-`Succeeded` env). If the fresh env also fails, it's usually region capacity — retry or set `LOCATION` to e.g. `westeurope`. | +| `az role assignment create` → **`Role 'Azure AI User' doesn't exist.`** (final step, app already created) | "Azure AI User" was **renamed to "Foundry User"** — the display name no longer resolves, but the GUID is stable. Assign by GUID: `--role 53ca6127-db72-4b80-b1b0-d745d6d5456d`. Current script does this automatically (falls back to `Cognitive Services User` / `Cognitive Services OpenAI User`). The app is already live; this just grants its identity model access. | +| `az containerapp up` → **`'NoneType' object has no attribute 'linux'`** (then `clm-mcp does not exist`, empty `principalId`, `--assignee-object-id: expected one argument`) | Azure CLI **core 2.86.0** regression in the cloud-build path ([#33369](https://github.com/Azure/azure-cli/issues/33369)); fixed in **2.87.0+**. Run **`az upgrade`** then `az extension update -n containerapp` and re-run — the partial ACR/env are reused and the follow-on errors are pure cascade that clear once the app is created. | | Foundry can't reach the server | Ingress must be **external** and the Server URL must end with `/mcp`; open `https://.azurecontainerapps.io/mcp` to confirm it responds. | diff --git a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/walkthrough/challenge-05/solution-05.md b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/walkthrough/challenge-05/solution-05.md index 162b50f0a..59d944ce1 100644 --- a/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/walkthrough/challenge-05/solution-05.md +++ b/03-Azure/01-04-AI/04_Agentic_Contract_Lifecycle_Management/walkthrough/challenge-05/solution-05.md @@ -18,7 +18,7 @@ Copilot & Teams** so people chat with it live where contract managers already wo In the **Foundry portal**, open the **`clm-contract-agent`** you published in **Challenge 4 (Task 4 Part B)** — the MCP-backed portal agent. *(The `clm-orchestrator` from Ch4 Task 2 was in-process and isn't in the portal.)* ### Task 2 · Publish to Teams & M365 Copilot -Select **Publish** → **Publish to Teams and Microsoft 365 Copilot** → **Continue** (provisions an **Azure Bot Service**; first time: `az provider register --namespace Microsoft.BotService`). Leave the **Azure bot services** dropdown on *auto*; delete any stale bot from earlier attempts to avoid an **App ID collision**. +Select **Publish** → **Publish to Teams and Microsoft 365 Copilot** → **Continue** (provisions an **Azure Bot Service**; needs the `Microsoft.BotService` provider registered on the **subscription** — the lab's shared deploy hook `shared-deploy-lab.ps1` does this once per subscription before the labs, else a **subscription Owner** runs `az provider register --namespace Microsoft.BotService` once). Leave the **Azure bot services** dropdown on *auto*; delete any stale bot from earlier attempts to avoid an **App ID collision**. Foundry portal: the Publish dropdown on clm-contract-agent with Teams & Microsoft 365 Copilot selected @@ -97,4 +97,4 @@ Select **Publish** → **Publish to Teams and Microsoft 365 Copilot** → **Cont | Published, but "nothing in Teams" | Publish with **Individual scope → Submit**, then look under **Apps → Your agents** (wait 1–2 min). If direct publish 400s, use **Download & customize** and sideload the zip. | | Can't sideload the Teams app | Many corp tenants block sideloading — use a coach-provided tenant. | | App ID collision on re-publish | Delete the stale Azure Bot from the earlier attempt, then re-publish (Foundry provisions a fresh one). | -| `Microsoft.BotService` errors | Register the provider: `az provider register --namespace Microsoft.BotService`. | +| `Microsoft.BotService` errors / **`MissingSubscriptionRegistration`** (409) on the bot dropdown | The provider isn't registered on the **subscription**. The lab's shared deploy hook (`shared-deploy-lab.ps1`) registers it once per subscription before the labs; if it's still missing, a **subscription Owner** runs once (subscription-wide): `az provider register --namespace Microsoft.BotService`. In an RG-scoped lab the participant (RG-Owner) can't self-register — ask the coach / lab admin. Wait for `Registered` (~1–2 min), then reopen the publish dialog. |