FinOps Azure Architecture
Architecture reference for the
/finops:azure-monthlycommand. Documents the Azure-specific execution pipeline, Portal CSV as SSOT for EA billing, multi-tenant authentication pattern, and cross-validation approach — including the Vircom PAYG incident (API vs Portal discrepancy).
Diagram 1: End-to-End Phase Flow
Diagram 2: SSOT Decision Tree
Portal CSV as SSOT
For Enterprise Agreement tenants, the Azure Portal billing export is the authoritative data source. The API (az rest against Cost Management) is RBAC-scoped — subscriptions where the service principal lacks Reader access are invisible.
The Vircom PAYG Incident
| Source | Value (NZD) | Scope |
|---|---|---|
API total (az rest) | NZD 27,890 | 6 RBAC-accessible subscriptions |
| Portal CSV export | NZD 43,498 | 7 subscriptions + credits |
| Delta | NZD 15,608 | Vircom PAYG subscription (35.9% of total) |
The Vircom PAYG subscription was not visible to az account list due to RBAC boundary. The API reported "30% under budget" when the actual spend was 8.7% over budget (pre-budget-update).
Rule: If a Portal CSV exists at cloudops/data/finops/azure/YYYY-MM/, it is the SSOT. API totals must include the caveat: "RBAC-scoped, may exclude ungoverned subscriptions."
Anti-pattern: FINOPS_API_SSOT_MISMATCH — presenting API-only totals without stating the RBAC caveat.
Multi-Tenant Authentication
Step 1: az account list
→ Returns all subscriptions in accessible tenants
→ Some tenants may require separate az login
Step 2: Identify tenant IDs
→ Group subscriptions by tenantId field
→ Flag subscriptions with inaccessible tenants
Step 3: Per-tenant token validation
→ az account get-access-token --tenant <tenantId>
→ Verify Cost Management scope access
Step 4: Run collection per tenant
→ Aggregate totals across tenants
→ Document inaccessible tenants in cross-validation.json
Never assume the AZURE_SUBSCRIPTION_ID environment variable represents the full tenant scope. Always enumerate all subscriptions via az account list before running cost queries.
Azure vs AWS Differences
| Dimension | AWS | Azure |
|---|---|---|
| Billing SSOT | Cost Explorer (API = authoritative) | EA: Portal CSV — PAYG/MCA: API |
| Auth model | IAM profiles with SSO | Service principal or az login per tenant |
| Multi-account | Organizations API auto-enumerates | az account list — tenants may be separate |
| Currency | USD (default) | NZD for ANZ customers |
| Org hierarchy | Management account + linked accounts | Billing account + subscriptions + resource groups |
| Recommendation source | Cost Explorer + Trusted Advisor | Azure Advisor (recommendationTypeId required) |
| RBAC visibility gap | SCP boundaries (per-account) | Subscription-level RBAC (per-subscription) |
| FOCUS 1.2+ output | account-costs.csv | FOCUS 1.2 CSV with provider mapping |
| HITL gate | Report total threshold | Same threshold, NZD currency |
Live-Validated Results
Results from a 10-subscription Azure Landing Zone (2026-03-24):
| Metric | Result | Notes |
|---|---|---|
| Subscriptions visible | 10 | az account list across 2 tenants |
| Tenants | 2 accessible + 2 inaccessible | Document inaccessible tenants |
| Portal CSV | SSOT for EA billing | cloudops/data/finops/azure/YYYY-MM/ |
| CFO mode | Working | NZD totals, spend vs budget |
| CTO mode | Working | Service breakdown |
| FOCUS 1.2+ output | CSV generated | Provider field maps to Microsoft.Azure |
| HITL gate | Active | Triggers when total exceeds threshold |
| Evidence path | tmp/cloud-infrastructure/finops/azure/ | Ephemeral |
Quality Gates
| Gate | Criteria | Action on Fail |
|---|---|---|
| G0 | az account list returns at least one subscription | HALT, run az login |
| G1 | Cost Management API accessible | Check RBAC role assignment |
| G2 | Portal CSV exists (EA only) | Export from Azure Portal before running |
| G3 | API total vs Portal total gap documented | Document RBAC-invisible subscriptions |
| G4 | Advisor recommendations include recommendationTypeId | Remove unverified savings estimates |
| G5 | FOCUS 1.2+ CSV written | Verify provider field mapping |
| G6 | HITL gate passed for reports over threshold | Block distribution |
# Install
pip install runbooks
# Single subscription
uv run python -m runbooks.main finops azure-dashboard --subscription $AZURE_SUBSCRIPTION_ID
# Full tenant (recommended)
uv run python -m runbooks.main finops azure-dashboard --all --mode cfo
See the Azure quickstart guide for step-by-step instructions including Portal CSV export.
For Enterprise Agreement tenants, run the Portal billing export BEFORE executing the command. Without the Portal CSV, the API-only total will miss RBAC-invisible subscriptions.
Export path: Azure Portal → Cost Management + Billing → Exports → Download CSV → save to cloudops/data/finops/azure/YYYY-MM/
Anti-Patterns
| Anti-Pattern | Description | Prevention |
|---|---|---|
FINOPS_API_SSOT_MISMATCH | Presenting API total as authoritative for EA tenants | Always check for Portal CSV; document RBAC gap |
AZURE_SINGLE_SUBSCRIPTION_ASSUMPTION | Targeting single subscription without enumerating all | Run az account list first |
FINOPS_HALLUCINATED_SAVINGS | Savings figures not sourced from Azure Advisor | All savings must cite recommendationTypeId |
NATO_VIOLATION | Claiming "report generated" without cross-validation.json | Require evidence file before completion claim |
Architecture validated against CloudOps-S1 sprint, 2026-03-24. Component versions: runbooks v1.3.4, ADLC framework v3.7.2.