diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/azure.yaml b/03-Azure/01-04-AI/05_AI_Governance/challenges/azure.yaml deleted file mode 100644 index e76000e09..000000000 --- a/03-Azure/01-04-AI/05_AI_Governance/challenges/azure.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# Application name -name: ai-citadel-governance-hub -metadata: - template: ai-citadel-governance-hub -infra: - path: bicep/infra - provider: bicep -services: - usageProcessingLogicApp: - project: ./src/usage-ingestion-logicapp - language: js - host: function \ No newline at end of file diff --git a/03-Azure/01-04-AI/05_AI_Governance/README.md b/03-Azure/01-04-AI/06_AI_Governance/README.md similarity index 76% rename from 03-Azure/01-04-AI/05_AI_Governance/README.md rename to 03-Azure/01-04-AI/06_AI_Governance/README.md index d85a1d432..81cb1c59d 100644 --- a/03-Azure/01-04-AI/05_AI_Governance/README.md +++ b/03-Azure/01-04-AI/06_AI_Governance/README.md @@ -133,19 +133,53 @@ Each challenge follows the same anatomy, so you always know where to look: ## Repository layout ``` -05_AI_Governance/ - README.md This file - challenges/ Challenge instructions (challenge-01.md … challenge-09.md) - labautomation/ Platform entry point — provisions the Hub + Spoke per attendee - deploy-lab.ps1 Called once per attendee by the MicroHack platform +06_AI_Governance/ + README.md This file + challenges/ Participant-facing content + challenge-01.md … challenge-09.md The challenge instructions + finish.md Wrap-up and cleanup + azure.yaml azd project-root marker — makes the notebooks' + `azd env get-value` calls resolve. Not used to deploy. + workshop/ The 9 unchanged Citadel workshop notebooks this + MicroHack is built on (plus their agent/MCP sources) + shared/ utils.py / apimtools.py — imported by the notebooks + via `sys.path.insert(1, '../shared')` + bicep/ Per-challenge Bicep the notebooks deploy themselves + (`../bicep/infra/llm-backend-onboarding`, + `../bicep/infra/citadel-access-contracts`) + walkthrough/ Coach solutions — challenge-0N/solution-0N.md + labautomation/ Platform entry point — provisions the Hub + Spoke per attendee + deploy-lab.ps1 Called once per attendee by the MicroHack platform + shared-deploy-lab.ps1 Called once per subscription (resource-provider registration) + infra/resources.bicep The hub + spoke template, resource-group scoped run-local.ps1 Local-only dry-run wrapper (not used by the platform) setup-notebook-env.ps1 Bridges dashboard credentials into a local azd environment README.md Full provisioning + notebook-environment-setup reference - reference/ - ai-hub-gateway-solution-accelerator/ - workshop/ The 9 unchanged Citadel workshop notebooks this MicroHack is built on ``` +> [!NOTE] +> `challenges/workshop/`, `challenges/shared/`, `challenges/bicep/` and `challenges/azure.yaml` +> reproduce the upstream repository's own layout. That nesting is deliberate: the workshop +> notebooks are **unmodified**, and they resolve `../shared` and `../bicep/infra` relative to +> their own folder. Moving or flattening these breaks challenges 1, 2, 3, 5, 6, 8 and 9. + +## Solutions — spoiler warning + +Coach-facing walkthroughs live in [`walkthrough/`](walkthrough/), one folder per challenge. +Participants should attempt each challenge first. + +* [Solution 1 — Onboard a New LLM Backend](walkthrough/challenge-01/solution-01.md) +* [Solution 2 — Universal LLM API Across Every Model](walkthrough/challenge-02/solution-02.md) +* [Solution 3 — Access Contracts: Model RBAC & Capacity](walkthrough/challenge-03/solution-03.md) +* [Solution 4 — Drive Access Contracts from Agent Frameworks](walkthrough/challenge-04/solution-04.md) +* [Solution 5 — PII Anonymization, Blocking & Analytics](walkthrough/challenge-05/solution-05.md) +* [Solution 6 — Unified AI API Across Providers](walkthrough/challenge-06/solution-06.md) +* [Solution 7 — Build a Governed Hosted Agent with AGT](walkthrough/challenge-07/solution-07.md) +* [Solution 8 — Publish a Foundry Agent as an A2A Endpoint](walkthrough/challenge-08/solution-08.md) +* [Solution 9 — HR MCP via APIM](walkthrough/challenge-09/solution-09.md) + +➡️ [Finish](challenges/finish.md) + ## Contributors | Name | Role | diff --git a/03-Azure/01-04-AI/06_AI_Governance/challenges/azure.yaml b/03-Azure/01-04-AI/06_AI_Governance/challenges/azure.yaml new file mode 100644 index 000000000..30aefe793 --- /dev/null +++ b/03-Azure/01-04-AI/06_AI_Governance/challenges/azure.yaml @@ -0,0 +1,21 @@ +# Application name +# +# In the MicroHack this file is NOT used to deploy anything — the lab is provisioned by +# labautomation/deploy-lab.ps1 (Bicep, resource-group scoped), not by `azd up`. +# +# It is kept because it is azd's project-root marker: the unchanged workshop notebooks read +# their configuration with `azd env get-value `, and azd locates the current environment +# by searching upward from the working directory for the nearest azure.yaml — exactly as git +# searches for .git. Notebooks run from ./workshop/, so this file one level up is what makes +# those calls resolve. labautomation/setup-notebook-env.ps1 depends on it and fails fast if +# it is missing. Do not move or delete it. +name: ai-citadel-governance-hub +metadata: + template: ai-citadel-governance-hub +infra: + path: bicep/infra + provider: bicep +# NOTE: the upstream workshop declares a `services:` block here for the usage-ingestion +# Logic App (./src/usage-ingestion-logicapp). That source folder is not vendored into this +# MicroHack module, and the lab never runs `azd deploy`, so the block is intentionally +# omitted — leaving it in place made `azd` fail on a path that does not exist. diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/abbreviations.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/abbreviations.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/abbreviations.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/abbreviations.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/apim-gateway-upgrade/README.md b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/apim-gateway-upgrade/README.md similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/apim-gateway-upgrade/README.md rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/apim-gateway-upgrade/README.md diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/apim-gateway-upgrade/main.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/apim-gateway-upgrade/main.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/apim-gateway-upgrade/main.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/apim-gateway-upgrade/main.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/apim-gateway-upgrade/main.bicepparam b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/apim-gateway-upgrade/main.bicepparam similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/apim-gateway-upgrade/main.bicepparam rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/apim-gateway-upgrade/main.bicepparam diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/citadel-access-contracts/README.md b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/citadel-access-contracts/README.md similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/citadel-access-contracts/README.md rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/citadel-access-contracts/README.md diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/citadel-access-contracts/citadel-access-contracts-policy.md b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/citadel-access-contracts/citadel-access-contracts-policy.md similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/citadel-access-contracts/citadel-access-contracts-policy.md rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/citadel-access-contracts/citadel-access-contracts-policy.md diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/citadel-access-contracts/main.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/citadel-access-contracts/main.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/citadel-access-contracts/main.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/citadel-access-contracts/main.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/citadel-access-contracts/main.bicepparam b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/citadel-access-contracts/main.bicepparam similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/citadel-access-contracts/main.bicepparam rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/citadel-access-contracts/main.bicepparam diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/citadel-access-contracts/modules/apimOnboardService.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/citadel-access-contracts/modules/apimOnboardService.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/citadel-access-contracts/modules/apimOnboardService.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/citadel-access-contracts/modules/apimOnboardService.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/citadel-access-contracts/modules/apimProduct.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/citadel-access-contracts/modules/apimProduct.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/citadel-access-contracts/modules/apimProduct.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/citadel-access-contracts/modules/apimProduct.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/citadel-access-contracts/modules/apimSubscription.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/citadel-access-contracts/modules/apimSubscription.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/citadel-access-contracts/modules/apimSubscription.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/citadel-access-contracts/modules/apimSubscription.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/citadel-access-contracts/modules/foundryConnection.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/citadel-access-contracts/modules/foundryConnection.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/citadel-access-contracts/modules/foundryConnection.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/citadel-access-contracts/modules/foundryConnection.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/citadel-access-contracts/modules/kvSecrets.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/citadel-access-contracts/modules/kvSecrets.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/citadel-access-contracts/modules/kvSecrets.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/citadel-access-contracts/modules/kvSecrets.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/citadel-access-contracts/policies/default-ai-product-policy.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/citadel-access-contracts/policies/default-ai-product-policy.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/citadel-access-contracts/policies/default-ai-product-policy.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/citadel-access-contracts/policies/default-ai-product-policy.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/entra-id-setup/README.md b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/entra-id-setup/README.md similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/entra-id-setup/README.md rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/entra-id-setup/README.md diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/entra-id-setup/setup.ps1 b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/entra-id-setup/setup.ps1 similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/entra-id-setup/setup.ps1 rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/entra-id-setup/setup.ps1 diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/README.md b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/README.md similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/README.md rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/README.md diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/connection-apim.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/connection-apim.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/connection-apim.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/connection-apim.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/main.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/main.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/main.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/main.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/main.bicepparam b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/main.bicepparam similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/main.bicepparam rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/main.bicepparam diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/modules/apim-connection-common.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/modules/apim-connection-common.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/modules/apim-connection-common.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/modules/apim-connection-common.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/samples/custom-auth.bicepparam b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/samples/custom-auth.bicepparam similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/samples/custom-auth.bicepparam rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/samples/custom-auth.bicepparam diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/samples/custom-headers.bicepparam b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/samples/custom-headers.bicepparam similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/samples/custom-headers.bicepparam rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/samples/custom-headers.bicepparam diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/samples/dynamic-discovery.bicepparam b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/samples/dynamic-discovery.bicepparam similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/samples/dynamic-discovery.bicepparam rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/samples/dynamic-discovery.bicepparam diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/samples/full-config.bicepparam b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/samples/full-config.bicepparam similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/samples/full-config.bicepparam rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/samples/full-config.bicepparam diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/samples/parameters-custom-auth-config.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/samples/parameters-custom-auth-config.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/samples/parameters-custom-auth-config.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/samples/parameters-custom-auth-config.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/samples/parameters-custom-headers.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/samples/parameters-custom-headers.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/samples/parameters-custom-headers.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/samples/parameters-custom-headers.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/samples/parameters-dynamic-discovery.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/samples/parameters-dynamic-discovery.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/samples/parameters-dynamic-discovery.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/samples/parameters-dynamic-discovery.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/samples/parameters-static-models.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/samples/parameters-static-models.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/samples/parameters-static-models.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/samples/parameters-static-models.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/samples/static-models.bicepparam b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/samples/static-models.bicepparam similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/foundry-integration/samples/static-models.bicepparam rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/foundry-integration/samples/static-models.bicepparam diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/README.md b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/README.md similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/README.md rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/README.md diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/main.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/main.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/main.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/main.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/main.bicepparam b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/main.bicepparam similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/main.bicepparam rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/main.bicepparam diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/llm-backend-pools.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/llm-backend-pools.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/llm-backend-pools.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/llm-backend-pools.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/llm-backends.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/llm-backends.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/llm-backends.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/llm-backends.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/llm-policy-fragments.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/llm-policy-fragments.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/llm-policy-fragments.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/llm-policy-fragments.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-get-available-models.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-get-available-models.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-get-available-models.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-get-available-models.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-metadata-config.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-metadata-config.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-metadata-config.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-metadata-config.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-responses-id-cache-store.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-responses-id-cache-store.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-responses-id-cache-store.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-responses-id-cache-store.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-responses-id-security.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-responses-id-security.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-responses-id-security.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-responses-id-security.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-set-backend-authorization.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-set-backend-authorization.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-set-backend-authorization.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-set-backend-authorization.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-set-backend-pools.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-set-backend-pools.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-set-backend-pools.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-set-backend-pools.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-set-llm-requested-model.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-set-llm-requested-model.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-set-llm-requested-model.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-set-llm-requested-model.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-set-llm-usage.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-set-llm-usage.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-set-llm-usage.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-set-llm-usage.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-set-target-backend-pool.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-set-target-backend-pool.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-set-target-backend-pool.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-set-target-backend-pool.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-validate-model-access.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-validate-model-access.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-validate-model-access.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/frag-validate-model-access.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/models-inference-openapi.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/models-inference-openapi.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/models-inference-openapi.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/models-inference-openapi.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/universal-llm-api-policy.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/universal-llm-api-policy.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/universal-llm-api-policy.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/universal-llm-api-policy.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/universal-llm-openapi.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/universal-llm-openapi.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/universal-llm-openapi.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/policies/universal-llm-openapi.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/universal-llm-api.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/universal-llm-api.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/universal-llm-api.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/llm-backend-onboarding/modules/universal-llm-api.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/main.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/main.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/main.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/main.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/main.bicepparam b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/main.bicepparam similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/main.bicepparam rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/main.bicepparam diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/main.parameters.dev.bicepparam b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/main.parameters.dev.bicepparam similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/main.parameters.dev.bicepparam rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/main.parameters.dev.bicepparam diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/main.parameters.prod.bicepparam b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/main.parameters.prod.bicepparam similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/main.parameters.prod.bicepparam rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/main.parameters.prod.bicepparam diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apic/apic-metadata.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apic/apic-metadata.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apic/apic-metadata.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apic/apic-metadata.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apic/apic.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apic/apic.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apic/apic.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apic/apic.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apic/remote-mcp-servers-SAMPLES.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apic/remote-mcp-servers-SAMPLES.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apic/remote-mcp-servers-SAMPLES.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apic/remote-mcp-servers-SAMPLES.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apic/remote-mcp-servers.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apic/remote-mcp-servers.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apic/remote-mcp-servers.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apic/remote-mcp-servers.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apic/servers.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apic/servers.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apic/servers.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apic/servers.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/README-llm-backends.md b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/README-llm-backends.md similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/README-llm-backends.md rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/README-llm-backends.md diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/ai-model-inference/ai-model-inference-api-spec.yaml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/ai-model-inference/ai-model-inference-api-spec.yaml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/ai-model-inference/ai-model-inference-api-spec.yaml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/ai-model-inference/ai-model-inference-api-spec.yaml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/ai-search-api/Azure AI Search Service API.openapi.yaml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/ai-search-api/Azure AI Search Service API.openapi.yaml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/ai-search-api/Azure AI Search Service API.openapi.yaml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/ai-search-api/Azure AI Search Service API.openapi.yaml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/ai-search-api/ai-search-api-spec.yaml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/ai-search-api/ai-search-api-spec.yaml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/ai-search-api/ai-search-api-spec.yaml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/ai-search-api/ai-search-api-spec.yaml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/ai-search-api/ai-search-index-2024-07-01-api-spec.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/ai-search-api/ai-search-index-2024-07-01-api-spec.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/ai-search-api/ai-search-index-2024-07-01-api-spec.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/ai-search-api/ai-search-index-2024-07-01-api-spec.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/ai-search-api/ai-search-service-2024-07-01-api-spec.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/ai-search-api/ai-search-service-2024-07-01-api-spec.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/ai-search-api/ai-search-service-2024-07-01-api-spec.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/ai-search-api/ai-search-service-2024-07-01-api-spec.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/api-center-onboarding-all.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/api-center-onboarding-all.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/api-center-onboarding-all.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/api-center-onboarding-all.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/api-center-onboarding.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/api-center-onboarding.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/api-center-onboarding.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/api-center-onboarding.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/api.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/api.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/api.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/api.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/apim.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/apim.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/apim.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/apim.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/doc-intel-api/document-intelligence-2024-11-30-compressed.openapi.yaml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/doc-intel-api/document-intelligence-2024-11-30-compressed.openapi.yaml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/doc-intel-api/document-intelligence-2024-11-30-compressed.openapi.yaml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/doc-intel-api/document-intelligence-2024-11-30-compressed.openapi.yaml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/doc-intel-api/document-intelligence-2024-11-30.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/doc-intel-api/document-intelligence-2024-11-30.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/doc-intel-api/document-intelligence-2024-11-30.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/doc-intel-api/document-intelligence-2024-11-30.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/doc-intel-api/document-intelligence-2024-11-30.openapi.yaml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/doc-intel-api/document-intelligence-2024-11-30.openapi.yaml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/doc-intel-api/document-intelligence-2024-11-30.openapi.yaml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/doc-intel-api/document-intelligence-2024-11-30.openapi.yaml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/inference-api.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/inference-api.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/inference-api.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/inference-api.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/inference-backend.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/inference-backend.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/inference-backend.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/inference-backend.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/language-api/language-api-2024-11-01.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/language-api/language-api-2024-11-01.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/language-api/language-api-2024-11-01.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/language-api/language-api-2024-11-01.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/llm-backend-pools.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/llm-backend-pools.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/llm-backend-pools.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/llm-backend-pools.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/llm-backends.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/llm-backends.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/llm-backends.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/llm-backends.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/llm-policy-fragments.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/llm-policy-fragments.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/llm-policy-fragments.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/llm-policy-fragments.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/llm-policy-fragments.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/llm-policy-fragments.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/llm-policy-fragments.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/llm-policy-fragments.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/mcp-existing.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/mcp-existing.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/mcp-existing.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/mcp-existing.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/mcp-from-api.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/mcp-from-api.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/mcp-from-api.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/mcp-from-api.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/OpenAI-Import.openapi.yaml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/OpenAI-Import.openapi.yaml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/OpenAI-Import.openapi.yaml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/OpenAI-Import.openapi.yaml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/oai-api-spec-2024-02-01.yaml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/oai-api-spec-2024-02-01.yaml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/oai-api-spec-2024-02-01.yaml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/oai-api-spec-2024-02-01.yaml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/oai-api-spec-2024-05-01-preview.yaml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/oai-api-spec-2024-05-01-preview.yaml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/oai-api-spec-2024-05-01-preview.yaml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/oai-api-spec-2024-05-01-preview.yaml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/oai-api-spec-2024-06-01.yaml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/oai-api-spec-2024-06-01.yaml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/oai-api-spec-2024-06-01.yaml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/oai-api-spec-2024-06-01.yaml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/oai-api-spec-2024-10-21.yaml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/oai-api-spec-2024-10-21.yaml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/oai-api-spec-2024-10-21.yaml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/oai-api-spec-2024-10-21.yaml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/oai-realtime-api-ws.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/oai-realtime-api-ws.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/oai-realtime-api-ws.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/openai-api/oai-realtime-api-ws.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/ai-model-inference-api-policy.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/ai-model-inference-api-policy.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/ai-model-inference-api-policy.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/ai-model-inference-api-policy.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/ai-search-index-api-policy.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/ai-search-index-api-policy.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/ai-search-index-api-policy.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/ai-search-index-api-policy.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/ai-search-service-api-policy.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/ai-search-service-api-policy.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/ai-search-service-api-policy.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/ai-search-service-api-policy.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/azure-open-ai-api-policy.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/azure-open-ai-api-policy.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/azure-open-ai-api-policy.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/azure-open-ai-api-policy.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/doc-intelligence-api-policy.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/doc-intelligence-api-policy.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/doc-intelligence-api-policy.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/doc-intelligence-api-policy.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-aad-auth-custom.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-aad-auth-custom.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-aad-auth-custom.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-aad-auth-custom.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-aad-auth.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-aad-auth.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-aad-auth.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-aad-auth.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-ai-foundry-compatibility.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-ai-foundry-compatibility.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-ai-foundry-compatibility.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-ai-foundry-compatibility.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-ai-foundry-deployments.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-ai-foundry-deployments.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-ai-foundry-deployments.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-ai-foundry-deployments.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-ai-usage.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-ai-usage.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-ai-usage.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-ai-usage.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-central-cache-manager.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-central-cache-manager.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-central-cache-manager.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-central-cache-manager.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-entra-auth.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-entra-auth.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-entra-auth.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-entra-auth.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-get-available-models.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-get-available-models.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-get-available-models.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-get-available-models.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-llm-usage.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-llm-usage.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-llm-usage.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-llm-usage.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-metadata-config.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-metadata-config.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-metadata-config.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-metadata-config.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-openai-usage-streaming.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-openai-usage-streaming.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-openai-usage-streaming.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-openai-usage-streaming.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-openai-usage.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-openai-usage.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-openai-usage.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-openai-usage.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-path-builder.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-path-builder.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-path-builder.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-path-builder.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-pii-anonymization.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-pii-anonymization.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-pii-anonymization.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-pii-anonymization.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-pii-deanonymization.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-pii-deanonymization.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-pii-deanonymization.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-pii-deanonymization.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-pii-state-saving.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-pii-state-saving.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-pii-state-saving.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-pii-state-saving.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-raise-throttling-events.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-raise-throttling-events.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-raise-throttling-events.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-raise-throttling-events.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-request-processor.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-request-processor.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-request-processor.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-request-processor.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-responses-id-cache-store.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-responses-id-cache-store.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-responses-id-cache-store.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-responses-id-cache-store.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-responses-id-security.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-responses-id-security.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-responses-id-security.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-responses-id-security.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-security-handler.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-security-handler.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-security-handler.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-security-handler.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-backend-authorization.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-backend-authorization.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-backend-authorization.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-backend-authorization.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-backend-pools.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-backend-pools.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-backend-pools.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-backend-pools.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-llm-requested-model.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-llm-requested-model.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-llm-requested-model.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-llm-requested-model.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-llm-usage.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-llm-usage.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-llm-usage.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-llm-usage.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-response-headers.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-response-headers.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-response-headers.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-response-headers.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-target-backend-pool.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-target-backend-pool.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-target-backend-pool.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-set-target-backend-pool.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-strip-backend-headers.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-strip-backend-headers.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-strip-backend-headers.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-strip-backend-headers.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-throttling-events.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-throttling-events.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-throttling-events.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-throttling-events.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-validate-model-access.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-validate-model-access.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-validate-model-access.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/frag-validate-model-access.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/mcp-default-policy.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/mcp-default-policy.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/mcp-default-policy.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/mcp-default-policy.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/openai-realtime-policy.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/openai-realtime-policy.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/openai-realtime-policy.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/openai-realtime-policy.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/openai_api_policy.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/openai_api_policy.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/openai_api_policy.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/openai_api_policy.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/openai_api_policy_dynamic_throttling.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/openai_api_policy_dynamic_throttling.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/openai_api_policy_dynamic_throttling.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/openai_api_policy_dynamic_throttling.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/product-llm-oauth-access.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/product-llm-oauth-access.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/product-llm-oauth-access.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/product-llm-oauth-access.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/translator-api-policy.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/translator-api-policy.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/translator-api-policy.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/translator-api-policy.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/unified-ai-api-deployment-by-name-policy.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/unified-ai-api-deployment-by-name-policy.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/unified-ai-api-deployment-by-name-policy.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/unified-ai-api-deployment-by-name-policy.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/unified-ai-api-deployments-policy.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/unified-ai-api-deployments-policy.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/unified-ai-api-deployments-policy.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/unified-ai-api-deployments-policy.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/unified-ai-api-policy.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/unified-ai-api-policy.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/unified-ai-api-policy.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/unified-ai-api-policy.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/unified-ai-product-subscription.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/unified-ai-product-subscription.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/unified-ai-product-subscription.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/unified-ai-product-subscription.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/universal-llm-api-deployment-by-name-policy.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/universal-llm-api-deployment-by-name-policy.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/universal-llm-api-deployment-by-name-policy.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/universal-llm-api-deployment-by-name-policy.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/universal-llm-api-deployments-policy.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/universal-llm-api-deployments-policy.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/universal-llm-api-deployments-policy.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/universal-llm-api-deployments-policy.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/universal-llm-api-policy-v2.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/universal-llm-api-policy-v2.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policies/universal-llm-api-policy-v2.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policies/universal-llm-api-policy-v2.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policy-fragments.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policy-fragments.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/policy-fragments.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/policy-fragments.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/sample/weather/openapi.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/sample/weather/openapi.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/sample/weather/openapi.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/sample/weather/openapi.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/sample/weather/policy.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/sample/weather/policy.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/sample/weather/policy.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/sample/weather/policy.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/speech-api/speech-api-3-1.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/speech-api/speech-api-3-1.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/speech-api/speech-api-3-1.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/speech-api/speech-api-3-1.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/translator-api/translator-api-spec.yaml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/translator-api/translator-api-spec.yaml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/translator-api/translator-api-spec.yaml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/translator-api/translator-api-spec.yaml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/unified-ai-api.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/unified-ai-api.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/unified-ai-api.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/unified-ai-api.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/unified-ai-api/UnifiedAIWildcard.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/unified-ai-api/UnifiedAIWildcard.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/unified-ai-api/UnifiedAIWildcard.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/unified-ai-api/UnifiedAIWildcard.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/AIFoundryAzureAI.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/AIFoundryAzureAI.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/AIFoundryAzureAI.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/AIFoundryAzureAI.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/AIFoundryOpenAI.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/AIFoundryOpenAI.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/AIFoundryOpenAI.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/AIFoundryOpenAI.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/AIFoundryOpenAIV1.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/AIFoundryOpenAIV1.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/AIFoundryOpenAIV1.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/AIFoundryOpenAIV1.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/PassThrough.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/PassThrough.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/PassThrough.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/PassThrough.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/Universal-LLM-API.openapi.yaml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/Universal-LLM-API.openapi.yaml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/Universal-LLM-API.openapi.yaml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/Universal-LLM-API.openapi.yaml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/Universal-LLM-Basic-API.openapi.yaml b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/Universal-LLM-Basic-API.openapi.yaml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/Universal-LLM-Basic-API.openapi.yaml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/apim/universal-llm-api/Universal-LLM-Basic-API.openapi.yaml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/azure-roles.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/azure-roles.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/azure-roles.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/azure-roles.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/cosmos-db/cosmos-db.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/cosmos-db/cosmos-db.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/cosmos-db/cosmos-db.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/cosmos-db/cosmos-db.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/event-hub/event-hub.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/event-hub/event-hub.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/event-hub/event-hub.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/event-hub/event-hub.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/foundry/deployments.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/foundry/deployments.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/foundry/deployments.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/foundry/deployments.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/foundry/foundry.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/foundry/foundry.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/foundry/foundry.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/foundry/foundry.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/functionapp/functionapp.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/functionapp/functionapp.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/functionapp/functionapp.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/functionapp/functionapp.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/functionapp/storageaccount.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/functionapp/storageaccount.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/functionapp/storageaccount.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/functionapp/storageaccount.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/keyvault/keyvault-rbac.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/keyvault/keyvault-rbac.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/keyvault/keyvault-rbac.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/keyvault/keyvault-rbac.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/keyvault/keyvault-secret.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/keyvault/keyvault-secret.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/keyvault/keyvault-secret.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/keyvault/keyvault-secret.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/keyvault/keyvault.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/keyvault/keyvault.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/keyvault/keyvault.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/keyvault/keyvault.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/logicapp/api-connection-access.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/logicapp/api-connection-access.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/logicapp/api-connection-access.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/logicapp/api-connection-access.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/logicapp/api-connection.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/logicapp/api-connection.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/logicapp/api-connection.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/logicapp/api-connection.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/logicapp/logicapp.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/logicapp/logicapp.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/logicapp/logicapp.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/logicapp/logicapp.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/monitor/applicationinsights-dashboard.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/monitor/applicationinsights-dashboard.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/monitor/applicationinsights-dashboard.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/monitor/applicationinsights-dashboard.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/monitor/applicationinsights.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/monitor/applicationinsights.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/monitor/applicationinsights.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/monitor/applicationinsights.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/monitor/loganalytics.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/monitor/loganalytics.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/monitor/loganalytics.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/monitor/loganalytics.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/monitor/monitoring.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/monitor/monitoring.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/monitor/monitoring.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/monitor/monitoring.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/networking/dns.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/networking/dns.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/networking/dns.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/networking/dns.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/networking/private-endpoint-multi-dns.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/networking/private-endpoint-multi-dns.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/networking/private-endpoint-multi-dns.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/networking/private-endpoint-multi-dns.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/networking/private-endpoint.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/networking/private-endpoint.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/networking/private-endpoint.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/networking/private-endpoint.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/networking/subnet.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/networking/subnet.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/networking/subnet.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/networking/subnet.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/networking/vnet-existing.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/networking/vnet-existing.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/networking/vnet-existing.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/networking/vnet-existing.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/networking/vnet.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/networking/vnet.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/networking/vnet.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/networking/vnet.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/redis/redis.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/redis/redis.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/redis/redis.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/redis/redis.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/security/managed-identity-apim.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/security/managed-identity-apim.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/security/managed-identity-apim.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/security/managed-identity-apim.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/security/managed-identity-usage.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/security/managed-identity-usage.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/bicep/infra/modules/security/managed-identity-usage.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/bicep/infra/modules/security/managed-identity-usage.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/challenge-01.md b/03-Azure/01-04-AI/06_AI_Governance/challenges/challenge-01.md similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/challenge-01.md rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/challenge-01.md diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/challenge-02.md b/03-Azure/01-04-AI/06_AI_Governance/challenges/challenge-02.md similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/challenge-02.md rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/challenge-02.md diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/challenge-03.md b/03-Azure/01-04-AI/06_AI_Governance/challenges/challenge-03.md similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/challenge-03.md rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/challenge-03.md diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/challenge-04.md b/03-Azure/01-04-AI/06_AI_Governance/challenges/challenge-04.md similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/challenge-04.md rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/challenge-04.md diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/challenge-05.md b/03-Azure/01-04-AI/06_AI_Governance/challenges/challenge-05.md similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/challenge-05.md rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/challenge-05.md diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/challenge-06.md b/03-Azure/01-04-AI/06_AI_Governance/challenges/challenge-06.md similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/challenge-06.md rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/challenge-06.md diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/challenge-07.md b/03-Azure/01-04-AI/06_AI_Governance/challenges/challenge-07.md similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/challenge-07.md rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/challenge-07.md diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/challenge-08.md b/03-Azure/01-04-AI/06_AI_Governance/challenges/challenge-08.md similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/challenge-08.md rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/challenge-08.md diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/challenge-09.md b/03-Azure/01-04-AI/06_AI_Governance/challenges/challenge-09.md similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/challenge-09.md rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/challenge-09.md diff --git a/03-Azure/01-04-AI/06_AI_Governance/challenges/finish.md b/03-Azure/01-04-AI/06_AI_Governance/challenges/finish.md new file mode 100644 index 000000000..dd8f9e753 --- /dev/null +++ b/03-Azure/01-04-AI/06_AI_Governance/challenges/finish.md @@ -0,0 +1,54 @@ +# Finish + +[← Solution 09](../walkthrough/challenge-09/solution-09.md) · **[Home](../README.md)** + +## Congratulations 🎉 + +You finished the **AI Governance** MicroHack. + +Along the way you turned an ungoverned pile of models into a governed AI platform: + +- **Challenge 1–2** — onboarded an LLM backend behind a single gateway and proved one + Universal LLM API works across every deployed model. +- **Challenge 3–4** — enforced *access contracts*: which caller may use which model, at what + capacity — then drove those same contracts from three different agent frameworks. +- **Challenge 5** — masked, blocked, and analysed PII **at the gateway**, so governance holds + no matter which client calls. +- **Challenge 6** — unified Azure OpenAI, Foundry inference, and Responses-style APIs behind + one contract. +- **Challenge 7–9** *(follow-on)* — built a governed hosted agent with the Agent Governance + Toolkit, published it as an A2A endpoint, and exposed an MCP server through APIM. + +The governance idea worth taking home: **policy belongs at the gateway, not in each app.** +Every control you configured applies identically to a notebook, an SDK agent, and a +third-party tool — because none of them can reach a model any other way. + +## Clean up + +Your MicroHack resource group is deleted by the platform when the lab expires — no action +needed. If you deployed the workshop yourself outside the MicroHack, delete the resource +group and then purge the soft-deleted resources: + +```bash +az group delete --name --yes --no-wait +az cognitiveservices account purge --name --resource-group --location +az keyvault purge --name --location +``` + +> [!NOTE] +> API Management and Azure AI Foundry accounts are **soft-deleted**. Without the purge +> commands their names stay reserved and a redeploy in the same subscription fails. + +## Keep going + +- [Azure API Management — AI gateway capabilities](https://learn.microsoft.com/azure/api-management/genai-gateway-capabilities) +- [Azure AI Foundry documentation](https://learn.microsoft.com/azure/ai-foundry/) +- [Responsible AI in Azure AI Foundry](https://learn.microsoft.com/azure/ai-foundry/responsible-use-of-ai-overview) +- [AI Hub Gateway Solution Accelerator](https://github.com/mohamedsaif/ai-hub-gateway-solution-accelerator) — the upstream project this MicroHack is built on + +## Feedback + +Found a bug, or something that could be explained better? Please open an issue on this +repository or reach out to the contributors listed in the [module README](../README.md). + +Thank you for investing the time — see you at the next MicroHack! diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/shared/apimtools.py b/03-Azure/01-04-AI/06_AI_Governance/challenges/shared/apimtools.py similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/shared/apimtools.py rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/shared/apimtools.py diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/shared/requirements.txt b/03-Azure/01-04-AI/06_AI_Governance/challenges/shared/requirements.txt similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/shared/requirements.txt rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/shared/requirements.txt diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/shared/snippets/README.md b/03-Azure/01-04-AI/06_AI_Governance/challenges/shared/snippets/README.md similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/shared/snippets/README.md rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/shared/snippets/README.md diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/shared/snippets/api-http-requests.py b/03-Azure/01-04-AI/06_AI_Governance/challenges/shared/snippets/api-http-requests.py similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/shared/snippets/api-http-requests.py rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/shared/snippets/api-http-requests.py diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/shared/snippets/create-az-deployment.py b/03-Azure/01-04-AI/06_AI_Governance/challenges/shared/snippets/create-az-deployment.py similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/shared/snippets/create-az-deployment.py rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/shared/snippets/create-az-deployment.py diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/shared/snippets/create-az-resource-group.py b/03-Azure/01-04-AI/06_AI_Governance/challenges/shared/snippets/create-az-resource-group.py similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/shared/snippets/create-az-resource-group.py rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/shared/snippets/create-az-resource-group.py diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/shared/snippets/deployment-outputs.py b/03-Azure/01-04-AI/06_AI_Governance/challenges/shared/snippets/deployment-outputs.py similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/shared/snippets/deployment-outputs.py rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/shared/snippets/deployment-outputs.py diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/shared/snippets/openai-api-requests.py b/03-Azure/01-04-AI/06_AI_Governance/challenges/shared/snippets/openai-api-requests.py similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/shared/snippets/openai-api-requests.py rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/shared/snippets/openai-api-requests.py diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/shared/utils.py b/03-Azure/01-04-AI/06_AI_Governance/challenges/shared/utils.py similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/shared/utils.py rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/shared/utils.py diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/1. llm-backend-onboarding-runner.ipynb b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/1. llm-backend-onboarding-runner.ipynb similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/1. llm-backend-onboarding-runner.ipynb rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/1. llm-backend-onboarding-runner.ipynb diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/2. citadel-universal-llm-api-all-models-tests.ipynb b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/2. citadel-universal-llm-api-all-models-tests.ipynb similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/2. citadel-universal-llm-api-all-models-tests.ipynb rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/2. citadel-universal-llm-api-all-models-tests.ipynb diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/3. citadel-access-contracts-tests.ipynb b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/3. citadel-access-contracts-tests.ipynb similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/3. citadel-access-contracts-tests.ipynb rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/3. citadel-access-contracts-tests.ipynb diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/4. citadel-agent-frameworks-tests.ipynb b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/4. citadel-agent-frameworks-tests.ipynb similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/4. citadel-agent-frameworks-tests.ipynb rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/4. citadel-agent-frameworks-tests.ipynb diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/5. citadel-pii-processing-tests.ipynb b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/5. citadel-pii-processing-tests.ipynb similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/5. citadel-pii-processing-tests.ipynb rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/5. citadel-pii-processing-tests.ipynb diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/6. citadel-unified-ai-api-tests.ipynb b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/6. citadel-unified-ai-api-tests.ipynb similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/6. citadel-unified-ai-api-tests.ipynb rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/6. citadel-unified-ai-api-tests.ipynb diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/7. citadel-hosted-agent-with-agt.ipynb b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/7. citadel-hosted-agent-with-agt.ipynb similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/7. citadel-hosted-agent-with-agt.ipynb rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/7. citadel-hosted-agent-with-agt.ipynb diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/8. publish-and-use-a2a-endpoint.ipynb b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/8. publish-and-use-a2a-endpoint.ipynb similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/8. publish-and-use-a2a-endpoint.ipynb rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/8. publish-and-use-a2a-endpoint.ipynb diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/9. publish-and-use-hr-mcp-via-apim.ipynb b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/9. publish-and-use-hr-mcp-via-apim.ipynb similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/9. publish-and-use-hr-mcp-via-apim.ipynb rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/9. publish-and-use-hr-mcp-via-apim.ipynb diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/governed-agent/Dockerfile b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/governed-agent/Dockerfile similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/governed-agent/Dockerfile rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/governed-agent/Dockerfile diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/governed-agent/agent.yaml b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/governed-agent/agent.yaml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/governed-agent/agent.yaml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/governed-agent/agent.yaml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/governed-agent/governance.py b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/governed-agent/governance.py similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/governed-agent/governance.py rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/governed-agent/governance.py diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/governed-agent/main.py b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/governed-agent/main.py similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/governed-agent/main.py rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/governed-agent/main.py diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/governed-agent/policies/governance-policy.yaml b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/governed-agent/policies/governance-policy.yaml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/governed-agent/policies/governance-policy.yaml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/governed-agent/policies/governance-policy.yaml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/governed-agent/requirements.txt b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/governed-agent/requirements.txt similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/governed-agent/requirements.txt rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/governed-agent/requirements.txt diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/guided-workshop-prompt.md b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/guided-workshop-prompt.md similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/guided-workshop-prompt.md rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/guided-workshop-prompt.md diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/hosted-agent/Dockerfile b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/hosted-agent/Dockerfile similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/hosted-agent/Dockerfile rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/hosted-agent/Dockerfile diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/hosted-agent/agent.yaml b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/hosted-agent/agent.yaml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/hosted-agent/agent.yaml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/hosted-agent/agent.yaml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/hosted-agent/main.py b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/hosted-agent/main.py similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/hosted-agent/main.py rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/hosted-agent/main.py diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/hosted-agent/policies/hr-policy.yaml b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/hosted-agent/policies/hr-policy.yaml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/hosted-agent/policies/hr-policy.yaml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/hosted-agent/policies/hr-policy.yaml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/hosted-agent/requirements.txt b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/hosted-agent/requirements.txt similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/hosted-agent/requirements.txt rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/hosted-agent/requirements.txt diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/hosted-agent/Dockerfile b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/hosted-agent/Dockerfile similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/hosted-agent/Dockerfile rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/hosted-agent/Dockerfile diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/hosted-agent/agent.yaml b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/hosted-agent/agent.yaml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/hosted-agent/agent.yaml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/hosted-agent/agent.yaml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/hosted-agent/main.py b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/hosted-agent/main.py similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/hosted-agent/main.py rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/hosted-agent/main.py diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/hosted-agent/requirements.txt b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/hosted-agent/requirements.txt similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/hosted-agent/requirements.txt rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/hosted-agent/requirements.txt diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/hr-mcp-implementation-plan.md b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/hr-mcp-implementation-plan.md similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/hr-mcp-implementation-plan.md rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/hr-mcp-implementation-plan.md diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/infra/README.md b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/infra/README.md similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/infra/README.md rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/infra/README.md diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/infra/main.bicep b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/infra/main.bicep similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/infra/main.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/infra/main.bicep diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/infra/policies/hr-mcp-api-policy.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/infra/policies/hr-mcp-api-policy.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/infra/policies/hr-mcp-api-policy.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/infra/policies/hr-mcp-api-policy.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/infra/policies/hr-mcp-product-policy.xml b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/infra/policies/hr-mcp-product-policy.xml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/infra/policies/hr-mcp-product-policy.xml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/infra/policies/hr-mcp-product-policy.xml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/scripts/deploy-hr-mcp.ps1 b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/scripts/deploy-hr-mcp.ps1 similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/scripts/deploy-hr-mcp.ps1 rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/scripts/deploy-hr-mcp.ps1 diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/scripts/deploy-hr-mcp.sh b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/scripts/deploy-hr-mcp.sh similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/scripts/deploy-hr-mcp.sh rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/scripts/deploy-hr-mcp.sh diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/scripts/publish-hr-mcp-apim.ps1 b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/scripts/publish-hr-mcp-apim.ps1 similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/scripts/publish-hr-mcp-apim.ps1 rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/scripts/publish-hr-mcp-apim.ps1 diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/scripts/publish-hr-mcp-apim.sh b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/scripts/publish-hr-mcp-apim.sh similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/scripts/publish-hr-mcp-apim.sh rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/scripts/publish-hr-mcp-apim.sh diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/scripts/teardown-hr-mcp.ps1 b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/scripts/teardown-hr-mcp.ps1 similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/scripts/teardown-hr-mcp.ps1 rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/scripts/teardown-hr-mcp.ps1 diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/scripts/teardown-hr-mcp.sh b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/scripts/teardown-hr-mcp.sh similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/scripts/teardown-hr-mcp.sh rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/scripts/teardown-hr-mcp.sh diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/scripts/test-hr-mcp-apim.py b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/scripts/test-hr-mcp-apim.py similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/scripts/test-hr-mcp-apim.py rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/scripts/test-hr-mcp-apim.py diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/scripts/test-hr-mcp-direct.py b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/scripts/test-hr-mcp-direct.py similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/scripts/test-hr-mcp-direct.py rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/scripts/test-hr-mcp-direct.py diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/server/.dockerignore b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/server/.dockerignore similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/server/.dockerignore rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/server/.dockerignore diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/server/Dockerfile b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/server/Dockerfile similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/server/Dockerfile rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/server/Dockerfile diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/server/auth.py b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/server/auth.py similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/server/auth.py rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/server/auth.py diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/server/hr_data.py b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/server/hr_data.py similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/server/hr_data.py rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/server/hr_data.py diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/server/pyproject.toml b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/server/pyproject.toml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/server/pyproject.toml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/server/pyproject.toml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/server/requirements.txt b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/server/requirements.txt similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/server/requirements.txt rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/server/requirements.txt diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/server/server.py b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/server/server.py similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/server/server.py rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/server/server.py diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/server/telemetry.py b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/server/telemetry.py similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/server/telemetry.py rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/server/telemetry.py diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/server/uv.lock b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/server/uv.lock similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/mcp-hr/server/uv.lock rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/mcp-hr/server/uv.lock diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/pyproject.toml b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/pyproject.toml similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/pyproject.toml rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/pyproject.toml diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/readme.md b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/readme.md similarity index 97% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/readme.md rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/readme.md index 36495206b..71eda9c50 100644 --- a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/readme.md +++ b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/readme.md @@ -1,5 +1,22 @@ # Citadel AI Governance Hub - Hands-On Workshop Lab Guide 🏛️ +> [!CAUTION] +> **MicroHack participants: do NOT follow the deployment steps in this file.** +> +> This is the **unmodified upstream workshop guide**, kept here because the notebooks are +> unchanged and reference it. It assumes you provision everything yourself with `azd up` +> plus `scripts/deploy-spoke-foundry.*`, into **two** resource groups. +> +> In the MicroHack, **your hub and spoke are already deployed for you**, both inside the +> **single** resource group listed on your lab dashboard. Running `azd up` or +> `deploy-spoke-foundry.*` here will fail or create duplicate resources you will be billed +> for, and it will not match the credentials on your dashboard. +> +> **Start at [`../challenge-01.md`](../challenge-01.md) instead**, and use +> `labautomation/setup-notebook-env.ps1` to wire the notebooks to your environment. +> Sections 4 onward of this file (notebook walkthroughs, architecture, concepts) are still +> accurate and worth reading. + **Duration:** ~3 hours **Level:** Intermediate **Last updated:** June 2026 @@ -11,6 +28,7 @@ This hands-on lab deploys a governed AI hub-and-spoke architecture into your Azu - **Lab environment** — your local machine or the included Devcontainer, where deployment commands, notebooks, and validation traffic originate. You deploy the hub with `azd up`, then deploy the sample spoke with `workshop/scripts/deploy-spoke-foundry.*`. +*(MicroHack: both are already deployed into your single resource group — see the caution above.)* --- diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/requirements.txt b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/requirements.txt similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/requirements.txt rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/requirements.txt diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/scripts/cleanup-apim-defaults.ps1 b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/scripts/cleanup-apim-defaults.ps1 similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/scripts/cleanup-apim-defaults.ps1 rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/scripts/cleanup-apim-defaults.ps1 diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/scripts/cleanup-apim-defaults.sh b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/scripts/cleanup-apim-defaults.sh similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/scripts/cleanup-apim-defaults.sh rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/scripts/cleanup-apim-defaults.sh diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/scripts/deploy-spoke-foundry.ps1 b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/scripts/deploy-spoke-foundry.ps1 similarity index 90% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/scripts/deploy-spoke-foundry.ps1 rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/scripts/deploy-spoke-foundry.ps1 index eb82a1e46..22916d84d 100644 --- a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/scripts/deploy-spoke-foundry.ps1 +++ b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/scripts/deploy-spoke-foundry.ps1 @@ -5,9 +5,44 @@ param( [string]$FoundryAccountName = $env:FOUNDRY_ACCOUNT_NAME, [string]$FoundryProjectName = $env:FOUNDRY_PROJECT_NAME, [string]$KeyVaultName = $env:KEY_VAULT_NAME, - [string]$KeyVaultEnablePurgeProtection = $(if ($env:KEY_VAULT_ENABLE_PURGE_PROTECTION) { $env:KEY_VAULT_ENABLE_PURGE_PROTECTION } else { 'false' }) + [string]$KeyVaultEnablePurgeProtection = $(if ($env:KEY_VAULT_ENABLE_PURGE_PROTECTION) { $env:KEY_VAULT_ENABLE_PURGE_PROTECTION } else { 'false' }), + + # MicroHack guard. Not part of the upstream workshop script. + [switch]$Force ) +# --------------------------------------------------------------------------- +# MicroHack guard (added for the MicroHack port — not in the upstream workshop) +# +# Upstream, this script is step 3.7: it creates a SEPARATE spoke resource group after +# `azd up` has built the hub. In the MicroHack each participant gets exactly ONE resource +# group, and the spoke is already provisioned into it by +# labautomation/infra/resources.bicep. Running this script here would create a second, +# unbudgeted resource group whose resource names do not match the lab dashboard +# credentials, and would silently diverge from the environment the notebooks are wired to. +# +# The script is kept (rather than deleted) because notebook 7 and the workshop guide refer +# to it, and because it is still the correct tool outside the MicroHack. Pass -Force to run +# it deliberately. +# --------------------------------------------------------------------------- +if (-not $Force) { + Write-Host '' + Write-Host 'This script is NOT used in the MicroHack.' -ForegroundColor Yellow + Write-Host '' + Write-Host 'Your Citadel spoke (Foundry account + project, Key Vault, Log Analytics,' + Write-Host 'Application Insights, and ACR) is already deployed into the single resource' + Write-Host 'group shown on your lab dashboard. There is nothing to run here.' + Write-Host '' + Write-Host 'To connect the notebooks to it, run from the labautomation folder:' + Write-Host ' ./setup-notebook-env.ps1 -ResourceGroup ...' -ForegroundColor Cyan + Write-Host 'See labautomation/README.md -> "Notebook Environment Setup".' + Write-Host '' + Write-Host 'If you are running the ORIGINAL workshop outside the MicroHack and really do' + Write-Host 'want a separate spoke resource group, re-run with -Force.' + Write-Host '' + exit 0 +} + Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' if ($PSVersionTable.PSVersion.Major -ge 7) { diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/scripts/deploy-spoke-foundry.sh b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/scripts/deploy-spoke-foundry.sh similarity index 88% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/scripts/deploy-spoke-foundry.sh rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/scripts/deploy-spoke-foundry.sh index b35341bd2..de9a5734a 100755 --- a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/scripts/deploy-spoke-foundry.sh +++ b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/scripts/deploy-spoke-foundry.sh @@ -1,6 +1,55 @@ #!/usr/bin/env bash set -euo pipefail +# --------------------------------------------------------------------------- +# MicroHack guard (added for the MicroHack port — not in the upstream workshop) +# +# Upstream, this script is step 3.7: it creates a SEPARATE spoke resource group after +# `azd up` has built the hub. In the MicroHack each participant gets exactly ONE resource +# group, and the spoke is already provisioned into it by +# labautomation/infra/resources.bicep. Running this script here would create a second, +# unbudgeted resource group whose resource names do not match the lab dashboard +# credentials, and would silently diverge from the environment the notebooks are wired to. +# +# The script is kept (rather than deleted) because notebook 7 and the workshop guide refer +# to it, and because it is still the correct tool outside the MicroHack. Pass --force to +# run it deliberately. +# --------------------------------------------------------------------------- +microhack_force="false" +for _arg in "$@"; do + if [[ "$_arg" == "--force" ]]; then + microhack_force="true" + fi +done + +if [[ "$microhack_force" != "true" ]]; then + cat <<'MICROHACK_GUARD' + +This script is NOT used in the MicroHack. + +Your Citadel spoke (Foundry account + project, Key Vault, Log Analytics, +Application Insights, and ACR) is already deployed into the single resource +group shown on your lab dashboard. There is nothing to run here. + +To connect the notebooks to it, run from the labautomation folder: + ./setup-notebook-env.ps1 -ResourceGroup ... +See labautomation/README.md -> "Notebook Environment Setup". + +If you are running the ORIGINAL workshop outside the MicroHack and really do +want a separate spoke resource group, re-run with --force. + +MICROHACK_GUARD + exit 0 +fi + +# Drop the MicroHack-only --force flag so the upstream argument parser below never sees it. +_filtered_args=() +for _arg in "$@"; do + [[ "$_arg" == "--force" ]] && continue + _filtered_args+=("$_arg") +done +set -- ${_filtered_args[@]+"${_filtered_args[@]}"} + # Silence a known Azure CLI Python SyntaxWarning from an upstream dependency. export PYTHONWARNINGS="${PYTHONWARNINGS:+$PYTHONWARNINGS,}ignore::SyntaxWarning" diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/scripts/import-model-pricing.py b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/scripts/import-model-pricing.py similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/scripts/import-model-pricing.py rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/scripts/import-model-pricing.py diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/scripts/model-pricing.json b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/scripts/model-pricing.json similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/scripts/model-pricing.json rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/scripts/model-pricing.json diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/scripts/run-all-notebooks.ps1 b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/scripts/run-all-notebooks.ps1 similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/scripts/run-all-notebooks.ps1 rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/scripts/run-all-notebooks.ps1 diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/scripts/run-all-notebooks.sh b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/scripts/run-all-notebooks.sh similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/scripts/run-all-notebooks.sh rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/scripts/run-all-notebooks.sh diff --git a/03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/uv.lock b/03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/uv.lock similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/challenges/workshop/uv.lock rename to 03-Azure/01-04-AI/06_AI_Governance/challenges/workshop/uv.lock diff --git a/03-Azure/01-04-AI/05_AI_Governance/labautomation/README.md b/03-Azure/01-04-AI/06_AI_Governance/labautomation/README.md similarity index 56% rename from 03-Azure/01-04-AI/05_AI_Governance/labautomation/README.md rename to 03-Azure/01-04-AI/06_AI_Governance/labautomation/README.md index ad992788c..03598603b 100644 --- a/03-Azure/01-04-AI/05_AI_Governance/labautomation/README.md +++ b/03-Azure/01-04-AI/06_AI_Governance/labautomation/README.md @@ -37,14 +37,54 @@ Adapted from the Citadel workshop (`azd up`), the lab deploys: ## Platform Contract - The parameter block matches the MicroHack platform contract exactly. -- The platform **pre-creates the resource group** and pre-sets the Azure context. - The script does **not** call `Connect-AzAccount` or `New-AzResourceGroup`. +- The platform **pre-creates the resource group** and pre-sets the Azure context for the + `resourcegroup` / `resourcegroup-with-subscriptionowner` deployment types, so the script + does **not** call `Connect-AzAccount` or `New-AzResourceGroup` in those modes. In + `subscription` mode the platform hands over an empty subscription instead, and the script + creates its own `rg-citadel-microhack-` resource group. +- [`shared-deploy-lab.ps1`](shared-deploy-lab.ps1) is picked up automatically by file name and + runs **once per subscription, before** the per-participant fan-out, to register the resource + providers this lab needs (see [Subscription Scope Prerequisites](#subscription-scope-prerequisites)). - Hub and spoke resources are deployed into **the same platform-provided ResourceGroupName** using deterministic suffixes to avoid name collisions. - Foundry accounts, projects, Log Analytics, App Insights, Key Vault, and APIM are all provisioned with **managed identities** — no keys or secrets in logs. -- RBAC is assigned **per attendee** (via `$AllowedEntraUserIds`) across Foundry, Key Vault, - Cosmos DB, and monitoring services, so the notebooks work out-of-the-box. +- RBAC is assigned **per attendee** (via `$AllowedEntraUserIds`) across **both the hub and the + spoke** copies of Foundry, Key Vault, Cosmos DB, APIM, ACR, and monitoring, so the notebooks + work out-of-the-box. + +## Spoke Deployment (workshop step 3.7) + +The original workshop provisions the hub with `azd up` and then requires a **separate** +`scripts/deploy-spoke-foundry.ps1` run, which creates a *distinct spoke resource group*. +MicroHack gives each participant exactly **one** resource group, so that step is not run +separately here — the spoke is part of `infra/resources.bicep` and lands in the **same +platform-provided resource group** as the hub. Name collisions are avoided by the `-spoke` +infix plus the shared `resourceToken`. + +| Workshop `deploy-spoke-foundry.ps1` step | Equivalent here | +|---|---| +| Create spoke resource group | *Not applicable* — hub and spoke share the participant's single RG | +| Foundry account (`allowProjectManagement`, system-assigned identity) | `spokeFoundryAccount` → `aif-spoke-` | +| Foundry project (system-assigned identity) | `spokeFoundryProject` → `citadel-agents-project` | +| Log Analytics workspace | `laSpoke` → `law-spoke-` | +| Application Insights | `aiSpoke` → `appi-spoke-` | +| App Insights → Foundry connection (agent tracing) | `spokeFoundryAppInsightsConnection` | +| Key Vault (RBAC authorization) | `kvSpoke` → `kv-spoke-` | +| Container Registry (Basic, admin disabled) | `acrSpoke` → `acrcitadel` | +| **AcrPull → Foundry *project* managed identity** | `acrPullForSpokeProject` role assignment | +| `Azure AI Project Manager` / `Key Vault Secrets User` to the user | Granted by `deploy-lab.ps1` to every id in `$AllowedEntraUserIds` | +| Write `SPOKE_*` values to the azd environment | Emitted as `HackboxCredential`s, applied by `setup-notebook-env.ps1` | + +Two details are load-bearing for **challenge 7**, which builds a hosted agent container: + +- The Foundry **project** managed identity holds **AcrPull** on the spoke ACR. Without it the + agent version deploys and then fails to start with an image-pull error — the notebook itself + states this grant is expected to come from the spoke deployment. +- The ACR has **`adminUserEnabled: false`**. The notebook builds with `az acr build` (cloud + build, no local Docker) and the agent pulls via managed identity, so admin credentials are + never needed. `SPOKE_RESOURCE_GROUP` is emitted as the same value as `ResourceGroup`, which + is what the unchanged notebooks read. ## What the Attendee Receives @@ -113,9 +153,13 @@ can then be opened and run without modification. The script follows MicroHack conventions: - **Region fallback** — Retries across `$PreferredLocation` (honoring platform preference), - falling back to [swedencentral, westeurope, norwayeast] if all fail. + falling back to [swedencentral, westeurope, norwayeast] if all fail. Retries are gated on + `Test-MhhDeploymentFailureRetryable`: a non-retryable failure (template or permission bug) + aborts immediately with the real error rather than being retried in every region. - **Deterministic naming** — `Get-MhhStableHash` generates a stable suffix per attendee, - ensuring names are DNS-safe, globally unique, and consistent across re-runs. + ensuring names are DNS-safe, globally unique, and consistent across re-runs. The hash is + computed **once** and passed to Bicep as `resourceToken`; the template is the single source + of truth for every resource name. - **Idempotent provisioning** — All resources are created once; re-runs skip existing resources. - **Managed identities** — No hardcoded keys or connection strings; all auth uses Entra ID + Azure RBAC (DefaultAzureCredential in notebooks). @@ -129,26 +173,41 @@ The script follows MicroHack conventions: roles cannot create/update products or subscriptions). - **`AcrPush`** on the spoke ACR — required only for Notebook 7's container publishing; granted by default since the ACR already exists in the core deployment. - + + Grants are driven off the **template's own resource-ID outputs**, not a resource-group + lookup, so the hub *and* spoke instances of each duplicated type (Foundry, Key Vault, Log + Analytics, App Insights) are both covered. Every grant is classified Required or Optional: + if any **Required** grant fails, the script throws with a per-scope summary instead of + handing the attendee a lab whose notebooks silently 403. Optional grants (challenges 7–9) + only warn. + So team labs work for all members. - **Robust output** — `[INFO]`, `[OK]`, `[WARN]` prefixes guide troubleshooting. ## Subscription Scope Prerequisites -Before the platform runs `deploy-lab.ps1`, these must be pre-registered at the subscription level: - -- `Microsoft.ApiManagement` -- `Microsoft.CognitiveServices` -- `Microsoft.EventHub` -- `Microsoft.KeyVault` -- `Microsoft.OperationalInsights` -- `Microsoft.DocumentDB` -- `Microsoft.Insights` -- `Microsoft.Storage` -- `Microsoft.ContainerRegistry` -- `Microsoft.Logic` (if Logic App is deployed) - -If a provider is not registered, the script will fail with a clear message: *"Provider X must be registered at subscription scope before lab deployment."* +[`shared-deploy-lab.ps1`](shared-deploy-lab.ps1) registers these providers automatically, +once per subscription, before any `deploy-lab.ps1` runs — participants only hold RG-Owner and +subscription-Reader, so they cannot self-register: + +| Provider | Needed for | +|---|---| +| `Microsoft.ApiManagement` | APIM AI gateway (all challenges) | +| `Microsoft.CognitiveServices` | Foundry hub + spoke accounts, model deployments | +| `Microsoft.DocumentDB` | Cosmos DB usage tracking (challenge 2) | +| `Microsoft.KeyVault` | Hub and spoke Key Vaults | +| `Microsoft.OperationalInsights` | Log Analytics workspaces (challenge 3) | +| `Microsoft.Insights` | Application Insights / agent tracing (challenge 3) | +| `Microsoft.EventHub` | Usage event streaming pipeline | +| `Microsoft.Storage` | Usage ingestion storage account | +| `Microsoft.ManagedIdentity` | User-assigned identities for APIM backend auth | +| `Microsoft.ContainerRegistry` | Spoke ACR (challenge 7) | +| `Microsoft.App` *(optional)* | HR MCP Container App (challenge 9, instructor-led) | +| `Microsoft.AlertsManagement` *(optional)* | Governance alert rules (optional extension) | + +Registration is best-effort by design: if the shared hook throws, the platform runs **no** labs +in that subscription. A provider that cannot be registered is reported with the exact +`az provider register` command for a subscription Owner to run. ## Local Dry Run @@ -172,9 +231,31 @@ After a successful dry run, run `setup-notebook-env.ps1` (see ## Configuration -Edit [`lab-defaults.json`](lab-defaults.json) to tune: -- Regional preferences (adjust for availability) -- Labs per subscription (8 labs × ~15 USD/day = ~120 USD/day per subscription) -- Estimated daily cost estimate +Edit [`lab-defaults.json`](lab-defaults.json) to tune regional preferences, density, and budget. + +**`estimatedDailyCostsUsd: 32.0`** — the Console uses this to size participant budgets, so it +must reflect the real burn rate. Breakdown per lab: + +| Component | ~USD/day | +|---|---| +| API Management **Standard v2** (~$0.95–1.00/hr, always-on) | ~23 | +| Cosmos DB (serverless), Event Hub (Basic), Storage | ~2 | +| 2× Log Analytics + 2× Application Insights (ingestion) | ~3 | +| ACR (Basic), Key Vaults, managed identities | ~1 | +| Model inference (6 deployments, pay-per-token, workshop-scale usage) | ~3 | +| **Total** | **~32** | + +> APIM Standard v2 dominates the cost. Dropping to **Basic v2** would cut the APIM line by +> roughly 7×, but it changes gateway capabilities — treat that as a functional decision, not a +> pure cost tweak. + +**`labsPerSubscription: 8`** — bounded by Azure OpenAI regional quota, not cost: +`8 labs × 100 capacity units per model = 800` units, inside the typical 1 000-unit +GlobalStandard quota per model per region. **If you raise `labsPerSubscription`, or raise the +model `capacity` in `infra/resources.bicep`, re-check that product against the subscription's +quota in the target region** — exceeding it makes later labs fail to deploy their models. + +`estimatedSharedDeploymentDailyCostsUsd: 0.0` — `shared-deploy-lab.ps1` only registers resource +providers and deploys nothing billable. No credentials or secrets are stored in this folder. diff --git a/03-Azure/01-04-AI/05_AI_Governance/labautomation/deploy-lab.ps1 b/03-Azure/01-04-AI/06_AI_Governance/labautomation/deploy-lab.ps1 similarity index 56% rename from 03-Azure/01-04-AI/05_AI_Governance/labautomation/deploy-lab.ps1 rename to 03-Azure/01-04-AI/06_AI_Governance/labautomation/deploy-lab.ps1 index 5c6f7a77a..d5d29e472 100644 --- a/03-Azure/01-04-AI/05_AI_Governance/labautomation/deploy-lab.ps1 +++ b/03-Azure/01-04-AI/06_AI_Governance/labautomation/deploy-lab.ps1 @@ -61,26 +61,6 @@ $ErrorActionPreference = "Stop" # Resolve effective values — honour $PreferredLocation, fall back across the list $candidateRegions = if ($PreferredLocation.Count -gt 0) { $PreferredLocation } else { @("swedencentral", "westeurope", "norwayeast") } -$effectiveLocation = $candidateRegions[0] -$effectiveRG = $ResourceGroupName - -# Deterministic token for resource naming (based on subscription + RG). -# Ensures hub and spoke resources don't collide and survive re-runs. -$stableHash = (Get-MhhStableHash $AllowedEntraUserIds -Length 12).ToLower() -$resourceToken = (Get-MhhStableHash "$SubscriptionId-$effectiveRG" -Length 13).ToLower() - -# Resource naming -$hubFoundryAccountName = "aif-hub-$stableHash" -$hubFoundryProjectName = "citadel-hub-project" -$spokeFoundryAccountName = "aif-spoke-$stableHash" -$spokeFoundryProjectName = "citadel-agents-project" -$apimName = "apim-citadel-$stableHash" -$cosmosName = "cos-citadel-$stableHash" -$keyVaultName = "kv-hub-$stableHash" -$laName = "law-citadel-$stableHash" -$aiName = "appi-citadel-$stableHash" - -Write-Host "[INFO] Deploying Citadel Agentic Governance Hub + Spoke into RG '$effectiveRG'..." $scriptPath = Split-Path -Parent $MyInvocation.MyCommand.Definition $bicepFile = Join-Path $scriptPath 'infra/resources.bicep' @@ -89,7 +69,33 @@ if (-not (Test-Path $bicepFile)) { throw "Bicep template not found at '$bicepFile'. Ensure infra/resources.bicep exists." } -$primaryPrincipalId = if ($AllowedEntraUserIds.Count -gt 0) { $AllowedEntraUserIds[0] } else { '' } +# --- Resolve the resource group per the platform contract ------------------- +# 'resourcegroup' / 'resourcegroup-with-subscriptionowner': the platform pre-created the +# RG and granted Owner on it, so we must NOT create it. +# 'subscription': the platform granted Owner on the subscription and created nothing — +# we create the RG ourselves under a deterministic per-lab name. lab-defaults.json pins +# this lab to 'resourcegroup', but the parameter contract allows all three values, so +# the subscription path is implemented rather than left to fail with an empty RG name. +if ($DeploymentType -eq 'subscription') { + $hashInput = if ($AllowedEntraUserIds.Count -gt 0) { $AllowedEntraUserIds } else { @($SubscriptionId) } + $rgHash = (Get-MhhStableHash $hashInput -Length 24).ToLower() + $effectiveRG = "rg-citadel-microhack-$rgHash" + Write-Host "[INFO] Subscription-scoped lab — creating resource group '$effectiveRG' in '$($candidateRegions[0])'..." + New-AzResourceGroup -Name $effectiveRG -Location $candidateRegions[0] -Force | Out-Null +} +else { + $effectiveRG = $ResourceGroupName + if ([string]::IsNullOrWhiteSpace($effectiveRG)) { + throw "DeploymentType '$DeploymentType' requires the platform to pass a pre-created -ResourceGroupName, but it was empty." + } +} + +# Deterministic token for resource naming (subscription + RG based, so it is stable +# across region retries and re-runs). infra/resources.bicep derives EVERY hub and spoke +# resource name from this token — resource names must not be recomputed here, or the +# script and the template disagree about what was actually deployed. +$resourceToken = (Get-MhhStableHash "$SubscriptionId-$effectiveRG" -Length 13).ToLower() + $tags = @{ project = 'citadel-agentic-governance'; 'lab-deployment-type' = $DeploymentType } # Guard: ALL participant data-plane RBAC is keyed off $AllowedEntraUserIds. @@ -101,21 +107,20 @@ if ($AllowedEntraUserIds.Count -eq 0) { Write-Host "[WARN] pass -AllowedEntraUserIds (comma-separate ids for a team lab)." } +Write-Host "[INFO] Deploying Citadel Agentic Governance Hub + Spoke into RG '$effectiveRG'..." +Write-Host "[INFO] Engine: Bicep (infra/resources.bicep), resource-group-scoped." + $deployOutputs = $null $effectiveLocation = $null -Write-Host "[INFO] Engine: Bicep (infra/resources.bicep), resource-group-scoped." - foreach ($region in $candidateRegions) { - Write-Host "[INFO] Deploying Bicep → RG '$effectiveRG' in '$region'..." + Write-Host "[INFO] Deploying Bicep → RG '$effectiveRG' in '$region' (token '$resourceToken')..." try { $d = New-AzResourceGroupDeployment ` -ResourceGroupName $effectiveRG ` -TemplateFile $bicepFile ` -location $region ` -resourceToken $resourceToken ` - -principalId $primaryPrincipalId ` - -principalType 'User' ` -tags $tags ` -ErrorAction Stop $deployOutputs = $d.Outputs @@ -123,6 +128,16 @@ foreach ($region in $candidateRegions) { break } catch { + # Only capacity/quota/region-availability failures are worth retrying elsewhere. + # A template or permission bug fails identically in every region, so retrying it + # just triples the runtime and buries the real error under the last region's message. + $retryable = $true + if (Get-Command Test-MhhDeploymentFailureRetryable -ErrorAction SilentlyContinue) { + $retryable = [bool](Test-MhhDeploymentFailureRetryable -ErrorRecord $_) + } + if (-not $retryable) { + throw "Bicep deployment failed in '$region' with a non-retryable error (retrying other regions would fail the same way): $_" + } Write-Host "[WARN] Bicep deployment failed in '$region': $_ — trying next region." } } @@ -133,280 +148,213 @@ if (-not $deployOutputs) { Write-Host "[OK] Provisioning complete in '$effectiveLocation' (resource group '$effectiveRG')." -# --- Managed Identity Readiness Check --- -# Hub and Spoke Foundry accounts have system-assigned managed identities that may not be -# immediately available after creation. Wait for them to be readable before granting RBAC. -Write-Host "[INFO] Verifying managed identity availability..." -$hubAccount = Get-AzResource ` - -ResourceGroupName $effectiveRG ` - -ResourceType 'Microsoft.CognitiveServices/accounts' ` - -ErrorAction SilentlyContinue | Where-Object { $_.Name -like "aif-hub-*" } | Select-Object -First 1 - -$spokeAccount = Get-AzResource ` - -ResourceGroupName $effectiveRG ` - -ResourceType 'Microsoft.CognitiveServices/accounts' ` - -ErrorAction SilentlyContinue | Where-Object { $_.Name -like "aif-spoke-*" } | Select-Object -First 1 - -$maxWaitSecs = 120 -$waitInterval = 10 -$elapsed = 0 - -while ($elapsed -lt $maxWaitSecs) { - $hubMIReady = $false - $spokeMIReady = $false - - if ($hubAccount) { - $hubMI = Get-AzResource -ResourceId $hubAccount.ResourceId -ExpandProperties -ErrorAction SilentlyContinue - if ($hubMI -and $hubMI.Identity.PrincipalId) { - $hubMIReady = $true - } - } - - if ($spokeAccount) { - $spokeMI = Get-AzResource -ResourceId $spokeAccount.ResourceId -ExpandProperties -ErrorAction SilentlyContinue - if ($spokeMI -and $spokeMI.Identity.PrincipalId) { - $spokeMIReady = $true - } - } - - if ($hubMIReady -and $spokeMIReady) { - Write-Host "[OK] Managed identities are ready." - break - } - - if ($elapsed -eq 0) { - Write-Host "[INFO] Waiting for managed identities to become available... (up to ${maxWaitSecs}s)" - } - - Start-Sleep -Seconds $waitInterval - $elapsed += $waitInterval +# --- Deployment output helper (used by the RBAC block and the dashboard block) --- +function Get-OutVal { + param($Outputs, [string]$Key) + if ($Outputs -and $Outputs.ContainsKey($Key)) { return "$($Outputs[$Key].Value)" } + return '' } -if ($hubAccount -and -not $hubMIReady) { - Write-Host "[WARN] Hub Foundry managed identity did not become ready within ${maxWaitSecs}s. RBAC grants may fail." +# --- Managed identity sanity check --- +# The hub and spoke Foundry accounts carry system-assigned identities that APIM and the +# notebooks depend on. The template exports their principal ids, so an empty value here +# means the account came up without an identity and downstream auth will fail. +foreach ($mi in @( + @{ Label = 'hub Foundry'; Value = (Get-OutVal $deployOutputs 'HUB_FOUNDRY_MANAGED_IDENTITY_PRINCIPAL_ID') } + @{ Label = 'spoke Foundry'; Value = (Get-OutVal $deployOutputs 'SPOKE_FOUNDRY_MANAGED_IDENTITY_PRINCIPAL_ID') } + )) { + if ([string]::IsNullOrWhiteSpace($mi.Value)) { + Write-Host "[WARN] The $($mi.Label) account reported no managed identity — agent tracing and backend auth may fail." + } } -# --- Multi-user data-plane RBAC --- -# Grant Foundry and Cosmos DB access to all attendees so team labs work for every member. -if ($AllowedEntraUserIds.Count -gt 0 -and $effectiveRG) { - $account = $hubAccount - - $cosmos = Get-AzResource ` - -ResourceGroupName $effectiveRG ` - -ResourceType 'Microsoft.DocumentDB/databaseAccounts' ` - -ErrorAction SilentlyContinue | Select-Object -First 1 - - $keyVault = Get-AzResource ` - -ResourceGroupName $effectiveRG ` - -ResourceType 'Microsoft.KeyVault/vaults' ` - -ErrorAction SilentlyContinue | Select-Object -First 1 - - $appInsightsRes = Get-AzResource ` - -ResourceGroupName $effectiveRG ` - -ResourceType 'Microsoft.Insights/components' ` - -ErrorAction SilentlyContinue | Select-Object -First 1 - - $logAnalyticsRes = Get-AzResource ` - -ResourceGroupName $effectiveRG ` - -ResourceType 'Microsoft.OperationalInsights/workspaces' ` - -ErrorAction SilentlyContinue | Select-Object -First 1 - - $apim = Get-AzResource ` - -ResourceGroupName $effectiveRG ` - -ResourceType 'Microsoft.ApiManagement/service' ` - -ErrorAction SilentlyContinue | Select-Object -First 1 - - $acr = Get-AzResource ` - -ResourceGroupName $effectiveRG ` - -ResourceType 'Microsoft.ContainerRegistry/registries' ` - -ErrorAction SilentlyContinue | Select-Object -First 1 - - # RBAC role IDs (built-in) - $foundryRoles = [ordered]@{ - 'Azure AI User' = '53ca6127-db72-4b80-b1b0-d745d6d5456d' - 'Cognitive Services User' = 'a97b65f3-24c7-4388-baec-2e87135dc908' - } +# --------------------------------------------------------------------------- +# Participant RBAC +# +# Scopes come from the template outputs, NOT from `Get-AzResource ... | Select -First 1`. +# This lab packs a hub AND a spoke of four resource types (Foundry account, Key Vault, +# Log Analytics workspace, Application Insights) into ONE resource group, so a +# first-match probe granted the participant access to only one of each pair and left +# notebooks 3, 4 and 7-9 failing at runtime with an authorization error. +# +# Grants flagged Required are the ones the core challenges (1-6) cannot run without. +# If any of them fails, the script throws at the end so the platform reports a failed +# provision instead of handing the attendee a lab whose notebooks break on first use. +# --------------------------------------------------------------------------- +$hubFoundryId = Get-OutVal $deployOutputs 'HUB_FOUNDRY_ACCOUNT_RESOURCE_ID' +$spokeFoundryId = Get-OutVal $deployOutputs 'SPOKE_FOUNDRY_ACCOUNT_RESOURCE_ID' +$cosmosId = Get-OutVal $deployOutputs 'COSMOS_ACCOUNT_RESOURCE_ID' +$hubKvId = Get-OutVal $deployOutputs 'KEY_VAULT_RESOURCE_ID' +$spokeKvId = Get-OutVal $deployOutputs 'SPOKE_KEY_VAULT_RESOURCE_ID' +$hubLaId = Get-OutVal $deployOutputs 'LOG_ANALYTICS_WORKSPACE_RESOURCE_ID' +$spokeLaId = Get-OutVal $deployOutputs 'SPOKE_LOG_ANALYTICS_WORKSPACE_RESOURCE_ID' +$hubAiId = Get-OutVal $deployOutputs 'APP_INSIGHTS_RESOURCE_ID' +$spokeAiId = Get-OutVal $deployOutputs 'SPOKE_APP_INSIGHTS_RESOURCE_ID' +$apimResourceId = Get-OutVal $deployOutputs 'APIM_RESOURCE_ID' +$acrId = Get-OutVal $deployOutputs 'SPOKE_ACR_RESOURCE_ID' + +# RBAC role IDs (built-in) +$foundryRoles = [ordered]@{ + 'Azure AI User' = '53ca6127-db72-4b80-b1b0-d745d6d5456d' + 'Cognitive Services User' = 'a97b65f3-24c7-4388-baec-2e87135dc908' +} + +# The spoke is the attendee's own build space: challenges 7-9 create and manage Foundry +# agents, hosted-agent versions and project connections there. Those are project data-plane +# management operations, which RG-Owner does NOT cover (Owner grants actions:* but no +# dataActions). The original workshop grants the signed-in user 'Azure AI Project Manager' +# on the spoke account in deploy-spoke-foundry.ps1 for exactly this reason. Kept as its own +# Optional target so a tenant that restricts this role fails challenges 7-9 only, rather +# than failing the whole provision for the core challenges 1-6. +$spokeProjectMgmtRoles = [ordered]@{ + 'Azure AI Project Manager' = 'eadc314b-1a2d-4efa-be10-5d325db5065e' +} + +$keyVaultRoles = [ordered]@{ + 'Key Vault Secrets Officer' = 'b86a8fe4-44ce-4948-aee5-eccb2c155090' +} + +$monitoringRoles = [ordered]@{ + 'Monitoring Reader' = '43d0d8ad-25c7-4714-9337-8ba259a9fe05' +} + +# APIMClientTool (used by notebooks 3-7, 9) is an ARM control-plane client — it needs +# a management-plane role on the APIM resource, not just a gateway subscription key, +# to list APIs/subscriptions and to create products/subscriptions dynamically. +# 'API Management Service Contributor' is the least-privileged BUILT-IN role that covers +# both the service and its entities (APIs, products, subscriptions, policies). +# 'API Management Service Operator Role' only manages the service itself (not entities), +# and 'API Management Service Reader Role' is read-only — neither supports the +# create/update-product/subscription flows the notebooks perform. +$apimRoles = [ordered]@{ + 'API Management Service Contributor' = '312a565d-c81f-4fd8-895a-4e21e48d571c' +} + +# Notebook 7 pushes container images to the spoke ACR — AcrPush is the least-privileged +# built-in role for that (data-plane push/pull only, no control-plane permissions). +$acrRoles = [ordered]@{ + 'AcrPush' = '8311e382-0749-4cb8-b61a-304f252e45ec' +} + +# Required = a core challenge (1-6) breaks without it. +# Optional = only an observability view or a follow-on challenge (7-9) is affected. +$rbacTargets = @( + @{ Label = 'hub Foundry account'; Scope = $hubFoundryId; Roles = $foundryRoles; Required = $true } + @{ Label = 'spoke Foundry account'; Scope = $spokeFoundryId; Roles = $foundryRoles; Required = $true } + @{ Label = 'spoke Foundry account (project management, challenges 7-9)'; Scope = $spokeFoundryId; Roles = $spokeProjectMgmtRoles; Required = $false } + @{ Label = 'hub Key Vault'; Scope = $hubKvId; Roles = $keyVaultRoles; Required = $true } + @{ Label = 'spoke Key Vault'; Scope = $spokeKvId; Roles = $keyVaultRoles; Required = $true } + @{ Label = 'APIM service'; Scope = $apimResourceId; Roles = $apimRoles; Required = $true } + @{ Label = 'hub Log Analytics'; Scope = $hubLaId; Roles = $monitoringRoles; Required = $false } + @{ Label = 'spoke Log Analytics'; Scope = $spokeLaId; Roles = $monitoringRoles; Required = $false } + @{ Label = 'hub App Insights'; Scope = $hubAiId; Roles = $monitoringRoles; Required = $false } + @{ Label = 'spoke App Insights'; Scope = $spokeAiId; Roles = $monitoringRoles; Required = $false } + @{ Label = 'spoke ACR'; Scope = $acrId; Roles = $acrRoles; Required = $false } +) - $cosmosDataRole = '00000000-0000-0000-0000-000000000002' # Cosmos DB Built-in Data Contributor +$rbacFailures = New-Object System.Collections.Generic.List[string] - $keyVaultRoles = [ordered]@{ - 'Key Vault Secrets Officer' = 'b86a8fe4-44ce-4948-aee5-eccb2c155090' - } +function Grant-MhhRole { + param([string]$ObjectId, [string]$RoleName, [string]$RoleId, [string]$Scope, [string]$Label) - $monitoringRoles = [ordered]@{ - 'Monitoring Reader' = '43d0d8ad-25c7-4714-9337-8ba259a9fe05' + $existing = Get-AzRoleAssignment -ObjectId $ObjectId -Scope $Scope -RoleDefinitionId $RoleId -ErrorAction SilentlyContinue + if ($existing) { + Write-Host "[OK] '$RoleName' already granted to $ObjectId on the $Label." + return $true } - - # APIMClientTool (used by notebooks 3-7, 9) is an ARM control-plane client — it needs - # a management-plane role on the APIM resource, not just a gateway subscription key, - # to list APIs/subscriptions and to create products/subscriptions dynamically. - # 'API Management Service Contributor' is the least-privileged BUILT-IN role that covers - # both the service and its entities (APIs, products, subscriptions, policies). - # 'API Management Service Operator Role' only manages the service itself (not entities), - # and 'API Management Service Reader Role' is read-only — neither supports the - # create/update-product/subscription flows the notebooks perform. - $apimRoles = [ordered]@{ - 'API Management Service Contributor' = '312a565d-c81f-4fd8-895a-4e21e48d571c' + try { + New-AzRoleAssignment -ObjectId $ObjectId -RoleDefinitionId $RoleId -Scope $Scope -ErrorAction Stop | Out-Null + Write-Host "[OK] Granted '$RoleName' to $ObjectId on the $Label." + return $true } - - # Notebook 7 pushes container images to the spoke ACR — AcrPush is the least-privileged - # built-in role for that (data-plane push/pull only, no control-plane permissions). - $acrRoles = [ordered]@{ - 'AcrPush' = '8311e382-0749-4cb8-b61a-304f252e45ec' + catch { + Write-Host "[WARN] Could not grant '$RoleName' to $ObjectId on the ${Label}: $_" + return $false } +} - foreach ($userId in $AllowedEntraUserIds) { - # Foundry data-plane RBAC - if ($account) { - foreach ($roleName in $foundryRoles.Keys) { - $roleId = $foundryRoles[$roleName] - $existing = Get-AzRoleAssignment -ObjectId $userId -Scope $account.ResourceId -RoleDefinitionId $roleId -ErrorAction SilentlyContinue - if ($existing) { - Write-Host "[OK] '$roleName' already granted to $userId on Foundry." - continue - } - try { - New-AzRoleAssignment -ObjectId $userId -RoleDefinitionId $roleId -Scope $account.ResourceId -ErrorAction Stop | Out-Null - Write-Host "[OK] Granted '$roleName' to $userId on Foundry." - } catch { - Write-Host "[WARN] Could not grant '$roleName' to ${userId}: $_" - } - } - } +function Grant-MhhCosmosDataRole { + param([string]$ObjectId, [string]$CosmosResourceId) - # Cosmos DB data-plane RBAC (via REST because Az.CosmosDB module is complex) - if ($cosmos) { - $cosmosBase = $cosmos.ResourceId - $cosmosApi = "2024-11-15" - $cosmosRoleUri = "$cosmosBase/sqlRoleDefinitions/00000000-0000-0000-0000-000000000002?api-version=$cosmosApi" - $existingRAs = Invoke-AzRestMethod -Method GET -Path "$cosmosBase/sqlRoleAssignments?api-version=$cosmosApi" -ErrorAction SilentlyContinue - $raList = if ($existingRAs.StatusCode -eq 200) { ($existingRAs.Content | ConvertFrom-Json).value } else { @() } - - $already = $raList | Where-Object { $_.properties.principalId -eq $userId -and $_.properties.roleDefinitionId -like "*000000000002" } - if ($already) { - Write-Host "[OK] Cosmos DB data role already granted to $userId." - continue - } + $cosmosApi = '2024-11-15' + $roleDefinitionId = "$CosmosResourceId/sqlRoleDefinitions/00000000-0000-0000-0000-000000000002" # Built-in Data Contributor - try { - $assignId = [guid]::NewGuid().ToString() - $raBody = @{ properties = @{ - roleDefinitionId = $cosmosRoleUri - principalId = $userId - scope = $cosmosBase - } } | ConvertTo-Json -Depth 5 - Invoke-AzRestMethod -Method PUT -Path "$cosmosBase/sqlRoleAssignments/${assignId}?api-version=$cosmosApi" -Payload $raBody -ErrorAction Stop | Out-Null - Write-Host "[OK] Cosmos DB data-plane role granted to $userId." - } catch { - Write-Host "[WARN] Could not grant Cosmos DB role to ${userId}: $_" - } + $list = Invoke-AzRestMethod -Method GET -Path "$CosmosResourceId/sqlRoleAssignments?api-version=$cosmosApi" -ErrorAction SilentlyContinue + if ($list -and $list.StatusCode -eq 200) { + $already = ($list.Content | ConvertFrom-Json).value | Where-Object { + $_.properties.principalId -eq $ObjectId -and $_.properties.roleDefinitionId -like '*000000000002' } - - # Key Vault RBAC - if ($keyVault) { - foreach ($roleName in $keyVaultRoles.Keys) { - $roleId = $keyVaultRoles[$roleName] - $existing = Get-AzRoleAssignment -ObjectId $userId -Scope $keyVault.ResourceId -RoleDefinitionId $roleId -ErrorAction SilentlyContinue - if ($existing) { - Write-Host "[OK] '$roleName' already granted to $userId on Key Vault." - continue - } - try { - New-AzRoleAssignment -ObjectId $userId -RoleDefinitionId $roleId -Scope $keyVault.ResourceId -ErrorAction Stop | Out-Null - Write-Host "[OK] Granted '$roleName' to $userId on Key Vault." - } catch { - Write-Host "[WARN] Could not grant '$roleName' to ${userId}: $_" - } - } + if ($already) { + Write-Host "[OK] Cosmos DB data role already granted to $ObjectId." + return $true } + } - # Monitoring RBAC (Log Analytics + App Insights) - if ($logAnalyticsRes) { - foreach ($roleName in $monitoringRoles.Keys) { - $roleId = $monitoringRoles[$roleName] - $existing = Get-AzRoleAssignment -ObjectId $userId -Scope $logAnalyticsRes.ResourceId -RoleDefinitionId $roleId -ErrorAction SilentlyContinue - if ($existing) { - Write-Host "[OK] '$roleName' already granted to $userId on Log Analytics." - continue - } - try { - New-AzRoleAssignment -ObjectId $userId -RoleDefinitionId $roleId -Scope $logAnalyticsRes.ResourceId -ErrorAction Stop | Out-Null - Write-Host "[OK] Granted '$roleName' to $userId on Log Analytics." - } catch { - Write-Host "[WARN] Could not grant '$roleName' to ${userId}: $_" - } - } + $body = @{ properties = @{ + roleDefinitionId = $roleDefinitionId + principalId = $ObjectId + scope = $CosmosResourceId } + } | ConvertTo-Json -Depth 5 - if ($appInsightsRes) { - foreach ($roleName in $monitoringRoles.Keys) { - $roleId = $monitoringRoles[$roleName] - $existing = Get-AzRoleAssignment -ObjectId $userId -Scope $appInsightsRes.ResourceId -RoleDefinitionId $roleId -ErrorAction SilentlyContinue - if ($existing) { - Write-Host "[OK] '$roleName' already granted to $userId on Application Insights." - continue - } - try { - New-AzRoleAssignment -ObjectId $userId -RoleDefinitionId $roleId -Scope $appInsightsRes.ResourceId -ErrorAction Stop | Out-Null - Write-Host "[OK] Granted '$roleName' to $userId on Application Insights." - } catch { - Write-Host "[WARN] Could not grant '$roleName' to ${userId}: $_" - } - } - } + $assignId = [guid]::NewGuid().ToString() + try { + $resp = Invoke-AzRestMethod -Method PUT -Path "$CosmosResourceId/sqlRoleAssignments/${assignId}?api-version=$cosmosApi" -Payload $body -ErrorAction Stop + } + catch { + Write-Host "[WARN] Could not grant the Cosmos DB data role to ${ObjectId}: $_" + return $false + } - # APIM management RBAC (APIMClientTool: list APIs/subscriptions, create/update - # products/subscriptions dynamically — notebooks 3-7, 9) - if ($apim) { - foreach ($roleName in $apimRoles.Keys) { - $roleId = $apimRoles[$roleName] - $existing = Get-AzRoleAssignment -ObjectId $userId -Scope $apim.ResourceId -RoleDefinitionId $roleId -ErrorAction SilentlyContinue - if ($existing) { - Write-Host "[OK] '$roleName' already granted to $userId on APIM." - continue - } - try { - New-AzRoleAssignment -ObjectId $userId -RoleDefinitionId $roleId -Scope $apim.ResourceId -ErrorAction Stop | Out-Null - Write-Host "[OK] Granted '$roleName' to $userId on APIM." - } catch { - Write-Host "[WARN] Could not grant '$roleName' to ${userId}: $_" - } - } - } else { - Write-Host "[WARN] No APIM resource found in RG '$effectiveRG' — skipping APIM management RBAC." + # Invoke-AzRestMethod does NOT throw on an HTTP error status, so it must be checked + # explicitly — otherwise a 403/404 was reported to the coach as a successful grant. + if ($resp.StatusCode -ge 200 -and $resp.StatusCode -lt 300) { + Write-Host "[OK] Cosmos DB data-plane role granted to $ObjectId." + return $true + } + + Write-Host "[WARN] Cosmos DB role assignment for $ObjectId failed with HTTP $($resp.StatusCode): $($resp.Content)" + return $false +} + +# --- Multi-user data-plane RBAC --- +# Every id in $AllowedEntraUserIds is granted every role, so team labs work for all members. +foreach ($userId in $AllowedEntraUserIds) { + foreach ($target in $rbacTargets) { + if ([string]::IsNullOrWhiteSpace($target.Scope)) { + $message = "the $($target.Label) was not returned by the deployment, so its roles were skipped" + if ($target.Required) { $rbacFailures.Add("$userId : $message") } + else { Write-Host "[WARN] Skipping the $($target.Label) — the deployment returned no resource id for it." } + continue } - # ACR RBAC (optional — required only for Notebook 7's container publishing) - if ($acr) { - foreach ($roleName in $acrRoles.Keys) { - $roleId = $acrRoles[$roleName] - $existing = Get-AzRoleAssignment -ObjectId $userId -Scope $acr.ResourceId -RoleDefinitionId $roleId -ErrorAction SilentlyContinue - if ($existing) { - Write-Host "[OK] '$roleName' already granted to $userId on ACR." - continue - } - try { - New-AzRoleAssignment -ObjectId $userId -RoleDefinitionId $roleId -Scope $acr.ResourceId -ErrorAction Stop | Out-Null - Write-Host "[OK] Granted '$roleName' to $userId on ACR." - } catch { - Write-Host "[WARN] Could not grant '$roleName' to ${userId}: $_" - } + foreach ($roleName in $target.Roles.Keys) { + $granted = Grant-MhhRole -ObjectId $userId -RoleName $roleName -RoleId $target.Roles[$roleName] -Scope $target.Scope -Label $target.Label + if (-not $granted -and $target.Required) { + $rbacFailures.Add("$userId is missing '$roleName' on the $($target.Label)") } - } else { - Write-Host "[WARN] No ACR found in RG '$effectiveRG' — Notebook 7 (container publishing) will be blocked." } } + + # Cosmos DB data-plane RBAC (REST — the data roles are not ARM role assignments) + if ([string]::IsNullOrWhiteSpace($cosmosId)) { + $rbacFailures.Add("$userId is missing the Cosmos DB data role (the deployment returned no Cosmos account id)") + } + elseif (-not (Grant-MhhCosmosDataRole -ObjectId $userId -CosmosResourceId $cosmosId)) { + $rbacFailures.Add("$userId is missing 'Cosmos DB Built-in Data Contributor' on the usage-tracking account") + } +} + +# Fail loudly rather than reporting a green provision over a broken lab. +if ($rbacFailures.Count -gt 0) { + Write-Host "[ERROR] Provisioning finished, but required participant access is incomplete:" + foreach ($failure in $rbacFailures) { Write-Host "[ERROR] - $failure" } + throw "Required participant RBAC could not be applied ($($rbacFailures.Count) grant(s) failed). Resources exist in '$effectiveRG', but the notebooks would fail at runtime, so this lab is reported as failed." } # --- Return credentials to the attendee dashboard --- Write-Host "[OK] Lab provisioning complete." -function Get-OutVal { - param($Outputs, [string]$Key) - if ($Outputs -and $Outputs.ContainsKey($Key)) { return "$($Outputs[$Key].Value)" } - return '' -} - # Extract outputs from Bicep deployment $hubFoundryEndpoint = Get-OutVal $deployOutputs 'HUB_FOUNDRY_PROJECT_ENDPOINT' $spokeFoundryEndpoint = Get-OutVal $deployOutputs 'SPOKE_FOUNDRY_PROJECT_ENDPOINT' diff --git a/03-Azure/01-04-AI/05_AI_Governance/labautomation/docs/phase4-evidence-tables.md b/03-Azure/01-04-AI/06_AI_Governance/labautomation/docs/phase4-evidence-tables.md similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/labautomation/docs/phase4-evidence-tables.md rename to 03-Azure/01-04-AI/06_AI_Governance/labautomation/docs/phase4-evidence-tables.md diff --git a/03-Azure/01-04-AI/05_AI_Governance/labautomation/infra/resources.bicep b/03-Azure/01-04-AI/06_AI_Governance/labautomation/infra/resources.bicep similarity index 94% rename from 03-Azure/01-04-AI/05_AI_Governance/labautomation/infra/resources.bicep rename to 03-Azure/01-04-AI/06_AI_Governance/labautomation/infra/resources.bicep index f2b0dffde..3b8c55e9c 100644 --- a/03-Azure/01-04-AI/05_AI_Governance/labautomation/infra/resources.bicep +++ b/03-Azure/01-04-AI/06_AI_Governance/labautomation/infra/resources.bicep @@ -16,12 +16,11 @@ param location string @description('Deterministic suffix for resource naming (DNS-safe, lowercase).') param resourceToken string -@description('Primary Entra user object ID for initial RBAC grants. Multi-user RBAC is applied post-deployment.') -param principalId string = '' - -@description('Principal type for RBAC assignments: User (interactive) or ServicePrincipal (CI/CD).') -@allowed(['User', 'ServicePrincipal']) -param principalType string = 'User' +// Participant RBAC is applied by labautomation/deploy-lab.ps1 AFTER this deployment, +// so that EVERY id in $AllowedEntraUserIds is granted the roles (a template can only +// conveniently grant the first one) and so that hub AND spoke resources are both +// covered. This template therefore takes no principalId: it exports the resource ids +// the script needs as RBAC scopes (see the *_RESOURCE_ID outputs at the bottom). @description('Tags to apply to all resources.') param tags object = {} @@ -1125,11 +1124,35 @@ resource acrSpoke 'Microsoft.ContainerRegistry/registries@2023-07-01' = { name: 'Basic' } properties: { - adminUserEnabled: true + // Challenge 7 builds with `az acr build` (cloud build) and the hosted agent pulls via + // the project managed identity below, so no admin username/password is ever needed. + // Matches the workshop's `--admin-enabled false`. + adminUserEnabled: false publicNetworkAccess: 'Enabled' } } +// ===== Spoke: AcrPull for the Foundry project managed identity ===== +// Challenge 7 deploys a hosted agent whose image lives in the spoke ACR. The Foundry +// project's managed identity is what pulls that image at agent-start time, so without +// this grant the agent version deploys and then fails to start with an image-pull error. +// The original workshop applies this in deploy-spoke-foundry.ps1 ("Assigning AcrPull to +// Foundry project managed identity"); it is reproduced here because this lab has no +// separate spoke script. Participant-facing RBAC stays in deploy-lab.ps1 — this one is +// service-to-service, so it belongs with the resources. +resource acrPullForSpokeProject 'Microsoft.Authorization/roleAssignments@2022-04-01' = { + scope: acrSpoke + name: guid(acrSpoke.id, spokeFoundryProject.id, '7f951dda-4ed3-4680-a7ca-43fe172d538d') + properties: { + roleDefinitionId: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '7f951dda-4ed3-4680-a7ca-43fe172d538d' // AcrPull + ) + principalId: spokeFoundryProject.identity.principalId + principalType: 'ServicePrincipal' + } +} + // ===== Connect App Insights to Hub Foundry for Agent Tracing ===== resource hubFoundryAppInsightsConnection 'Microsoft.CognitiveServices/accounts/connections@2025-04-01-preview' = { parent: hubFoundryAccount @@ -1282,3 +1305,24 @@ output SPOKE_ACR_LOGIN_SERVER string = '${replace(spokeAcrName, '-', '')}.azurec output RESOURCE_GROUP_NAME string = resourceGroup().name output LOCATION string = location + +// ========================================================================== +// RBAC scopes consumed by labautomation/deploy-lab.ps1 +// +// The lab packs a hub AND a spoke of four resource types (Foundry account, Key +// Vault, Log Analytics workspace, Application Insights) into ONE resource group, +// so the script must not discover them with `Get-AzResource ... | Select -First 1` +// — that grants the participant access to only one of each pair. These outputs +// give it the exact scope for every grant. +// ========================================================================== +output COSMOS_ACCOUNT_RESOURCE_ID string = cosmosAccount.id +output APIM_RESOURCE_ID string = apim.id + +output KEY_VAULT_RESOURCE_ID string = kvHub.id +output APP_INSIGHTS_RESOURCE_ID string = aiHub.id + +output SPOKE_FOUNDRY_PROJECT_RESOURCE_ID string = spokeFoundryProject.id +output SPOKE_KEY_VAULT_RESOURCE_ID string = kvSpoke.id +output SPOKE_LOG_ANALYTICS_WORKSPACE_RESOURCE_ID string = laSpoke.id +output SPOKE_APP_INSIGHTS_RESOURCE_ID string = aiSpoke.id +output SPOKE_ACR_RESOURCE_ID string = acrSpoke.id diff --git a/03-Azure/01-04-AI/05_AI_Governance/labautomation/infra/resources.json b/03-Azure/01-04-AI/06_AI_Governance/labautomation/infra/resources.json similarity index 83% rename from 03-Azure/01-04-AI/05_AI_Governance/labautomation/infra/resources.json rename to 03-Azure/01-04-AI/06_AI_Governance/labautomation/infra/resources.json index 4aed55d5d..0e483cf71 100644 --- a/03-Azure/01-04-AI/05_AI_Governance/labautomation/infra/resources.json +++ b/03-Azure/01-04-AI/06_AI_Governance/labautomation/infra/resources.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "0.41.2.15936", - "templateHash": "1184583719695337759" + "templateHash": "17782287074782196978" } }, "parameters": { @@ -23,24 +23,6 @@ "description": "Deterministic suffix for resource naming (DNS-safe, lowercase)." } }, - "principalId": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Primary Entra user object ID for initial RBAC grants. Multi-user RBAC is applied post-deployment." - } - }, - "principalType": { - "type": "string", - "defaultValue": "User", - "allowedValues": [ - "User", - "ServicePrincipal" - ], - "metadata": { - "description": "Principal type for RBAC assignments: User (interactive) or ServicePrincipal (CI/CD)." - } - }, "tags": { "type": "object", "defaultValue": {}, @@ -72,7 +54,6 @@ "cosmosApi": "2024-11-15", "aiServicesApi": "2025-04-01-preview", "cosmosDatabaseName": "citadel-hub", - "deployPolicyFragments": false, "cosmosContainers": [ { "name": "usage-events", @@ -367,8 +348,22 @@ } }, "dependsOn": [ - "[resourceId('Microsoft.ApiManagement/service', variables('apimName'))]", - "[resourceId('Microsoft.CognitiveServices/accounts/projects', variables('hubFoundryAccountName'), variables('hubFoundryProjectName'))]" + "[resourceId('Microsoft.ApiManagement/service', variables('apimName'))]" + ] + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.CognitiveServices/accounts', variables('hubFoundryAccountName'))]", + "name": "[guid(resourceId('Microsoft.CognitiveServices/accounts', variables('hubFoundryAccountName')), resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', variables('apimMIName')), 'CognitiveServicesOpenAIUser')]", + "properties": { + "principalId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', variables('apimMIName')), '2023-01-31').principalId]", + "principalType": "ServicePrincipal", + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '5e0bd9bd-7b93-4f28-af87-19fc36ad61bd')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', variables('apimMIName'))]", + "[resourceId('Microsoft.CognitiveServices/accounts', variables('hubFoundryAccountName'))]" ] }, { @@ -377,7 +372,7 @@ "name": "[format('{0}/{1}', variables('apimName'), 'get-available-models')]", "properties": { "format": "xml", - "value": "application/json@{var models = new JArray(); models.Add(\"gpt-4.1\"); models.Add(\"gpt-5.4-mini\"); models.Add(\"gpt-5.2\"); models.Add(\"Mistral-Large-3\"); models.Add(\"Phi-4\"); models.Add(\"text-embedding-3-large\"); return models.ToString();}" + "value": "application/json@{var models = new JArray(); models.Add(\"gpt-4.1\"); models.Add(\"gpt-5.4-mini\"); models.Add(\"gpt-5.2\"); models.Add(\"text-embedding-3-large\"); models.Add(\"Mistral-Large-3\"); models.Add(\"Phi-4\"); return models.ToString();}" }, "dependsOn": [ "[resourceId('Microsoft.ApiManagement/service', variables('apimName'))]" @@ -418,6 +413,18 @@ "[resourceId('Microsoft.ApiManagement/service/apis', variables('apimName'), 'universal-llm-api')]" ] }, + { + "type": "Microsoft.ApiManagement/service/apis/operations/policies", + "apiVersion": "2023-09-01-preview", + "name": "[format('{0}/{1}/{2}/{3}', variables('apimName'), 'universal-llm-api', 'get-models', 'policy')]", + "properties": { + "format": "xml", + "value": "application/json[\"gpt-4.1\",\"gpt-5.4-mini\",\"gpt-5.2\",\"text-embedding-3-large\",\"Mistral-Large-3\",\"Phi-4\"]" + }, + "dependsOn": [ + "[resourceId('Microsoft.ApiManagement/service/apis/operations', variables('apimName'), 'universal-llm-api', 'get-models')]" + ] + }, { "type": "Microsoft.ApiManagement/service/apis/operations", "apiVersion": "2023-09-01-preview", @@ -432,6 +439,20 @@ "[resourceId('Microsoft.ApiManagement/service/apis', variables('apimName'), 'universal-llm-api')]" ] }, + { + "type": "Microsoft.ApiManagement/service/apis/operations/policies", + "apiVersion": "2023-09-01-preview", + "name": "[format('{0}/{1}/{2}/{3}', variables('apimName'), 'universal-llm-api', 'post-chat-completions', 'policy')]", + "properties": { + "format": "xml", + "value": "@(\"Bearer \" + (string)context.Variables[\"msi-access-token\"])application/json@{return new JObject(new JProperty(\"error\",\"unsupported model requested for chat completions\"),new JProperty(\"requestedModel\",(string)context.Variables[\"requestedModel\"])).ToString();}" + }, + "dependsOn": [ + "[resourceId('Microsoft.ApiManagement/service/backends', variables('apimName'), 'backend-hub-foundry')]", + "[resourceId('Microsoft.ApiManagement/service/namedValues', variables('apimName'), 'uami-client-id')]", + "[resourceId('Microsoft.ApiManagement/service/apis/operations', variables('apimName'), 'universal-llm-api', 'post-chat-completions')]" + ] + }, { "type": "Microsoft.ApiManagement/service/apis/operations", "apiVersion": "2023-09-01-preview", @@ -446,6 +467,20 @@ "[resourceId('Microsoft.ApiManagement/service/apis', variables('apimName'), 'universal-llm-api')]" ] }, + { + "type": "Microsoft.ApiManagement/service/apis/operations/policies", + "apiVersion": "2023-09-01-preview", + "name": "[format('{0}/{1}/{2}/{3}', variables('apimName'), 'universal-llm-api', 'post-embeddings', 'policy')]", + "properties": { + "format": "xml", + "value": "@(\"Bearer \" + (string)context.Variables[\"msi-access-token\"])application/json@{return new JObject(new JProperty(\"error\",\"unsupported model requested for embeddings\"),new JProperty(\"requestedModel\",(string)context.Variables[\"requestedModel\"])).ToString();}" + }, + "dependsOn": [ + "[resourceId('Microsoft.ApiManagement/service/backends', variables('apimName'), 'backend-hub-foundry')]", + "[resourceId('Microsoft.ApiManagement/service/namedValues', variables('apimName'), 'uami-client-id')]", + "[resourceId('Microsoft.ApiManagement/service/apis/operations', variables('apimName'), 'universal-llm-api', 'post-embeddings')]" + ] + }, { "type": "Microsoft.ApiManagement/service/apis/operations", "apiVersion": "2023-09-01-preview", @@ -1106,10 +1141,25 @@ "name": "Basic" }, "properties": { - "adminUserEnabled": true, + "adminUserEnabled": false, "publicNetworkAccess": "Enabled" } }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[resourceId('Microsoft.ContainerRegistry/registries', replace(variables('spokeAcrName'), '-', ''))]", + "name": "[guid(resourceId('Microsoft.ContainerRegistry/registries', replace(variables('spokeAcrName'), '-', '')), resourceId('Microsoft.CognitiveServices/accounts/projects', variables('spokeFoundryAccountName'), variables('spokeFoundryProjectName')), '7f951dda-4ed3-4680-a7ca-43fe172d538d')]", + "properties": { + "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')]", + "principalId": "[reference(resourceId('Microsoft.CognitiveServices/accounts/projects', variables('spokeFoundryAccountName'), variables('spokeFoundryProjectName')), '2025-04-01-preview', 'full').identity.principalId]", + "principalType": "ServicePrincipal" + }, + "dependsOn": [ + "[resourceId('Microsoft.ContainerRegistry/registries', replace(variables('spokeAcrName'), '-', ''))]", + "[resourceId('Microsoft.CognitiveServices/accounts/projects', variables('spokeFoundryAccountName'), variables('spokeFoundryProjectName'))]" + ] + }, { "type": "Microsoft.CognitiveServices/accounts/connections", "apiVersion": "2025-04-01-preview", @@ -1234,7 +1284,7 @@ }, "LLM_BACKEND_CONFIG": { "type": "string", - "value": "[base64(string(createArray(createObject('name', 'gpt-4.1', 'publisher', 'Azure OpenAI', 'endpoint', format('https://{0}.openai.azure.com', variables('hubFoundryAccountName')), 'models', createArray('gpt-4.1')), createObject('name', 'gpt-5.4-mini', 'publisher', 'Azure OpenAI', 'endpoint', format('https://{0}.openai.azure.com', variables('hubFoundryAccountName')), 'models', createArray('gpt-5.4-mini')), createObject('name', 'gpt-5.2', 'publisher', 'Azure OpenAI', 'endpoint', format('https://{0}.openai.azure.com', variables('hubFoundryAccountName')), 'models', createArray('gpt-5.2')), createObject('name', 'Mistral-Large-3', 'publisher', 'Azure OpenAI', 'endpoint', format('https://{0}.openai.azure.com', variables('hubFoundryAccountName')), 'models', createArray('Mistral-Large-3')), createObject('name', 'Phi-4', 'publisher', 'Azure OpenAI', 'endpoint', format('https://{0}.openai.azure.com', variables('hubFoundryAccountName')), 'models', createArray('Phi-4')), createObject('name', 'text-embedding-3-large', 'publisher', 'Azure OpenAI', 'endpoint', format('https://{0}.openai.azure.com', variables('hubFoundryAccountName')), 'models', createArray('text-embedding-3-large')))))]" + "value": "[base64(string(createArray(createObject('name', 'gpt-4.1', 'publisher', 'Azure OpenAI', 'endpoint', format('https://{0}.openai.azure.com', variables('hubFoundryAccountName')), 'models', createArray('gpt-4.1')), createObject('name', 'gpt-5.4-mini', 'publisher', 'Azure OpenAI', 'endpoint', format('https://{0}.openai.azure.com', variables('hubFoundryAccountName')), 'models', createArray('gpt-5.4-mini')), createObject('name', 'gpt-5.2', 'publisher', 'Azure OpenAI', 'endpoint', format('https://{0}.openai.azure.com', variables('hubFoundryAccountName')), 'models', createArray('gpt-5.2')), createObject('name', 'text-embedding-3-large', 'publisher', 'Azure OpenAI', 'endpoint', format('https://{0}.openai.azure.com', variables('hubFoundryAccountName')), 'models', createArray('text-embedding-3-large')), createObject('name', 'Mistral-Large-3', 'publisher', 'Azure OpenAI', 'endpoint', format('https://{0}.openai.azure.com', variables('hubFoundryAccountName')), 'models', createArray('Mistral-Large-3')), createObject('name', 'Phi-4', 'publisher', 'Azure OpenAI', 'endpoint', format('https://{0}.openai.azure.com', variables('hubFoundryAccountName')), 'models', createArray('Phi-4')))))]" }, "COSMOS_ENDPOINT": { "type": "string", @@ -1351,6 +1401,42 @@ "LOCATION": { "type": "string", "value": "[parameters('location')]" + }, + "COSMOS_ACCOUNT_RESOURCE_ID": { + "type": "string", + "value": "[resourceId('Microsoft.DocumentDB/databaseAccounts', variables('cosmosAccountName'))]" + }, + "APIM_RESOURCE_ID": { + "type": "string", + "value": "[resourceId('Microsoft.ApiManagement/service', variables('apimName'))]" + }, + "KEY_VAULT_RESOURCE_ID": { + "type": "string", + "value": "[resourceId('Microsoft.KeyVault/vaults', variables('keyVaultName'))]" + }, + "APP_INSIGHTS_RESOURCE_ID": { + "type": "string", + "value": "[resourceId('Microsoft.Insights/components', variables('aiName'))]" + }, + "SPOKE_FOUNDRY_PROJECT_RESOURCE_ID": { + "type": "string", + "value": "[resourceId('Microsoft.CognitiveServices/accounts/projects', variables('spokeFoundryAccountName'), variables('spokeFoundryProjectName'))]" + }, + "SPOKE_KEY_VAULT_RESOURCE_ID": { + "type": "string", + "value": "[resourceId('Microsoft.KeyVault/vaults', variables('spokeKeyVaultName'))]" + }, + "SPOKE_LOG_ANALYTICS_WORKSPACE_RESOURCE_ID": { + "type": "string", + "value": "[resourceId('Microsoft.OperationalInsights/workspaces', variables('spokeLaName'))]" + }, + "SPOKE_APP_INSIGHTS_RESOURCE_ID": { + "type": "string", + "value": "[resourceId('Microsoft.Insights/components', variables('spokeAiName'))]" + }, + "SPOKE_ACR_RESOURCE_ID": { + "type": "string", + "value": "[resourceId('Microsoft.ContainerRegistry/registries', replace(variables('spokeAcrName'), '-', ''))]" } } } \ No newline at end of file diff --git a/03-Azure/01-04-AI/05_AI_Governance/labautomation/lab-defaults.json b/03-Azure/01-04-AI/06_AI_Governance/labautomation/lab-defaults.json similarity index 76% rename from 03-Azure/01-04-AI/05_AI_Governance/labautomation/lab-defaults.json rename to 03-Azure/01-04-AI/06_AI_Governance/labautomation/lab-defaults.json index cee68536a..c252e107f 100644 --- a/03-Azure/01-04-AI/05_AI_Governance/labautomation/lab-defaults.json +++ b/03-Azure/01-04-AI/06_AI_Governance/labautomation/lab-defaults.json @@ -4,5 +4,6 @@ "deploymentType": "resourcegroup", "labsPerSubscription": 8, "preferredLocation": "swedencentral, westeurope, norwayeast", - "estimatedDailyCostsUsd": 15.0 + "estimatedDailyCostsUsd": 32.0, + "estimatedSharedDeploymentDailyCostsUsd": 0.0 } diff --git a/03-Azure/01-04-AI/05_AI_Governance/labautomation/run-local.ps1 b/03-Azure/01-04-AI/06_AI_Governance/labautomation/run-local.ps1 similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/labautomation/run-local.ps1 rename to 03-Azure/01-04-AI/06_AI_Governance/labautomation/run-local.ps1 diff --git a/03-Azure/01-04-AI/05_AI_Governance/labautomation/setup-notebook-env.ps1 b/03-Azure/01-04-AI/06_AI_Governance/labautomation/setup-notebook-env.ps1 similarity index 100% rename from 03-Azure/01-04-AI/05_AI_Governance/labautomation/setup-notebook-env.ps1 rename to 03-Azure/01-04-AI/06_AI_Governance/labautomation/setup-notebook-env.ps1 diff --git a/03-Azure/01-04-AI/06_AI_Governance/labautomation/shared-deploy-lab.ps1 b/03-Azure/01-04-AI/06_AI_Governance/labautomation/shared-deploy-lab.ps1 new file mode 100644 index 000000000..cae0d10bd --- /dev/null +++ b/03-Azure/01-04-AI/06_AI_Governance/labautomation/shared-deploy-lab.ps1 @@ -0,0 +1,121 @@ +<# + shared-deploy-lab.ps1 — EMEA MicroHack once-per-subscription hook for the Citadel + Agentic Governance microhack. + + The microsoft/MicroHack platform picks this script up automatically by file name and runs + it exactly ONCE PER AZURE SUBSCRIPTION, before the per-participant deploy-lab.ps1 fan-out. + It is the sanctioned home for one-off subscription preparation that the many parallel lab + deployments would otherwise race on — registering resource providers being the canonical + example. + + Why this lab needs it: + The hub-and-spoke deployment touches ten resource providers (API Management, Cognitive + Services, Cosmos DB, Event Hub, Key Vault, Container Registry, ...). The platform + pre-registers only a small common set, and in a 'resourcegroup' lab the participant holds + RG-Owner plus subscription-READER — provider registration is a subscription-scope write, + so a participant cannot self-register and deploy-lab.ps1 fails with + MissingSubscriptionRegistration in every region it tries. Registering here, once and ahead + of the labs, makes the deployment work without granting anyone subscription Owner. + + Platform contract: + - Az context is already set to $SubscriptionId -> do NOT Connect-AzAccount. + - Az.Accounts / Az.Resources are already imported. + - 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. Provider + registration is therefore best-effort: a provider that is already registered, or that a + subscription policy manages centrally, must not take down the whole subscription's + provisioning. Failures are reported loudly with the exact remediation command instead. +#> +param( + [Parameter(Mandatory = $true)] + [string]$SubscriptionId, + + [Parameter(Mandatory = $true)] + [string[]]$PreferredLocation = @(), + + [Parameter(Mandatory = $false)] + [string[]]$AllowedEntraUserIds = @() +) + +$ErrorActionPreference = 'Stop' + +# Required = infra/resources.bicep deploys a resource of this type, so the lab cannot +# provision at all without it. +# Optional = only a follow-on challenge (7-9, incl. the instructor-led HR MCP) needs it. +$providers = @( + @{ Namespace = 'Microsoft.ApiManagement'; Required = $true; Reason = 'APIM AI gateway (all challenges)' } + @{ Namespace = 'Microsoft.CognitiveServices'; Required = $true; Reason = 'Azure AI Foundry hub + spoke accounts, model deployments' } + @{ Namespace = 'Microsoft.DocumentDB'; Required = $true; Reason = 'Cosmos DB usage tracking (challenge 2)' } + @{ Namespace = 'Microsoft.KeyVault'; Required = $true; Reason = 'Hub and spoke Key Vaults' } + @{ Namespace = 'Microsoft.OperationalInsights'; Required = $true; Reason = 'Log Analytics workspaces (challenge 3)' } + @{ Namespace = 'Microsoft.Insights'; Required = $true; Reason = 'Application Insights / agent tracing (challenge 3)' } + @{ Namespace = 'Microsoft.EventHub'; Required = $true; Reason = 'Usage event streaming pipeline' } + @{ Namespace = 'Microsoft.Storage'; Required = $true; Reason = 'Usage ingestion storage account' } + @{ Namespace = 'Microsoft.ManagedIdentity'; Required = $true; Reason = 'User-assigned identities for APIM backend auth' } + @{ Namespace = 'Microsoft.ContainerRegistry'; Required = $true; Reason = 'Spoke ACR (challenge 7 container publishing)' } + @{ Namespace = 'Microsoft.App'; Required = $false; Reason = 'HR MCP Container App (challenge 9, instructor-led)' } + @{ Namespace = 'Microsoft.AlertsManagement'; Required = $false; Reason = 'Governance alert rules (optional extension)' } +) + +$pending = New-Object System.Collections.Generic.List[string] +$failed = New-Object System.Collections.Generic.List[hashtable] + +foreach ($provider in $providers) { + $ns = $provider.Namespace + try { + $state = (Get-AzResourceProvider -ProviderNamespace $ns -ErrorAction SilentlyContinue | + Select-Object -First 1).RegistrationState + + if ($state -eq 'Registered') { + Write-Host "[OK] [$SubscriptionId] Resource provider '$ns' already registered." + continue + } + + Write-Host "[INFO] [$SubscriptionId] Registering '$ns' (state: '$state') — $($provider.Reason)..." + Register-AzResourceProvider -ProviderNamespace $ns -ErrorAction Stop | Out-Null + if ($provider.Required) { $pending.Add($ns) } + Write-Host "[OK] [$SubscriptionId] Registration submitted for '$ns' (async, idempotent, subscription-wide)." + } + catch { + # Best-effort: never abort the whole subscription's provisioning over one registration. + Write-Host "[WARN] [$SubscriptionId] Could not register '$ns': $_" + $failed.Add($provider) + } +} + +# Registration is asynchronous. The per-participant deployments start as soon as this hook +# returns, so wait for the REQUIRED providers to actually reach 'Registered' rather than +# letting every parallel lab race a half-registered subscription. +if ($pending.Count -gt 0) { + $timeoutSeconds = 300 + $intervalSeconds = 15 + $elapsed = 0 + + Write-Host "[INFO] [$SubscriptionId] Waiting for $($pending.Count) required provider(s) to finish registering (up to ${timeoutSeconds}s)..." + while ($elapsed -lt $timeoutSeconds -and $pending.Count -gt 0) { + Start-Sleep -Seconds $intervalSeconds + $elapsed += $intervalSeconds + + foreach ($ns in @($pending)) { + $state = (Get-AzResourceProvider -ProviderNamespace $ns -ErrorAction SilentlyContinue | + Select-Object -First 1).RegistrationState + if ($state -eq 'Registered') { + Write-Host "[OK] [$SubscriptionId] '$ns' is registered." + $pending.Remove($ns) | Out-Null + } + } + } + + foreach ($ns in $pending) { + Write-Host "[WARN] [$SubscriptionId] '$ns' was still registering after ${timeoutSeconds}s — deployments may hit MissingSubscriptionRegistration on their first attempt." + } +} + +foreach ($provider in $failed) { + $severity = if ($provider.Required) { 'ERROR' } else { 'WARN' } + Write-Host "[$severity] [$SubscriptionId] '$($provider.Namespace)' is NOT registered — $($provider.Reason)." + Write-Host "[$severity] [$SubscriptionId] A subscription Owner must run once (subscription-wide, unblocks every lab):" + Write-Host "[$severity] [$SubscriptionId] az provider register --namespace $($provider.Namespace) --subscription $SubscriptionId" +} + +Write-Host "[OK] [$SubscriptionId] Shared subscription preparation complete (Citadel Agentic Governance microhack)." diff --git a/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-01/solution-01.md b/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-01/solution-01.md new file mode 100644 index 000000000..222733ad2 --- /dev/null +++ b/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-01/solution-01.md @@ -0,0 +1,55 @@ +# Solution 01 — Onboard a New LLM Backend + +**[← Back to Challenge 1](../../challenges/challenge-01.md)** · [Home](../../README.md) · [Solution 02 →](../challenge-02/solution-02.md) + +Duration: 35 min + +> [!IMPORTANT] +> **Coach-facing spoiler content.** Do not share this folder with participants before they +> have attempted the challenge. + +## Reference notebook + +This challenge is driven by +[`challenges/workshop/1. llm-backend-onboarding-runner.ipynb`](../../challenges/workshop/1.%20llm-backend-onboarding-runner.ipynb). +The notebook is the **unmodified upstream Citadel workshop notebook** and is the +authoritative solution — it contains the working code, the expected output, and its own +`📊 Results Summary` cell. + +Participants must first bridge the lab-dashboard credentials into a local azd environment +with [`labautomation/setup-notebook-env.ps1`](../../labautomation/setup-notebook-env.ps1), +because the notebook reads its configuration via `azd env get-value`. See +[labautomation/README.md → Notebook Environment Setup](../../labautomation/README.md#notebook-environment-setup). + +## Expected end state + +- [ ] The notebook generated a `.bicepparam` file describing your backend, auth type, and per-model metadata. +- [ ] The LLM backend onboarding Bicep deployment succeeded and the backend pool + `get-available-models` policy fragment are visible in APIM. +- [ ] `GET /deployments` (Foundry integration) returns your onboarded model. +- [ ] The deployed model responds successfully via the Universal LLM API, the Azure OpenAI API, the Azure OpenAI Python SDK, and a streaming request. + +## 🛠️ Task-by-task walkthrough + + + +_Walkthrough narrative to be written._ + +## 🧭 Coaching notes + + + +- **Common failure:** _TBD_ +- **Where teams get stuck:** _TBD_ +- **Good question to ask the room:** _TBD_ + +## ✅ How to verify + + + +_Verification steps to be written._ diff --git a/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-02/solution-02.md b/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-02/solution-02.md new file mode 100644 index 000000000..5daaaf375 --- /dev/null +++ b/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-02/solution-02.md @@ -0,0 +1,55 @@ +# Solution 02 — Universal LLM API Across Every Model + +**[← Back to Challenge 2](../../challenges/challenge-02.md)** · [Home](../../README.md) · [← Solution 01](../challenge-01/solution-01.md) · [Solution 03 →](../challenge-03/solution-03.md) + +Duration: 25 min + +> [!IMPORTANT] +> **Coach-facing spoiler content.** Do not share this folder with participants before they +> have attempted the challenge. + +## Reference notebook + +This challenge is driven by +[`challenges/workshop/2. citadel-universal-llm-api-all-models-tests.ipynb`](../../challenges/workshop/2.%20citadel-universal-llm-api-all-models-tests.ipynb). +The notebook is the **unmodified upstream Citadel workshop notebook** and is the +authoritative solution — it contains the working code, the expected output, and its own +`📊 Results Summary` cell. + +Participants must first bridge the lab-dashboard credentials into a local azd environment +with [`labautomation/setup-notebook-env.ps1`](../../labautomation/setup-notebook-env.ps1), +because the notebook reads its configuration via `azd env get-value`. See +[labautomation/README.md → Notebook Environment Setup](../../labautomation/README.md#notebook-environment-setup). + +## Expected end state + +- [ ] An access contract (APIM product + subscription) with `allowedModels = ""` deployed successfully via Bicep. +- [ ] `GET /models/models` returned the live catalogue of models the gateway serves. +- [ ] Every discovered model was classified correctly (embedding vs. `gpt`-style vs. other) and exercised the matching operation — `POST /models/embeddings`, `POST /models/chat/completions`, or the Responses API trio (`/models/responses`, `GET /models/responses/{id}`, `GET /models/responses/{id}/input_items`). +- [ ] The final per-model results table shows no unexpected failures, confirming `allowedModels = ""` does not block any model. + +## 🛠️ Task-by-task walkthrough + + + +_Walkthrough narrative to be written._ + +## 🧭 Coaching notes + + + +- **Common failure:** _TBD_ +- **Where teams get stuck:** _TBD_ +- **Good question to ask the room:** _TBD_ + +## ✅ How to verify + + + +_Verification steps to be written._ diff --git a/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-03/solution-03.md b/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-03/solution-03.md new file mode 100644 index 000000000..b70e532ef --- /dev/null +++ b/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-03/solution-03.md @@ -0,0 +1,55 @@ +# Solution 03 — Access Contracts: Model RBAC & Capacity + +**[← Back to Challenge 3](../../challenges/challenge-03.md)** · [Home](../../README.md) · [← Solution 02](../challenge-02/solution-02.md) · [Solution 04 →](../challenge-04/solution-04.md) + +Duration: 35 min + +> [!IMPORTANT] +> **Coach-facing spoiler content.** Do not share this folder with participants before they +> have attempted the challenge. + +## Reference notebook + +This challenge is driven by +[`challenges/workshop/3. citadel-access-contracts-tests.ipynb`](../../challenges/workshop/3.%20citadel-access-contracts-tests.ipynb). +The notebook is the **unmodified upstream Citadel workshop notebook** and is the +authoritative solution — it contains the working code, the expected output, and its own +`📊 Results Summary` cell. + +Participants must first bridge the lab-dashboard credentials into a local azd environment +with [`labautomation/setup-notebook-env.ps1`](../../labautomation/setup-notebook-env.ps1), +because the notebook reads its configuration via `azd env get-value`. See +[labautomation/README.md → Notebook Environment Setup](../../labautomation/README.md#notebook-environment-setup). + +## Expected end state + +- [ ] All three access contracts (APIM products + subscriptions) deployed successfully via Bicep, each with its own dynamically generated product policy. +- [ ] Requests against each contract respect its `allowedModels` RBAC list. +- [ ] The load test shows throttling behaviour consistent with each contract's `llm-token-limit` capacity allocation. +- [ ] The Foundry connection name(s) for HR-ChatAgent were printed and noted for Challenge 4. + +## 🛠️ Task-by-task walkthrough + + + +_Walkthrough narrative to be written._ + +## 🧭 Coaching notes + + + +- **Common failure:** _TBD_ +- **Where teams get stuck:** _TBD_ +- **Good question to ask the room:** _TBD_ + +## ✅ How to verify + + + +_Verification steps to be written._ diff --git a/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-04/solution-04.md b/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-04/solution-04.md new file mode 100644 index 000000000..0828c78f8 --- /dev/null +++ b/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-04/solution-04.md @@ -0,0 +1,54 @@ +# Solution 04 — Drive Access Contracts from Agent Frameworks + +**[← Back to Challenge 4](../../challenges/challenge-04.md)** · [Home](../../README.md) · [← Solution 03](../challenge-03/solution-03.md) · [Solution 05 →](../challenge-05/solution-05.md) + +Duration: 30 min + +> [!IMPORTANT] +> **Coach-facing spoiler content.** Do not share this folder with participants before they +> have attempted the challenge. + +## Reference notebook + +This challenge is driven by +[`challenges/workshop/4. citadel-agent-frameworks-tests.ipynb`](../../challenges/workshop/4.%20citadel-agent-frameworks-tests.ipynb). +The notebook is the **unmodified upstream Citadel workshop notebook** and is the +authoritative solution — it contains the working code, the expected output, and its own +`📊 Results Summary` cell. + +Participants must first bridge the lab-dashboard credentials into a local azd environment +with [`labautomation/setup-notebook-env.ps1`](../../labautomation/setup-notebook-env.ps1), +because the notebook reads its configuration via `azd env get-value`. See +[labautomation/README.md → Notebook Environment Setup](../../labautomation/README.md#notebook-environment-setup). + +## Expected end state + +- [ ] All three agent frameworks are installed and each successfully authenticates against its access contract's API key. +- [ ] Each agent completes a multi-turn conversation relevant to its business domain (sales pricing, HR policies, technical support). +- [ ] The agent-statistics comparison and token-efficiency analysis produce a table comparing all three frameworks. + +## 🛠️ Task-by-task walkthrough + + + +_Walkthrough narrative to be written._ + +## 🧭 Coaching notes + + + +- **Common failure:** _TBD_ +- **Where teams get stuck:** _TBD_ +- **Good question to ask the room:** _TBD_ + +## ✅ How to verify + + + +_Verification steps to be written._ diff --git a/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-05/solution-05.md b/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-05/solution-05.md new file mode 100644 index 000000000..442c1d7b2 --- /dev/null +++ b/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-05/solution-05.md @@ -0,0 +1,55 @@ +# Solution 05 — PII Anonymization, Blocking & Analytics + +**[← Back to Challenge 5](../../challenges/challenge-05.md)** · [Home](../../README.md) · [← Solution 04](../challenge-04/solution-04.md) · [Solution 06 →](../challenge-06/solution-06.md) + +Duration: 35 min + +> [!IMPORTANT] +> **Coach-facing spoiler content.** Do not share this folder with participants before they +> have attempted the challenge. + +## Reference notebook + +This challenge is driven by +[`challenges/workshop/5. citadel-pii-processing-tests.ipynb`](../../challenges/workshop/5.%20citadel-pii-processing-tests.ipynb). +The notebook is the **unmodified upstream Citadel workshop notebook** and is the +authoritative solution — it contains the working code, the expected output, and its own +`📊 Results Summary` cell. + +Participants must first bridge the lab-dashboard credentials into a local azd environment +with [`labautomation/setup-notebook-env.ps1`](../../labautomation/setup-notebook-env.ps1), +because the notebook reads its configuration via `azd env get-value`. See +[labautomation/README.md → Notebook Environment Setup](../../labautomation/README.md#notebook-environment-setup). + +## Expected end state + +- [ ] The PII masking access contract anonymizes detected PII (names, emails, phone numbers, credit cards, IBANs, custom regex patterns) and can deanonymize it back. +- [ ] The PII blocking access contract detects and blocks requests containing PII, reporting what was detected. +- [ ] Your Cosmos DB query returns recent PII processing records, and the quantitative report/visualizations render successfully. +- [ ] The PII analytics access contract successfully sends the usage summary to an LLM through its own governed contract. + +## 🛠️ Task-by-task walkthrough + + + +_Walkthrough narrative to be written._ + +## 🧭 Coaching notes + + + +- **Common failure:** _TBD_ +- **Where teams get stuck:** _TBD_ +- **Good question to ask the room:** _TBD_ + +## ✅ How to verify + + + +_Verification steps to be written._ diff --git a/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-06/solution-06.md b/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-06/solution-06.md new file mode 100644 index 000000000..2c924bd14 --- /dev/null +++ b/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-06/solution-06.md @@ -0,0 +1,57 @@ +# Solution 06 — Unified AI API Across Providers + +**[← Back to Challenge 6](../../challenges/challenge-06.md)** · [Home](../../README.md) · [← Solution 05](../challenge-05/solution-05.md) · [Solution 07 →](../challenge-07/solution-07.md) + +Duration: 35 min + +> [!IMPORTANT] +> **Coach-facing spoiler content.** Do not share this folder with participants before they +> have attempted the challenge. + +## Reference notebook + +This challenge is driven by +[`challenges/workshop/6. citadel-unified-ai-api-tests.ipynb`](../../challenges/workshop/6.%20citadel-unified-ai-api-tests.ipynb). +The notebook is the **unmodified upstream Citadel workshop notebook** and is the +authoritative solution — it contains the working code, the expected output, and its own +`📊 Results Summary` cell. + +Participants must first bridge the lab-dashboard credentials into a local azd environment +with [`labautomation/setup-notebook-env.ps1`](../../labautomation/setup-notebook-env.ps1), +because the notebook reads its configuration via `azd env get-value`. See +[labautomation/README.md → Notebook Environment Setup](../../labautomation/README.md#notebook-environment-setup). + +## Expected end state + +- [ ] An access contract deployed successfully and model/deployment discovery lists your models, with a `404` for a non-existent deployment. +- [ ] `POST /unified-ai/openai/deployments/{model}/chat/completions` (Azure OpenAI pattern) and `POST /unified-ai/models/chat/completions` (Foundry inference pattern) both return `200`. +- [ ] A missing API key returns `401`; an invalid API key also returns `401`. +- [ ] A request to an unrecognized path returns `403 PathNotAllowed`, and a request for a model outside `allowedModels` is rejected. +- [ ] The load test shows a mix of `200`/`429` responses, and every `UAIG-*` debug header is present and populated on responses. +- [ ] A streaming request (`stream: true`) returns a `200` SSE response with `UAIG-Is-Streaming: true`. + +## 🛠️ Task-by-task walkthrough + + + +_Walkthrough narrative to be written._ + +## 🧭 Coaching notes + + + +- **Common failure:** _TBD_ +- **Where teams get stuck:** _TBD_ +- **Good question to ask the room:** _TBD_ + +## ✅ How to verify + + + +_Verification steps to be written._ diff --git a/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-07/solution-07.md b/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-07/solution-07.md new file mode 100644 index 000000000..178198b2b --- /dev/null +++ b/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-07/solution-07.md @@ -0,0 +1,60 @@ +# Solution 07 — Build a Governed Hosted Agent with AGT + +**[← Back to Challenge 7](../../challenges/challenge-07.md)** · [Home](../../README.md) · [← Solution 06](../challenge-06/solution-06.md) · [Solution 08 →](../challenge-08/solution-08.md) + +Duration: 40 min + +> [!NOTE] +> This is a **follow-on / optional** challenge. Teams that complete challenges 1–6 +> are already successful; treat this as a stretch goal. + +> [!IMPORTANT] +> **Coach-facing spoiler content.** Do not share this folder with participants before they +> have attempted the challenge. + +## Reference notebook + +This challenge is driven by +[`challenges/workshop/7. citadel-hosted-agent-with-agt.ipynb`](../../challenges/workshop/7.%20citadel-hosted-agent-with-agt.ipynb). +The notebook is the **unmodified upstream Citadel workshop notebook** and is the +authoritative solution — it contains the working code, the expected output, and its own +`📊 Results Summary` cell. + +Participants must first bridge the lab-dashboard credentials into a local azd environment +with [`labautomation/setup-notebook-env.ps1`](../../labautomation/setup-notebook-env.ps1), +because the notebook reads its configuration via `azd env get-value`. See +[labautomation/README.md → Notebook Environment Setup](../../labautomation/README.md#notebook-environment-setup). + +## Expected end state + +- [ ] The hosted agent container built and pushed via `az acr build`, and the agent deployed to your spoke Foundry project. +- [ ] The agent's managed identity received the RBAC it needs (step `5a`) and the agent responds to a basic invocation (step `6️⃣`). +- [ ] Step `6a` proves the AGT policy actually enforces governance — an SSN-disclosure or 3rd-party salary request is denied rather than answered. +- [ ] A multi-turn conversation and a streaming response both work end-to-end. +- [ ] You can find `agt.audit.event` trace records in Application Insights for a denied request. + +## 🛠️ Task-by-task walkthrough + + + +_Walkthrough narrative to be written._ + +## 🧭 Coaching notes + + + +- **Common failure:** _TBD_ +- **Where teams get stuck:** _TBD_ +- **Good question to ask the room:** _TBD_ + +## ✅ How to verify + + + +_Verification steps to be written._ diff --git a/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-08/solution-08.md b/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-08/solution-08.md new file mode 100644 index 000000000..5239e3d17 --- /dev/null +++ b/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-08/solution-08.md @@ -0,0 +1,59 @@ +# Solution 08 — Publish a Foundry Agent as an A2A Endpoint + +**[← Back to Challenge 8](../../challenges/challenge-08.md)** · [Home](../../README.md) · [← Solution 07](../challenge-07/solution-07.md) · [Solution 09 →](../challenge-09/solution-09.md) + +Duration: 35 min + +> [!NOTE] +> This is a **follow-on / optional** challenge. Teams that complete challenges 1–6 +> are already successful; treat this as a stretch goal. + +> [!IMPORTANT] +> **Coach-facing spoiler content.** Do not share this folder with participants before they +> have attempted the challenge. + +## Reference notebook + +This challenge is driven by +[`challenges/workshop/8. publish-and-use-a2a-endpoint.ipynb`](../../challenges/workshop/8.%20publish-and-use-a2a-endpoint.ipynb). +The notebook is the **unmodified upstream Citadel workshop notebook** and is the +authoritative solution — it contains the working code, the expected output, and its own +`📊 Results Summary` cell. + +Participants must first bridge the lab-dashboard credentials into a local azd environment +with [`labautomation/setup-notebook-env.ps1`](../../labautomation/setup-notebook-env.ps1), +because the notebook reads its configuration via `azd env get-value`. See +[labautomation/README.md → Notebook Environment Setup](../../labautomation/README.md#notebook-environment-setup). + +## Expected end state + +- [ ] The HR prompt agent was created on `gpt-4.1` and its A2A endpoint was enabled, with the `v1.0` agent card verified (step 8). +- [ ] Public network access on the Foundry account was re-disabled (step 9) **before** publishing through APIM. +- [ ] An APIM API, product, and subscription were created (steps `10.1`–`10.4`) fronting the agent's A2A JSON-RPC runtime URL. +- [ ] Both calls in step 11 (agent card `GET` and JSON-RPC `message/send` `POST`) succeed through APIM using only the subscription key — with the Foundry account still privately-networked. + +## 🛠️ Task-by-task walkthrough + + + +_Walkthrough narrative to be written._ + +## 🧭 Coaching notes + + + +- **Common failure:** _TBD_ +- **Where teams get stuck:** _TBD_ +- **Good question to ask the room:** _TBD_ + +## ✅ How to verify + + + +_Verification steps to be written._ diff --git a/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-09/solution-09.md b/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-09/solution-09.md new file mode 100644 index 000000000..0804aeb4f --- /dev/null +++ b/03-Azure/01-04-AI/06_AI_Governance/walkthrough/challenge-09/solution-09.md @@ -0,0 +1,60 @@ +# Solution 09 — HR MCP via APIM (Optional / Instructor-Led) + +**[← Back to Challenge 9](../../challenges/challenge-09.md)** · [Home](../../README.md) · [← Solution 08](../challenge-08/solution-08.md) · [Finish →](../../challenges/finish.md) + +Duration: variable + +> [!NOTE] +> This is a **follow-on / optional** challenge. Teams that complete challenges 1–6 +> are already successful; treat this as a stretch goal. + +> [!IMPORTANT] +> **Coach-facing spoiler content.** Do not share this folder with participants before they +> have attempted the challenge. + +## Reference notebook + +This challenge is driven by +[`challenges/workshop/9. publish-and-use-hr-mcp-via-apim.ipynb`](../../challenges/workshop/9.%20publish-and-use-hr-mcp-via-apim.ipynb). +The notebook is the **unmodified upstream Citadel workshop notebook** and is the +authoritative solution — it contains the working code, the expected output, and its own +`📊 Results Summary` cell. + +Participants must first bridge the lab-dashboard credentials into a local azd environment +with [`labautomation/setup-notebook-env.ps1`](../../labautomation/setup-notebook-env.ps1), +because the notebook reads its configuration via `azd env get-value`. See +[labautomation/README.md → Notebook Environment Setup](../../labautomation/README.md#notebook-environment-setup). + +## Expected end state + +- [ ] You can explain, from section `2️⃣`'s output, which `HR_MCP_*` values are missing and why (the HR MCP backend + Entra app role aren't part of this lab's automation). +- [ ] A delegated token was acquired without ever being printed (step `3️⃣`), and the APIM access contract was created or validated (step `4️⃣`). +- [ ] MCP `initialize`, `tools/list`, and both a read and a write tool call succeed through APIM only (step `7️⃣`). +- [ ] The 6th rapid `tools/call` request within 30 seconds returns `429` (step `8️⃣`). +- [ ] The hosted agent from step `🔟` calls HR MCP tools through APIM using its own managed identity, a fresh Entra token, and the APIM subscription key — not a participant token. + +## 🛠️ Task-by-task walkthrough + + + +_Walkthrough narrative to be written._ + +## 🧭 Coaching notes + + + +- **Common failure:** _TBD_ +- **Where teams get stuck:** _TBD_ +- **Good question to ask the room:** _TBD_ + +## ✅ How to verify + + + +_Verification steps to be written._ diff --git a/03-Azure/01-04-AI/Readme.md b/03-Azure/01-04-AI/Readme.md index 676b38d83..acb042a58 100644 --- a/03-Azure/01-04-AI/Readme.md +++ b/03-Azure/01-04-AI/Readme.md @@ -8,7 +8,7 @@ A collection of MicroHacks focused on AI on Azure and the GitHub + Azure agentic * [**Inventory Planning with Microsoft Fabric (low-code)**](02_Inventory_Planning_Fabric/README.md) — Build a closed sense → reason → act inventory-planning loop with **Microsoft Foundry Agent Service** prompt agents (no application code) grounded on a governed **Fabric Data Agent** doing NL2SQL over an inventory Lakehouse, with a human-in-the-loop approval gate. * [**Inventory Planning with AI Agents, Cosmos DB & Hosted Agents (pro-code)**](03_Inventory_Planning_Agentic/README.md) — Build hosted AI agents **in code** (Python) on the **Microsoft Foundry Agent Service** that reason over a governed per-attendee **Azure Cosmos DB** via typed function tools, chained into a sequential planning workflow with a human-in-the-loop approval gate and Foundry tracing. * [**Agentic Contract Lifecycle Management (multi-model, pro-code)**](04_Agentic_Contract_Lifecycle_Management/README.md) — Build a **multi-model, multi-agent** contract assistant **in code** (Python) on **Microsoft Foundry**: orchestration on **GPT-5.4** with specialist drafting on **Anthropic Claude** and clause analysis on **GPT-5.6 Sol**, grounded with **Foundry IQ** over your own contract corpus, traced and evaluated behind a quality gate, exposed as an **MCP server**, and published to **Microsoft 365 Copilot & Teams** with proactive renewal alerts. -* [**Agentic Governance Hub (Citadel framework)**](05_AI_Governance/README.md) — Deploy a unified AI governance platform using the **Citadel Agentic Governance Hub** framework: onboard LLM backends through the hub's API Management gateway, configure access controls and policy enforcement, demonstrate usage tracking and observability, build governed agents on the spoke, and publish them behind unified infrastructure — all with Bicep infrastructure-as-code and Jupyter notebook walkthroughs. * [**Agentic Claims Processing**](05_Claims_Processing/README.md) — Build an end-to-end claims workflow with **Mistral Document AI Models** and **Microsoft Foundry**, covering OCR and knowledge grounding, policy coverage decisions, sequential orchestration with human review, FIDES security hardening, and managed-identity deployment. +* [**Agentic Governance Hub (Citadel framework)**](06_AI_Governance/README.md) — Deploy a unified AI governance platform using the **Citadel Agentic Governance Hub** framework: onboard LLM backends through the hub's API Management gateway, configure access controls and policy enforcement, demonstrate usage tracking and observability, build governed agents on the spoke, and publish them behind unified infrastructure — all with Bicep infrastructure-as-code and Jupyter notebook walkthroughs. Collaborators wanted :) — see [CONTRIBUTING.md](../../CONTRIBUTING.md) to add your own. \ No newline at end of file