Files
regorus/docs/knowledge/azure-policy-aliases.md
copilot-swe-agent[bot] 524aab5528 Add knowledge docs, agent definitions, and skill files
Add comprehensive documentation and GitHub Copilot configuration:

- docs/knowledge/: 17 deep-dive knowledge files covering value semantics,
  RVM architecture, builtins, FFI boundary, feature composition, error
  handling migration, policy evaluation security, Rego semantics,
  interpreter/compiler architecture, Azure Policy/RBAC, engine API,
  time builtins, language extension guide, tooling architecture,
  causality/partial eval, Rego compiler, Azure Policy aliases, and
  telemetry/diagnostics

- .github/agents/: 16 role-specific AI agent definitions (red-teamer,
  semantics-expert, architect, performance-engineer, test-engineer,
  verification-engineer, security-auditor, reliability-engineer,
  support-engineer, ci-engineer, refactorer, api-steward, program-manager,
  demo-engineer, dx-engineer, tech-lead)

- .github/skills/: 6 workflow skill definitions (thorough-review,
  design-alternatives, add-builtin, opa-conformance, security-review,
  verification)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: anakrish <35780660+anakrish@users.noreply.github.com>
2026-04-25 21:20:10 +00:00

8.2 KiB

Knowledge: Azure Policy Aliases and Normalization

Deep knowledge about the Azure Policy alias system and ARM resource normalization. Read this before modifying alias resolution, the normalizer, or the denormalizer.

See also azure-policy-language.md for the overall Azure Policy compilation pipeline.

What Aliases Are

Azure Policy uses "aliases" to refer to Azure resource properties in a provider-independent way:

Full alias:  Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly
Short name:  supportsHttpsTrafficOnly
ARM path:    properties.supportsHttpsTrafficOnly

The alias system bridges between:

  • Policy authors — who write conditions using alias paths
  • ARM resources — which have nested JSON structures with varying casing

Alias Registry

Loading Sources

Control-plane aliases — loaded from Azure provider metadata:

GET /providers?$expand=resourceTypes/aliases

Produces ProviderAliases with resource type → alias mappings.

Data-plane aliases — loaded from data policy manifests for .Data namespaces (e.g., Microsoft.KeyVault.Data/vaults/secrets).

Registry Structure

struct AliasRegistry {
    // Maps full alias name → alias metadata
    aliases: BTreeMap<String, AliasInfo>,
    // Maps resource type → list of aliases
    resource_type_aliases: BTreeMap<String, Vec<String>>,
}

The registry provides:

  • Alias path segments (for navigating ARM JSON)
  • Alias type metadata (string, array, object, etc.)
  • Default path mappings when aliases are absent

Normalization Pipeline

The normalizer transforms ARM resource JSON into a flat structure that the policy compiler can evaluate directly.

Input: ARM Resource JSON

{
  "type": "Microsoft.Storage/storageAccounts",
  "id": "/subscriptions/.../storageAccounts/myaccount",
  "name": "myaccount",
  "location": "eastus",
  "properties": {
    "supportsHttpsTrafficOnly": true,
    "networkAcls": {
      "defaultAction": "Deny",
      "virtualNetworkRules": [
        { "id": "/subscriptions/.../subnets/default" }
      ]
    }
  }
}

Output: Normalized Resource

{
  "type": "microsoft.storage/storageaccounts",
  "id": "/subscriptions/.../storageAccounts/myaccount",
  "name": "myaccount",
  "location": "eastus",
  "supportshttpstrafficonly": true,
  "networkacls.defaultaction": "Deny",
  "networkacls.virtualnetworkrules": [
    { "id": "/subscriptions/.../subnets/default" }
  ]
}

Normalization Steps

  1. Copy root fields (lowercased): type, id, kind, name, location, identity, zones, sku, plan, tags

  2. Merge properties — contents of properties are merged into the result at the top level

  3. Apply alias path resolution:

    • Each alias has a path (e.g., properties.networkAcls.defaultAction)
    • The normalizer navigates the ARM JSON using path segments
    • The extracted value is placed at the alias short name (lowercased)
  4. Handle sub-resources — sub-resource types (e.g., extensions on VMs) are extracted from arrays and normalized separately

  5. Array element handling[*] in alias paths triggers iteration over array elements; each element is normalized independently

  6. Case folding — all property names are lowercased for case-insensitive matching (Azure ARM is case-insensitive)

Key Complexity: Case Preservation

ARM JSON casing is preserved through normalization and denormalization. The normalizer records original casing to enable round-trip fidelity. This matters for Modify/Append effects that construct output JSON.

Denormalization

The denormalizer converts flat normalized paths back to nested ARM JSON structure. This is needed for:

  • Modify effect — construct the resource patch to apply
  • Append effect — construct fields to add to the resource

Denormalization Challenge

Given a flat path like networkacls.defaultaction = "Allow", the denormalizer must reconstruct:

{
  "properties": {
    "networkAcls": {
      "defaultAction": "Allow"
    }
  }
}

This requires knowing:

  • Where properties nesting begins (alias metadata)
  • Original casing of each path segment
  • Whether intermediate nodes are objects or arrays

Compiler Integration

Alias Map

The compiler receives an alias map: BTreeMap<String, String> mapping alias short names to full ARM paths. This is populated from the AliasRegistry for the specific resource type being evaluated.

Field Compilation

When compiling a field condition:

{ "field": "supportsHttpsTrafficOnly", "equals": true }
  1. Look up field name in alias map
  2. If found: compile as property access on normalized input
  3. If dynamic ([concat(...)]): compile ARM expression, use result as key
  4. Emit Index/IndexLiteral/ChainedIndex instructions

Metadata Accumulation

During compilation, the compiler tracks:

  • observed_aliases — all alias names referenced
  • observed_field_kinds — static fields, dynamic fields, [*] wildcards
  • observed_resource_types — resource types from field conditions
  • observed_has_dynamic_fields — whether ARM expressions appear as fields

This metadata supports policy analysis and optimization.

Wildcard Semantics

Unbound [*] (outside count)

{ "field": "securityRules[*].destinationPortRange", "equals": "443" }

Implicit allOfevery element must match. The compiler generates a LoopStart { mode: Every } instruction.

Bound [*] (inside count)

{
  "count": {
    "field": "securityRules[*]",
    "where": { "field": "securityRules[*].destinationPortRange", "equals": "443" }
  },
  "greaterOrEquals": 1
}

Iteration with counting — each element is tested, matching ones are counted. The compiler generates LoopStart { mode: Count }.

Multi-level Wildcards

{ "field": "outer[*].inner[*].value" }

Nested loops: outer levels use ForEach, innermost carries the semantic operator. The compiler maintains a binding stack to track scope.

current() Function

Inside count.where blocks, current() refers to the current iteration element:

{
  "count": {
    "value": "[parameters('items')]",
    "name": "item",
    "where": {
      "value": "[current('item').status]",
      "equals": "active"
    }
  }
}

The compiler binds the loop variable and makes it accessible via current() calls in ARM template expressions.

Existence vs Null

Azure Policy distinguishes between missing fields and null values:

  • Missing fieldUndefined in regorus Value system
  • Null fieldValue::Null

For most operators, the compiler emits CoalesceUndefinedToNull to treat missing as null. The exists operator is the exception — it specifically tests for field presence:

{ "field": "optionalProperty", "exists": true }   // Field must be present
{ "field": "optionalProperty", "exists": false }  // Field must be absent

Key Invariants

  1. Normalization before compilation — aliases are resolved during normalization, not at compile time or runtime

  2. Case-insensitive everywhere — all field name comparisons use lowercased strings

  3. [*] context matters — same syntax has different semantics inside vs outside count expressions

  4. Round-trip fidelity — normalize → denormalize must preserve original ARM JSON casing for Modify/Append effects

  5. Missing = null (mostly)CoalesceUndefinedToNull is the default; exists is the exception

Common Pitfalls

  1. Alias path segments — paths like properties.a.b must be split correctly. Dots in property names (rare but possible) need escaping.

  2. Sub-resource normalization — sub-resources have their own type and their own alias set. Don't normalize with parent's aliases.

  3. Array vs scalar — some aliases point to arrays, others to scalars. The [*] wildcard only works on arrays. Applying it to a scalar is a compile-time error.

  4. Dynamic field resolution order — ARM template expressions in field positions are evaluated at runtime. The alias map must be available at runtime for dynamic alias resolution.