Skip to content

feat(skills): adopt AVM-first Azure module sourcing with azure-iac-solution skill - #2877

Open
Stu Mace (kewalaka) wants to merge 7 commits into
microsoft:mainfrom
kewalaka:kewalaka-avm-usage-feedback-pr
Open

feat(skills): adopt AVM-first Azure module sourcing with azure-iac-solution skill#2877
Stu Mace (kewalaka) wants to merge 7 commits into
microsoft:mainfrom
kewalaka:kewalaka-avm-usage-feedback-pr

Conversation

@kewalaka

@kewalaka Stu Mace (kewalaka) commented Sep 9, 2026

Copy link
Copy Markdown

Description

Adds an AVM-first module sourcing policy for Azure infrastructure as code.

azure-iac-solution skill (.github/skills/coding-standards/azure-iac-solution/) is the single owner of the policy:

  • A four-tier sourcing hierarchy applied per capability: AVM pattern module → AVM resource/utility modules → native azurerm/azapi/Bicep resource → local module. AVM-first is not AVM-only; a native resource is acceptable when its reason is recorded.
  • Lifecycle rules following the AVM module index: adopt only Available modules; Proposed is unpublished, Orphaned and Deprecated are reported rather than adopted silently.
  • Version pinning that reflects AVM's pre-1.0 Terraform modules (~> 0.10.0 or exact) and exact Bicep tags.
  • A module decision record that the user confirms before any code is generated, persisted with the implementation plan or at infra/module-decisions.yaml, so exceptions can be re-evaluated later.
  • references/module-discovery.md: the runtime procedure for resolving names, status, and versions from the AVM module-index CSVs, the Terraform registry API, and MCR tags (or the Azure MCP azureterraform/bicepschema tools).

terraform and bicep instructions gain a short "Azure Module Sourcing" section that points at the skill and carries only language-specific facts: registry namespace/path form, pin syntax, and a pinned module example. Project structure examples now scaffold around registry modules rather than local networking/storage/compute modules.

Terraform authoring corrections shipped alongside because they are fundamental smells in Azure Terraform:

  • count vs for_each follows AVM TFNFR7: count for conditional 0/1 creation, for_each over a map for collections.
  • validation blocks are expected for variables with known domain constraints.
  • Imperative terraform_data + local-exec + az lookups are replaced with provider or azapi data sources.
  • Backend configuration keeps environment values out of committed HCL.
  • ignore_changes = [tags] is no longer a default; it is a scoped governance decision with the owning system recorded.
  • tfsec (archived into Trivy) is replaced with trivy config or checkov.

Related Issue(s)

Closes #2876

Type of Change

Code & Documentation:

  • New feature (non-breaking change adding functionality)
  • Documentation update

AI Artifacts:

  • Reviewed contribution with hve-builder and addressed all actionable findings
  • Copilot instructions (.github/instructions/*.instructions.md)
  • Copilot skill (.github/skills/*/SKILL.md)
  • Eval spec added/updated for changed AI artifacts (evals/)

Sample Prompts (for AI Artifact Contributions)

/azure-iac-solution Design a private Container Apps environment with central diagnostics and produce the Terraform for it.
Review infra/main.bicep for module reuse gaps against Azure Verified Modules.
I need a storage account and virtual network in Terraform. Which module sources apply, and how should the versions be pinned?

Testing

Behavior-conformance stimuli (advisory tier):

  • skill-behavior.eval.yaml: 5 stimuli for azure-iac-solution covering tier ordering (sequence-locked), tool trigger with decision-record confirmation, bleed detection, exception record and location, and Proposed-status handling. Negative graders reject fabricated module versions.
  • instructions.eval.yaml: 4 stimuli for terraform and bicep covering skill handoff, patch-series pinning (negative grader rejects ~> x.y), TFNFR7 count/for_each selection, and exact Bicep tag pinning.

Every grader was checked offline to reject a verbatim echo of its prompt and accept a correct answer.

Local validation: plugin:validate, validate:skills, lint:frontmatter, lint:md, lint:tables, lint:ai-artifacts, lint:asset-docs, spell-check, ci:eval:lint:schema, ci:eval:lint:text all pass; docs:generate produces no drift.

Checklist

Required Checks

  • Documentation is updated (if applicable)
  • Files follow existing naming conventions
  • Changes are backwards compatible (if applicable)
  • Tests added for new functionality (if applicable)

AI Artifact Contributions

  • Used hve-builder review mode to review contribution
  • Addressed all actionable findings from the hve-builder review
  • Verified contribution follows common standards and type-specific requirements

Required Local Checks

  • Local validation aggregate: npm run validate:local
  • Documentation validation: docs:generate plus lint:asset-docs
  • Spell checking: npm run spell-check
  • Link validation: npm run lint:md-links

Security Considerations

No secrets, credentials, or network calls at rest. The discovery procedure documents read-only public endpoints (GitHub raw, Terraform registry, MCR). Backend guidance moves state-account identifiers out of committed configuration.

Additional Notes

Policy lives in one place (the skill); the instruction files carry only what is language-specific. The skill description no longer claims to select between Bicep, Terraform, AzureRM, and AzAPI, since the body does not cover that decision.

@kewalaka
Stu Mace (kewalaka) requested a review from a team as a code owner September 9, 2026 21:09
Stu Mace (kewalaka) and others added 7 commits September 10, 2026 09:49
…and bicep

- add mandatory Azure module sourcing hierarchy to both language instructions
- prefer azapi data sources over local-exec lookups in terraform guidance
- recommend for_each over count and document backend parameterization
- qualify ignore_changes guidance as a governance decision

🤖 - Generated by Copilot

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- sourcing hierarchy, suitability test, and discovery rules
- module decision record contract and end-to-end workflow
- AVM-aware review checklist for generated infrastructure

🤖 - Generated by Copilot

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- skill knowledge, tool-trigger, bleed-detection, and exception stimuli
- instruction conformance stimuli for terraform and bicep AVM sections

🤖 - Generated by Copilot

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

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- reduce terraform and bicep sourcing sections to a pointer at azure-iac-solution
- pin AVM Terraform modules to a patch series; pin Bicep tags exactly
- follow AVM TFNFR7: count for conditional 0/1, for_each for collections
- scaffold project structure around registry modules, not local resource groupings
- replace archived tfsec with trivy
- collapse hierarchy and suitability test into one tiered table with utl class
- add lifecycle status handling (Proposed, Available, Orphaned, Deprecated)
- require user confirmation of the decision record before generation
- replace abstract worked examples with a runtime module-discovery procedure
- persist the decision record with the plan or infra/module-decisions.yaml
- assert tier ordering and count/for_each selection with sequence-locked patterns
- add negative graders for fabricated versions and minor-floating pins
- add Proposed-status stimulus; update inventory counts
@kewalaka
Stu Mace (kewalaka) marked this pull request as draft September 9, 2026 22:24
@kewalaka
Stu Mace (kewalaka) force-pushed the kewalaka-avm-usage-feedback-pr branch from 37bc1c5 to 854eabc Compare September 9, 2026 22:27
@kewalaka
Stu Mace (kewalaka) marked this pull request as ready for review September 9, 2026 22:29

@WilliamBerryiii Bill Berry (WilliamBerryiii) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the thoughtful work on this AVM capability. The discovery guidance, language integration, and conformance coverage provide a strong foundation for helping teams make better-informed Azure IaC decisions.

We need AVM selection to be an evidence-backed, user-confirmed architecture decision rather than a universal language convention. HVE Core supports many greenfield and brownfield projects with valid non-AVM implementations, including native azurerm, azapi, Bicep resources, and established local modules. Those projects may already have approved ADRs, stable Terraform state addresses, imported resources, provider and API constraints, organizational module boundaries, or established operational ownership. Automatically applying an AVM-first workflow through every matching Terraform or Bicep instruction can supersede those decisions before the project's requirements and constraints have been assessed.

Technically, selecting an AVM module changes more than source syntax. It introduces a module interface and release lifecycle, registry or mirror dependencies, publisher and provenance assumptions, version and upgrade policy, transitive dependencies, supported API coverage, and potentially additional resources or topology coupling. Replacing an existing implementation can also change resource addressing or ownership and may introduce state moves, imports, replacement risk, or migration work. These are legitimate tradeoffs, but they belong at the architecture and dependency-selection boundary, with evidence and user acceptance, rather than in an always-on language rule.

The intended separation of responsibilities is:

  • Terraform and Bicep instructions remain broadly applicable and may recognize when capability-level sourcing is relevant.
  • azure-iac-solution assesses supplied ADRs, BRDs, PRDs, architecture documentation, security plans, and repository constraints before comparing AVM and non-AVM options.
  • AVM is recommended only when a candidate satisfies the project's functional and non-functional requirements, security and compliance needs, dependency policy, supply-chain expectations, and operational constraints.
  • The user can accept the recommendation, decline it, retain an existing implementation, or choose a native resource without being treated as non-conformant unless the repository has explicitly adopted AVM as policy.

This keeps AVM available and easy to discover while preserving compatibility with existing projects and maintaining a clean architectural boundary between language guidance and solution-level technology selection. The detailed review comments describe the requested activation rules, requirements traceability, user-choice paths, discovery safeguards, and workflow-evidence model. Thank you again for taking this on. Please reach out with questions as you work through the feedback; we will be glad to review the next revision.

As a general note if any of these issues are too much of a lift, totally OK. I'm working on remediating the Security Models/skill remediation and I'm happy for you to skip that work and I'll circle back around as part of our continuing security compliance rigor efforts.

copies or wrappers.

## Discovery Rules

@WilliamBerryiii Bill Berry (WilliamBerryiii) Sep 11, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please bind this workflow to the repository's canonical untrusted-content boundary. The skill ingests AVM indexes, registry/MCP responses, repository content, and supplied planning documents before those values influence tools, module decisions, and persisted records. Without an explicit binding, embedded directives can be mistaken for workflow authority.

Suggested change

Reference .github/instructions/shared/untrusted-content-boundary.instructions.md through the portable relative path required by HVE artifacts. Keep that instruction as the single owner of the rule that fetched, provider, MCP, PR, repository, ADR, BRD, and PRD content is data rather than instructions. Reuse the OWASP LLM prompt-injection and supply-chain references for threat scenarios instead of duplicating policy in this skill.

Add focused Vally behavior cases with instruction-like content in remote fields and supplied documents.

Acceptance checks

  • Embedded content cannot change role, autonomy, source authority, review gates, or confirmed requirements.
  • Untrusted text does not trigger tools or command construction.
  • The source skill references the canonical boundary rather than restating a divergent version.
  • Tests cover both remote responses and user-supplied planning artifacts.

---
name: azure-iac-solution
description: >
Design, generate, and review Azure infrastructure as code with Azure Verified Modules (AVM)

@WilliamBerryiii Bill Berry (WilliamBerryiii) Sep 11, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we narrow this semantic trigger? Skill descriptions are routing metadata, and this one currently matches nearly every Azure design, implementation, conversion, review, and modernization request. Once loaded, the body requires discovery, a decision record, and confirmation, so broad semantic matching effectively turns an optional capability into the default workflow.

Suggested change

Route explicit AVM evaluation, Azure module sourcing, architecture-to-module composition, or requirements-backed module-fit assessment. Ordinary Terraform/Bicep mechanics should remain owned by their language instructions. When module selection is relevant, the language instruction can offer this skill and load it after acceptance.

The routing description should also recognize evidence supplied through ADRs, BRDs, PRDs, security plans, and architecture documentation without implying that every Azure request needs a new requirements interview.

Acceptance checks

  • Explicit AVM/module-sourcing requests match the skill.
  • Syntax-only and implementation-preserving edits do not match it.
  • A general Azure IaC request can be handled without entering the gated workflow when the user declines.
  • Generated reference documentation reflects the same activation boundary.


### Azure Module Sourcing

Azure infrastructure composes [Azure Verified Modules](https://aka.ms/avm) before authoring `azurerm` or `azapi` resources directly. The `azure-iac-solution` skill owns the sourcing hierarchy, discovery procedure, and exception record; load it for any Azure implementation or review. In Terraform:

@WilliamBerryiii Bill Berry (WilliamBerryiii) Sep 11, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for centralizing the Azure module-sourcing policy. The current sentence applies to every matching Terraform session and says to load azure-iac-solution for any Azure implementation or review. That makes AVM evaluation automatic even when the user asks for language mechanics, has already selected native resources, or has not adopted AVM as a review criterion.

Suggested change

Keep the Terraform and Bicep instructions active by default. Offer azure-iac-solution only when the task requires a capability-level implementation choice, such as new solution design, architecture-to-IaC conversion, module-versus-resource selection, or a requested reuse/modernization review. Do not offer or load it for syntax, expressions, formatting, validation commands, file layout, or edits that preserve an existing source choice unless the user requests sourcing review.

The offer should explain the value and preserve choice, for example: "This task needs an implementation choice for storage and networking. I can compare AVM and native resources against your requirements, security posture, dependency policy, and supply-chain constraints. Run that evaluation, or continue with native Terraform guidance?"

Acceptance checks

  • Direct invocation enters the AVM evaluation.
  • A generic Azure IaC request receives an offer before the full workflow loads.
  • Language-only work does not enter AVM discovery.
  • Declining the offer continues with native Terraform/Bicep guidance.
  • Review applies AVM criteria only when requested or when the repository records AVM as adopted policy.


Terraform:

```bash

@WilliamBerryiii Bill Berry (WilliamBerryiii) Sep 11, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This discovery path should define a validated response contract before remote values influence module selection, commands, persisted evidence, or generated IaC. Today the examples use silent curl and non-strict jq, and the procedure does not distinguish malformed/unavailable responses from a legitimate no-match result.

Suggested change

Keep the Azure-specific controls in module-discovery.md:

  • Allowlist the AVM index, Terraform Registry, and MCR HTTPS hosts.
  • Validate content type, response size, status, redirects, timeout, and expected CSV/JSON shape.
  • Restrict fields and types, AVM namespace/reference patterns, lifecycle values, and SemVer.
  • Treat empty, duplicate, unknown, or identity-mismatched results as explicit non-success outcomes.
  • Never interpolate response fields into shell commands.
  • Prefer structured HTTP/MCP results; if shell examples remain, use fixed arguments, curl --fail-with-body --proto '=https' with bounds, and jq -e predicates.

Acceptance checks

Adversarial cases for shell metacharacters, redirects, non-2xx responses, oversized/malformed content, duplicate or empty matches, unknown lifecycle states, invalid identities, and invalid versions all fail closed or enter the documented discovery-unavailable path without constructing a response-derived command.


## Module Decision Record

Produce the decision record before generating code and confirm it with the user. AVM

@WilliamBerryiii Bill Berry (WilliamBerryiii) Sep 11, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make the decision branches explicit here. The current workflow has one path: discover AVM candidates, create a record, require confirmation, then generate code. It does not define what happens when the user declines AVM, no candidate meets the requirements, discovery is unavailable, or the request is specifically for native resources.

Suggested change

First assess supplied ADRs, BRDs, PRDs, architecture documentation, security plans, and other approved sources. Preserve requirement/decision IDs, approval status, and applicability; surface conflicts or gaps; and ask only targeted questions for missing evidence. Compare AVM pattern modules, AVM resource modules, native resources, and local modules against functional fit, security/compliance, dependency policy, supply-chain provenance, and operational constraints.

Record accepted, declined, unsuitable, and unavailable outcomes. Require the detailed AVM record and confirmation only after the user accepts a suitable AVM recommendation or explicitly requests AVM composition. A decline or unsuitable result should continue with the next viable native approach.

Acceptance checks

  • Every suitability result traces to requirement and decision IDs.
  • Unknown evidence is not treated as a pass.
  • Decline and unsuitable paths continue without AVM blocks.
  • Discovery failure is not recorded as proof that no AVM exists.
  • The user sees the material tradeoffs before confirming a source choice.

Produce the decision record before generating code and confirm it with the user. AVM
positions AI as an assistant: humans own the architecture decision, so do not proceed from
record to implementation without that confirmation. Persist the record with the
implementation plan, or at `infra/module-decisions.yaml` when no plan exists, so reviewers

@WilliamBerryiii Bill Berry (WilliamBerryiii) Sep 11, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The decision record persists requirement text and remote source/version/status values, but no closed schema, serializer, or reproducible provenance contract is defined. A multiline or structured external value could alter the YAML shape, and recording only a lookup date is insufficient to reconstruct the exact evidence later.

Suggested change

Define and validate a closed schema. Serialize through a structured YAML API, quote externally derived scalars, reject unexpected keys/types and control characters, and apply field-length bounds. Record the exact source URL, retrieval timestamp, response/source commit digest, publisher identity, mirror identity, and independently verified module reference/version/status. Render the serialized result back to the user before confirmation.

Acceptance checks

  • Round-trip tests cover multiline text, YAML structure characters, duplicate keys, control characters, and instruction-like strings.
  • Invalid input cannot add or replace record fields.
  • Provenance identifies the exact evidence used for the decision.
  • A later review can distinguish source changes from the originally confirmed evidence.

Public-registry consumption is the default. Where organizational review is required, resolve
the same modules through a synchronized private registry rather than maintaining internal
copies or wrappers.

@WilliamBerryiii Bill Berry (WilliamBerryiii) Sep 11, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is part of an active repository-wide remediation, so this contribution is not uniquely behind. Because this new workflow introduces network egress, MCP/tool use, subprocess examples, untrusted parsing, generated IaC, and persisted records, it is a good point to adopt the current per-skill security-model contract.

Suggested change

Create .github/skills/coding-standards/azure-iac-solution/SECURITY.md from docs/templates/skill-security-model-template.md and follow .github/instructions/skill-security-model.instructions.md. Model the network, MCP/tool, subprocess, parser, planning-artifact, generated-IaC, credential, and persisted-record boundaries. The mural, jira, and gitlab models are useful examples for response bounds, redirects, content types, credentials, and residual gaps. Register the model and primary residual gaps in docs/security/security-model.md.

Acceptance checks

  • The model covers all six STRIDE categories for each actual trust bucket.
  • Gaps use the repository's G-* identifiers and severity format.
  • The repository-wide model links the new skill model.
  • The model documents observed controls and residual gaps without claiming unimplemented protections.

config:
pattern: "(?i)~>\\s*0\\.\\d+\\.\\d+|exact\\s+version|patch"
- type: output-matches
name: no-minor-floating-pin

@WilliamBerryiii Bill Berry (WilliamBerryiii) Sep 11, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This negative pattern detects a two-component constraint only when a double quote follows it. A response such as Use a patch constraint: ~> 0.10 can satisfy the positive grader through the word "patch" while evading this negative assertion.

Suggested change

Reject ~> <major>.<minor> independently of the following delimiter, including quote, backtick, punctuation, whitespace, or end of output. Keep the expression from matching a valid three-component constraint accidentally; if the policy moves to exact-only versions, update the grader to reject every ~> range instead.

Acceptance checks

  • Fails: ~> 0.10, `~> 0.10`, and version = "~> 0.10".
  • Passes under the current policy: ~> 0.10.0 and exact 0.10.3.
  • Passes only exact 0.10.3 if Finding 7's exact-version policy is adopted.
  • Grader tests prove both positive and negative examples.

The skill extracts intent and non-functional requirements, resolves candidates from the AVM module index and registry, and emits a module decision record for confirmation before writing code:

```yaml
capabilities:

@WilliamBerryiii Bill Berry (WilliamBerryiii) Sep 11, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please document the lifecycle and ownership of this evaluation evidence. The source skill currently defaults to an implementation plan or infra/module-decisions.yaml, which can put AI-assisted workflow evidence into the product tree even when the user has not chosen to make it project documentation.

Suggested change

Following the repository's planning/backlog pattern, use .copilot-tracking/azure-iac-solution/<solution-slug>/ by default:

  • state.json stores phase, source pointers/revisions, current record pointer, user choice, unresolved questions, and next action.
  • module-decisions.yaml is the canonical record for requirement/decision IDs, candidate evidence, suitability, accepted risks, user decisions, and re-evaluation triggers.
  • State stores pointers and resumable status rather than copying the full record.

Promote a decision into an ADR, implementation plan, or repository policy only when the user requests project-owned documentation. Do not create infra/module-decisions.yaml by default, and keep generated IaC independent of .copilot-tracking/ at runtime.

Acceptance checks

Resume restores the active record without duplication; updates preserve stable IDs; promotion is explicit and traceable; declining AVM does not create a production-tree record; and tracking paths never appear as runtime dependencies in generated Terraform/Bicep.

config:
pattern: '(?i)azure-iac-solution'
- type: output-matches
name: decision-record-and-confirmation

@WilliamBerryiii Bill Berry (WilliamBerryiii) Sep 11, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assertion encodes confirmation as a universal pre-code gate, so it would reject the intended offered-and-optional workflow. The current Python bleed case proves only that non-IaC code avoids AVM; it does not cover language-only IaC, explicit decline, review without adopted AVM policy, unsuitable candidates, or unavailable discovery.

Suggested change

Replace the single universal-gate expectation with branch-sensitive scenarios:

  • Direct AVM invocation proceeds to requirements-backed evaluation and confirmation.
  • Generic Azure IaC offers evaluation before loading the full workflow.
  • Explicit decline continues with native resources.
  • Syntax/language-only help does not activate AVM sourcing.
  • Review offers AVM only when requested or adopted as repository policy.
  • Unsuitable and unavailable candidates do not produce AVM blocks or fabricated evidence.
  • ADR/BRD/PRD inputs preserve requirement IDs and ask only for gaps.

Acceptance checks

Each scenario has both positive and negative assertions for activation, user choice, record creation, persistence location, source traceability, and generated implementation kind. A correct decline or unavailable response must pass without a mandatory AVM confirmation phrase.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(skills): adopt AVM-first module sourcing for Azure IaC guidance

2 participants