name: lab-shared-contract description: Single source of truth for cross-cutting rules referenced by all agents and skills in the lab pipeline. Every requirement has a unique ID (R-0xx).
Shared Contract
Authoritative cross-cutting rules for the hands-on lab pipeline. Every requirement is identified by a unique ID (R-0xx). Other files MUST reference these IDs — never restate the rule.
For the full Azure governance implementation policy, see Governance-Lab.md at .assets/shared/Governance-Lab.md.
R-001: Resource Group Naming
Pattern: <exam>-<domain>-<topic>-<deployment>
<exam>: lowercase exam code (az104,az305)<domain>: lowercase domain slug (e.g.,networking,storage,generative-ai)<topic>: lowercase topic slug (e.g.,vnet-peering,blob-versioning)<deployment>:tf|bicep|scripted
Example: az104-networking-vnet-peering-tf
R-002: Resource Naming — AZ-104 Prefixes
Pattern: <prefix>-<role>[-instance]
<role>describes the resource's function in the lab scenario — NOT the lab topic (see R-031).
All names are static by default. Random suffixes are only added for resources subject to soft-delete retention (see R-028).
| Resource | Prefix | Random Suffix? |
|---|---|---|
| VNet | vnet | No |
| Subnet | snet | No |
| NSG | nsg | No |
| VM | vm | No |
| Storage | st<exam><role> (no hyphens) | No |
| Load Balancer | lb | No |
| Key Vault | kv | R-028 |
| Log Analytics | law | No |
| Recovery Vault | rsv | R-028 |
R-003: Resource Naming — AI-103 Prefixes
Pattern: <prefix>-<role>[-instance]
<role>describes the resource's function in the lab scenario — NOT the lab topic (see R-031).
All names are static by default. Random suffixes are only added for resources subject to soft-delete retention (see R-028).
| Resource | Prefix | Random Suffix? |
|---|---|---|
| OpenAI | oai | R-028 |
| Multi-service | cog | R-028 |
| Vision | cv | R-028 |
| Language | lang | R-028 |
| AI Search | srch | No |
| Deployment | deploy | No |
| Cosmos DB | cosmos | No |
| Storage (AI output) | st<exam><role> | No |
Cognitive Services resources (OpenAI, Multi-service, Vision, Language) require random suffix because they enter soft-deleted state on deletion — see R-028.
These prefixes apply to AI-103 labs. Legacy AI-102 artifacts also follow these conventions.
R-004: Bicep Stack Naming
Pattern: stack-<domain>-<topic>
No exam code in stack name.
R-005: Required Tags (All Resources)
| Tag | Rule |
|---|---|
| Environment | Always Lab |
| Project | Uppercase: AZ-104, AZ-305, or AI-103 |
| Domain | e.g., Networking, Storage, Generative AI |
| Purpose | Descriptive (e.g., VNet Peering) |
| Owner | Greg Tate |
| DateCreated | Static YYYY-MM-DD — no timestamp() / utcNow() |
| DeploymentMethod | Terraform or Bicep |
R-006: Region Rules
| Setting | Value |
|---|---|
| Default | eastus |
| Fallback | westus2 → eastus2 → centralus |
| Allowed | Any US region |
Use eastus unless capacity requires fallback.
R-007: Infrastructure SKU Defaults
| Resource | Default |
|---|---|
| VM | Standard_B2s (B1s if sufficient) |
| Storage | Standard LRS |
| Load Balancer | Basic |
| Public IP | Basic |
| SQL | Basic / S0 |
| Disk | Standard HDD |
R-008: AI Service SKU Defaults
| Service | Default |
|---|---|
| OpenAI | S0 |
| Cognitive Services | F0 → S0 |
| Computer Vision | F0 |
| Custom Vision | F0 |
| Language | F0 |
| Translator | F0 |
| Speech | F0 |
| Form Recognizer | F0 |
| AI Search | Free / Basic |
Start free tier when available.
R-009: Per-Lab Resource Limits
| Resource | Max |
|---|---|
| VMs | 4 |
| Public IPs | 5 |
| Storage Accounts | 3 |
| VNets | 4 |
| OpenAI Accounts | 2 |
| Cognitive Accounts | 3 |
| Model Deployments | 4 |
R-010: Lab Folder Structure
IaaC (Terraform)
<EXAM>/hands-on-labs/<domain>/lab-<topic>/
├── README.md
├── terraform/
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ ├── providers.tf
│ ├── terraform.tfvars
│ └── modules/
│ └── <module>/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── validation/
└── <validation-script>.ps1
IaaC (Bicep)
<EXAM>/hands-on-labs/<domain>/lab-<topic>/
├── README.md
├── bicep/
│ ├── main.bicep
│ ├── main.bicepparam
│ ├── bicepconfig.json
│ ├── bicep.ps1
│ └── modules/
│ └── <module>.bicep
└── validation/
└── <validation-script>.ps1
Scripted
<EXAM>/hands-on-labs/<domain>/lab-<topic>/
├── README.md
├── scripts/
│ ├── deploy.*
│ ├── config.* (if needed)
│ └── cleanup.*
└── validation/
Manual
<EXAM>/hands-on-labs/<domain>/lab-<topic>/
├── README.md
└── screenshots/ (optional)
R-011: README 14-Section Order
Every lab README must contain these sections in this exact sequence:
- Exam Question Scenario / Exam Task (depends on
Intake Mode) - Solution Architecture
- Architecture Diagram
- Lab Objectives
- Lab Structure
- Prerequisites
- Deployment
- Testing the Solution
- Cleanup
- Scenario Analysis / Task Deep Dive (depends on
Intake Mode) - Key Learning Points
- Related Objectives
- Additional Resources
- Related Labs
R-012: Code Header Block
Include in all .tf, .bicep, .ps1 files. Do NOT include in README.
# -------------------------------------------------------------------------
# Program: [filename]
# Description: [purpose]
# Context: [EXAM] Lab - [scenario]
# Author: Greg Tate
# Date: [YYYY-MM-DD]
# -------------------------------------------------------------------------
Use // for .bicep files, # for .tf and .ps1.
R-013: Mermaid Diagram Criteria
- Always required — every lab README must include a Mermaid diagram
- When 2+ interconnected resources are deployed, diagram the resource topology (dependencies, network paths, data flow)
- When fewer than 2 interconnected resources, diagram the overall process reflective of the exam question (e.g., data flow from source → service → output, access methods, decision paths)
- Use
graph TD(top-down) for hierarchical layouts orgraph LR(left-right) for pipeline / process flows - Resource names must match governance naming conventions
- Show dependencies and relationships
R-014: Review Report Schema
## Review Summary
- Overall: [PASS | FAIL]
- Checks Passed: [X/Y]
- Critical Violations: [count]
## Detailed Results
### [Category Name]
- [Check]: PASS
- [Check]: FAIL — [issue and fix]
## Required Fixes (if FAIL)
1. [File]: [exact change]
R-015: Cleanup Policy
- Destroy lab resources within 7 days
- Track via
DateCreatedtag - README cleanup section must reference 7-day policy
- Reference labs must justify permanence in README
R-016: Soft-Delete / Purge
Resources Requiring Purge
| Resource | Retention | Manual Purge | Requires Random Suffix (R-028) |
|---|---|---|---|
| Cognitive Services | 48 hrs | Yes | Yes — name reserved during retention |
| Key Vault | 7–90 days | Yes | Yes — name reserved during retention |
| API Management | 48 hrs | Yes | Yes — name reserved during retention |
| Recovery Vault | 14 days | Yes | Yes — backup items block name reuse |
| App Insights | 14 days | No | No — soft-delete can be disabled |
| Log Analytics | 14 days | No | No — soft-delete can be disabled |
Only resources whose names are reserved during soft-delete retention require random suffixes (see R-028). All other resources use static names.
Disable Patterns
Terraform:
soft_delete_enabled = false
purge_soft_delete_on_destroy = false # Unique names eliminate need to purge; false speeds up destroy
permanently_delete_on_destroy = true # Log Analytics
purge_protected_items_from_vault_on_destroy = true # Recovery Vault
Bicep:
softDeleteState: 'Disabled'
R-017: Deployment Method Priority
IaaC > Scripted > Manual
| Method | Use When |
|---|---|
| IaaC (Terraform / Bicep) | Deploying Azure resources, architecture focus, repeatable |
| Scripted (PowerShell/CLI) | Imperative workflows, operational focus |
| Manual (Portal / UI) | Portal navigation is tested, UI-centric |
If IaaC, always ask user to choose Terraform or Bicep. Never auto-select.
R-018: IaaC Validation Sequence
- Validate Syntax —
terraform validateorbicep build - Regional Capacity Test — for constrained services (see R-019)
- Final Validation —
terraform planor deployment preview
Terraform:
Use-AzProfile Lab
Test-Path terraform.tfvars
terraform init
terraform validate
terraform fmt
# Capacity tests here (R-019)
terraform plan
Bicep:
Use-AzProfile Lab
.\bicep.ps1 validate
# Capacity tests here (R-019)
.\bicep.ps1 plan
R-019: Capacity-Constrained Services
Services requiring regional capacity validation before deployment:
- Cosmos DB (
Microsoft.DocumentDB) - AI Search (
Microsoft.Search) - OpenAI / Cognitive Services (
Microsoft.CognitiveServices)
Validation commands:
az provider show --namespace Microsoft.DocumentDB `
--query "resourceTypes[?resourceType=='databaseAccounts'].locations[]"
az provider show --namespace Microsoft.Search `
--query "resourceTypes[?resourceType=='searchServices'].locations[]"
az provider show --namespace Microsoft.CognitiveServices `
--query "resourceTypes[?resourceType=='accounts'].locations[]"
If unavailable, use fallback regions per R-006.
R-020: Lab Subscription
e091f6e7-031a-4924-97bb-8c983ca5d21a
Must appear in:
terraform.tfvars→lab_subscription_id- Bicep → subscription context validation
- Validation scripts → subscription check
R-021: Language Style Conventions
| Tool | Style |
|---|---|
| Terraform | snake_case |
| Bicep | camelCase params |
| Azure Names | Prefix standards per R-002 / R-003 |
R-022: Module Rule
Use modules when 2+ related resource types are deployed.
- Domain grouping (one concern per module)
- Self-contained with clear inputs/outputs
- Pass
common_tags(TF) /commonTags(Bicep) to all modules - Pass identity references (e.g.,
principal_id) as explicit inputs for RBAC - Thin orchestration in root
main.tf/main.bicep - Anti-pattern: consolidating unrelated resource types into a single module
R-023: Common Tags Variable Pattern
Terraform:
locals {
common_tags = {
Environment = "Lab"
Project = "<EXAM>"
Domain = "<Domain>"
Purpose = "<Purpose>"
Owner = var.owner
DateCreated = var.date_created
DeploymentMethod = "Terraform"
}
}
Bicep:
var commonTags = {
Environment: 'Lab'
Project: '<EXAM>'
Domain: '<Domain>'
Purpose: '<Purpose>'
Owner: owner
DateCreated: dateCreated
DeploymentMethod: 'Bicep'
}
R-024: Password Generation
Must meet Azure complexity. Mark as @secure() (Bicep) or sensitive (Terraform). May be specified in a parameters file.
Terraform: Use hashicorp/random or supply via terraform.tfvars.
resource "random_password" "admin" {
length = 16
special = true
override_special = "!@#$%"
}
Bicep: Use uniqueString(), static pattern, or supply in .bicepparam. Mark @secure().
Target pattern: AzureLab2026!
R-025: Azure Configuration Guardrails
- Load Balancer SNAT: Set
disableOutboundSnat = truewhen frontend used for inbound + outbound - NIC + Public IP: NIC with instance public IP cannot join outbound backend pool
- Storage Containers (TF): Use
storage_account_idnotstorage_account_name - AI Services: Enable public network access for labs; validate model availability per region; start minimal capacity; output keys as sensitive
- AI Agent RBAC: Requires both data plane + control plane roles (e.g., Cosmos DB Operator)
For full details, see Governance-Lab.md at .assets/shared/Governance-Lab.md.
R-026: Bastion Dependency Ordering (Required)
Azure Bastion Developer SKU requires the VNet to be in Succeeded state. Creating Bastion in parallel with subnet resources causes a BastionHostVirtualNetworkNotFound race condition.
Always declare an explicit dependency so Bastion creation waits for all networking resources to complete.
Terraform — add depends_on to the module that contains azurerm_bastion_host:
module "compute" {
# ...
depends_on = [module.networking]
}
Bicep — add dependsOn to the Bastion resource or module:
resource bastion 'Microsoft.Network/bastionHosts@2024-10-01' = {
name: bastionName
// ...
dependsOn: [
networkingModule
]
}
R-027: Static Names by Default
All resource names must be static and predictable unless the resource is subject to soft-delete name reservation (see R-028).
- Static name: A literal string with no runtime-generated components (
uniqueString(),random_string, etc.). - Storage accounts, VMs, VNets, NSGs, subnets, load balancers, and all other non-soft-delete resources use static names.
- This keeps README code blocks, architecture diagrams, and testing steps simple and reproducible.
R-028: Random Suffix — Soft-Delete Resources Only
Random suffixes are only permitted for resources that enter a soft-deleted state where the name is reserved during the retention period. These are listed in R-016.
Resources Requiring Random Suffix
| Resource | Reason |
|---|---|
| Cognitive Services | Name reserved 48 hrs after deletion |
| Key Vault | Name reserved 7–90 days after deletion |
| API Management | Name reserved 48 hrs after deletion |
| Recovery Vault | Backup items block immediate name reuse |
Random Suffix Format
Bicep: '${resourcePrefix}${topic}${uniqueString(resourceGroup().id)}'
Terraform: "${resourcePrefix}-${topic}-${random_string.suffix.result}"
Length: 4 lowercase alphanumeric characters.
Resources That Do NOT Get Random Suffix
Even if globally unique (e.g., storage accounts), these resources do not need random suffixes because their names are released immediately on deletion:
- Storage Accounts
- AI Search
- Cosmos DB
- Log Analytics (soft-delete can be disabled)
- App Insights (soft-delete can be disabled)
R-029: README-to-IaC Resource Name Consistency
All resource names referenced in the README must exactly match the names defined in the IaC code. The README (authored in Phase 2) establishes the intended names. The IaC code (generated in Phase 3) must implement those same names.
This applies to all resource types: resource groups, VMs, storage accounts, VNets, NSGs, Key Vaults, and any other named Azure resource.
Scope
| README Section | What to Check |
|---|---|
| Architecture Diagram | Node labels and resource name annotations |
| Deployment | Resource group name, stack name, all -Name values |
| Testing the Solution | All -Name, -ResourceGroupName, and -StorageAccountId references |
| Cleanup | Resource group names, purge targets |
| Scenario Analysis | Code snippets referencing deployed resources |
Enforcement
- The Lab-Reviewer (Phase 4) must cross-reference every resource name in the README against the corresponding name in the IaC code.
- Any mismatch — in either direction — is a Category 1 Naming Compliance FAIL.
- The fix must align the IaC name to the README, or update the README to reflect the actual IaC name, before delivery.
- If the IaC uses a random suffix for a resource that should have a static name per R-027/R-028, flag it as a Naming Compliance FAIL.
R-030: Command-Line Fidelity
All command-line snippets in README Testing the Solution, Deployment, and Validation sections must be syntactically correct and produce the stated expected output when run against the deployed resources.
What to Check
| Check | Description |
|---|---|
| Property names | Cmdlet output properties must match the actual object model (e.g., AccountName not Name for Get-AzCognitiveServicesAccount; StorageAccountName not Name for Get-AzStorageAccount) |
| Nested properties | Properties that return complex objects must be expanded to show the intended value (e.g., $account.Sku.Name not $account.Sku when the expected output is a SKU tier string) |
| Format expressions | Format-List / Format-Table expressions must reference properties that exist on the object; use calculated properties (@{n=...;e=...}) when expanding nested values |
| Cmdlet parameters | All -ParameterName values must be valid for the cmdlet being invoked |
| Variable references | Variables set in earlier steps must be reachable in later steps within the same README section |
| Expected output comments | # Expected: comments must match what the command actually returns |
| API versions | REST API api-version values must be current and valid for the service |
| Pipeline correctness | Piped commands must accept the preceding output type |
Enforcement
- The Lab-Reviewer (Phase 4) evaluates command-line fidelity as Category 11.
- A FAIL in Category 11 does not block delivery on its own, but each issue must include an actionable fix.
- Common violations: wrong property names, unexpanded nested objects, stale API versions, invalid parameter names.
R-031: Role-Based Resource Names (No Topic Echo)
Resource names must describe the resource's role or function in the lab scenario — never echo the lab topic.
Why
When every resource is named <prefix>-<topic>, the names imply each resource IS the concept being studied rather than a resource that DEMONSTRATES the concept. This creates confusion, especially for resources that configure or contain the feature under study.
Rule
- The resource group retains the topic-based name per R-001 (e.g.,
az104-compute-keda-scaling-rule-bicep). - Individual resources within the lab use scenario-appropriate role names that describe what the resource does.
- Each resource should have a distinct, practical name — avoid giving every resource the same suffix.
Examples
Lab: lab-keda-scaling-rule (Container App with KEDA scaling)
| Resource Type | Bad (topic echo) | Good (role-based) |
|---|---|---|
| Container App | ca-keda-scaling-rule | ca-order-processor |
| Container Apps Environment | cae-keda-scaling-rule | cae-lab |
| Service Bus Namespace | sbns-keda-scaling-rule | sbns-orders |
| Log Analytics Workspace | law-keda-scaling-rule | law-monitoring |
Lab: lab-vnet-peering (Two peered VNets)
| Resource Type | Bad (topic echo) | Good (role-based) |
|---|---|---|
| VNet | vnet-vnet-peering-1 | vnet-hub |
| VNet | vnet-vnet-peering-2 | vnet-spoke |
| NSG | nsg-vnet-peering | nsg-web-tier |
Lab: lab-blob-versioning (Storage with versioning enabled)
| Resource Type | Bad (topic echo) | Good (role-based) |
|---|---|---|
| Storage Account | staz104blobversioning | staz104documents |
| Log Analytics | law-blob-versioning | law-monitoring |
Choosing Good Role Names
- Ask: "What does this resource represent in the scenario?" — not "What concept does this lab teach?"
- Draw names from the workload domain:
orders,web-frontend,monitoring,hub,spoke,documents,media,inventory. - Supporting infrastructure can use generic role names:
law-monitoring,cae-lab,nsg-default. - Keep names short (1–2 words after the prefix).