Skip to content

[Change Safety] Generator: emit inline IDynamicParameters (-AcquirePolicyToken/-ChangeReference) on write cmdlets (Stage D) - #1549

Merged
Yabo Hu (VeryEarly) merged 14 commits into
Azure:mainfrom
YangAn-microsoft:feature/change-safety-generator-static
Aug 10, 2026
Merged

Yabo Hu (VeryEarly) merged 14 commits into
Azure:mainfrom
YangAn-microsoft:feature/change-safety-generator-static

Conversation

@YangAn-microsoft

@YangAn-microsoft YangAn-microsoft commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Generator half of Change Safety for AutoRest management-plane modules, opt-in per module via an enable-change-safety config flag. Adds -AcquirePolicyToken and -ChangeReference to write-verb cmdlets and wires the pipeline step that acquires a policy token and stamps the x-ms-policy-external-evaluations header on write requests. Az.Accounts (Azure/azure-powershell#29840) fills the pipeline slot.

What it does

  1. Parameter surfacing. implementChangeSafetyParameters() in cmdlets/class.ts adds an inline IDynamicParameters.GetDynamicParameters() to generated write cmdlets, emitting -AcquirePolicyToken (SwitchParameter) and -ChangeReference (string) — the same dynamic-parameter mechanism SDK cmdlets use. Names + help text are reused verbatim from azure-powershell-common's ChangeSafetyParameters (a contract pin test in Accounts.Test keeps them in sync).
  2. Proxy forwarding. The public proxy function wraps the private cmdlet with a static param() block, so DynamicParamOutput (resources/psruntime/BuildTime/Models/PsProxyOutputs.cs) emits a dynamicparam block that forwards the wrapped cmdlet's dynamic parameters — self-gated on the wrapped cmdlet implementing IDynamicParameters. ExportProxyCmdlet.cs inserts it between the proxy param() block and begin{}.
  3. Pipeline hook. module/module-class.ts declares the ChangeSafetyPolicyTokenDelegate alias + AddChangeSafetyPolicyTokenHandler VTable property and invokes it in CreatePipeline right after OnNewRequest; generators/psm1.ts maps it in the .psm1.

internal/project.ts reads enable-change-safety into Project.enableChangeSafety (default false); internal/powershell-declarations.ts adds the IDynamicParameters declaration.

Gating

All change-safety output — parameters, proxy dynamicparam, and the VTable plumbing — is gated on enable-change-safety (management-plane only). Flag off (default): zero generated-code diff. The proxy dynamicparam is additionally self-gated on IDynamicParameters, so cmdlets without dynamic parameters are unaffected. The only unconditional change is to the two build-time proxy runtime sources (PsProxyOutputs.cs, ExportProxyCmdlet.cs), which emit nothing extra for cmdlets without dynamic parameters.

Scope

Seven files: powershell/cmdlets/class.ts, powershell/module/module-class.ts, powershell/generators/psm1.ts, powershell/internal/project.ts, powershell/internal/powershell-declarations.ts, powershell/resources/psruntime/BuildTime/Models/PsProxyOutputs.cs, powershell/resources/psruntime/BuildTime/Cmdlets/ExportProxyCmdlet.cs, plus refreshed tests-emitter baselines.

Verification

  • npm run build, npm run eslint, npm test, tests-sdk1-support (-AllowList -SkipCsharp): clean / all Equal.
  • tests-emitter baselines refreshed for the 43 whitelisted cases: only the two build-time runtime sources change per case (no Module.cs / .psm1 diff). The refresh also picks up a pre-existing stale runtime/Context.cs comment-typo baseline (fixed in the generator resource by Fix typo in comment: PSBoundParamters -> PSBoundParameters #1514; 25 cases).
  • Generated-module build + shape check (fresh regen of Az.ManagedServiceIdentity with --enable-change-safety): autorest → 425 files, exit 0; build-module.ps1 -NoDocs → exit 0. The emitted exports/ are correctly verb-gated — the dynamicparam block appears on all write cmdlets (New/Remove/Update-Az*, 6/6) and on none of the read cmdlets (Get-Az*, 0/4). The private write cmdlet's GetDynamicParameters() emits exactly -AcquirePolicyToken (SwitchParameter) and -ChangeReference (string) with help text matching azure-powershell-common's ChangeSafetyParameters; the public proxy re-declares them via the forwarding dynamicparam block.

Live end-to-end test (real Azure)

Re-validated on the latest upstream/main (merged into this branch) after rebuilding the generator (rush update && rush rebuild). Full flow on a real subscription with an opted-in module (Az.ManagedServiceIdentity, regenerated by this branch with enable-change-safety) plus the paired Az.Accounts build (Azure/azure-powershell#29840).

Command

New-AzUserAssignedIdentity -ResourceGroupName rg-cs-e2e -Name mi-cs-e2e -Location eastus -AcquirePolicyToken -Confirm:$false

The public proxy function accepted -AcquirePolicyToken — surfaced via the generated dynamicparam forwarding block this PR adds — and the identity was created (HTTP 200/201). A read cmdlet does not surface the parameter.

Pipeline trace (temporary token-safe debug hook; only metadata + a one-way SHA-256 hash of the stamped header are logged — never the raw token)

[PolicyTokenAcquirer] Intercept PUT .../userAssignedIdentities/mi-cs-e2e?api-version=2023-01-31
[PolicyTokenAcquirer] Payload prepared.
[PolicyTokenAcquirer] POST acquirePolicyToken .../Microsoft.Authorization/acquirePolicyToken?api-version=2025-03-01
[PolicyTokenAcquirer] Response 200 OK
[PolicyTokenAcquirer] Token acquired and header added.
writeRequest: PUT .../userAssignedIdentities/mi-cs-e2e?api-version=2023-01-31
  x-ms-policy-external-evaluations: sha256=789ed4cc92f969d73c3ad6d5004def21fdf381dce8998f822fd81802c1c7efb7 (len=4538)
  writeBody: { "location": "eastus" }

Note: the AutoRest -Debug dump prints the request at BeforeCall, before the appended change-safety step runs, so x-ms-policy-external-evaluations is not visible in that dump. The in-step trace above — a SHA-256 hash of the 4538-char token plus the stamped write request — confirms the token was acquired and the header stamped on the wire, and the write returned 200. Subscription/tenant IDs are redacted; test resources are provisioned in a BAMI subscription and removed after validation.

Dependencies

… to VTable dynamic-parameter delegate)

Emit -AcquirePolicyToken (switch) and -ChangeReference (string) as static [Parameter] properties on azure write-verb cmdlets, categorized Azure. No IDynamicParameters, no Module.cs/VTable delegate wiring; the module-level HTTP pipeline step reads the values from BoundParameters by name.
…eters

Gate implementChangeSafetyParameters on !endpointResourceIdKeyName (the generator's canonical data-plane signal, matching isDataPlane in module-class.ts), so only management-plane (ARM) write-verb cmdlets get -AcquirePolicyToken/-ChangeReference.
…meters)

Use the exact parameter names and help messages defined in azure-powershell-common ChangeSafetyParameters so AutoRest and SDK cmdlets present identically to users.
…flag

Add a change-safety project flag (default false) read from the module readme. implementChangeSafetyParameters only emits -AcquirePolicyToken/-ChangeReference when it is true, so the ~180-module rollout is deliberate (a module opts in alongside bumping its Az.Accounts minimum) rather than triggered by any unrelated regeneration.
@YangAn-microsoft
YangAn-microsoft marked this pull request as ready for review July 23, 2026 01:39
Comment thread powershell/cmdlets/class.ts Outdated
Comment thread powershell/internal/project.ts Outdated

@VeryEarly Yabo Hu (VeryEarly) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think the wiring of the runtime delegate to add headers is missing from this PR

…le-change-safety

Address review feedback on Azure#1549:
- Gate the -AcquirePolicyToken/-ChangeReference emission on the PowerShell verb
  instead of sniffing the HTTP method off requests[0]. A cmdlet's callGraph can
  hold multiple operations (e.g. GetPut Update = GET + PUT) and one operation can
  carry multiple requests, so requests[0] was unreliable. The verb also correctly
  treats POST list-style operations (surfaced as Get) as reads.
- Rename the opt-in config key change-safety -> enable-change-safety (and the
  Project.changeSafety property -> enableChangeSafety) so it reads as a toggle.
@YangAn-microsoft

Copy link
Copy Markdown
Contributor Author

Yabo Hu (@VeryEarly) re: "the wiring of the runtime delegate to add headers is missing from this PR" — that is intentional in this design. This is the static-parameter approach that replaced #1548 (which used a runtime-wired VTable delegate). Here the generator only emits the static -AcquirePolicyToken / -ChangeReference [Parameter] properties; their values flow through the cmdlet BoundParameters to the module-level HTTP pipeline step in Az.Accounts (Azure/azure-powershell#29840), which calls acquirePolicyToken and stamps the x-ms-policy-external-evaluations header. So there is deliberately no runtime-wired delegate in the generator anymore — that responsibility lives in Az.Accounts, keeping the generated cmdlet self-contained with no cross-repo runtime coupling. Design write-up: Azure/CLIPS#408.

…s (Stage D)

Switch the generator from emitting static -AcquirePolicyToken/-ChangeReference [Parameter] properties to an inline IDynamicParameters.GetDynamicParameters() implementation on azure management-plane write-verb cmdlets. This matches the dynamic-parameter mechanism SDK cmdlets use (AzurePSCmdlet/ChangeSafetyParameters), so SDK and AutoRest cmdlets are indistinguishable. The generated method builds the RuntimeDefinedParameterDictionary locally with names + help text mirroring azure-powershell-common's ChangeSafetyParameters (kept in sync by the Accounts.Test contract pin test); no runtime-wired VTable delegate. All gating (opt-in enable-change-safety flag, azure-only, data-plane excluded, verb-based write gating) is unchanged.
@YangAn-microsoft YangAn-microsoft changed the title [Change Safety] Generator: emit static -AcquirePolicyToken/-ChangeReference on write cmdlets (Stage D) [Change Safety] Generator: emit inline IDynamicParameters (-AcquirePolicyToken/-ChangeReference) on write cmdlets (Stage D) Jul 29, 2026
@YangAn-microsoft
YangAn-microsoft requested a review from Copilot July 29, 2026 05:33

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds opt-in support to emit Change Safety dynamic parameters (-AcquirePolicyToken, -ChangeReference) on generated Azure management-plane write-verb cmdlets by generating an inline IDynamicParameters implementation directly in each cmdlet (avoiding any Module/VTable runtime wiring), controlled by a new enable-change-safety configuration flag.

Changes:

  • Introduces enable-change-safety (default false) as a per-module configuration flag, exposed as Project.enableChangeSafety.
  • Adds a PowerShell declarations entry for System.Management.Automation.IDynamicParameters.
  • Updates cmdlet generation to (when gated conditions are met) implement IDynamicParameters and emit GetDynamicParameters() that returns a locally-built RuntimeDefinedParameterDictionary containing the two Change Safety parameters.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
powershell/internal/project.ts Adds enableChangeSafety and loads enable-change-safety config (default off) to gate emission.
powershell/internal/powershell-declarations.ts Declares IDynamicParameters so generated cmdlets can implement it.
powershell/cmdlets/class.ts Emits inline IDynamicParameters.GetDynamicParameters() for eligible Azure management-plane write-verb cmdlets, adding the two dynamic parameters.

…nNewRequest

Adds a dedicated AcquirePolicyTokenDelegate type and a PolicyTokenHandler property on the generated Module, invoked in CreatePipeline right after OnNewRequest (append-only). Emitted and mapped in the .psm1 only when enable-change-safety is set (azure management-plane), decoupling the change-safety step from OnNewRequest instead of composing into it.
… invoke

The AcquirePolicyTokenDelegate type, the Module PolicyTokenHandler property, and the .psm1 VTable mapping are now emitted for every management-plane module (like the other always-on delegates). Only the CreatePipeline invoke (and the per-cmdlet params) stay gated on enable-change-safety, so the plumbing is inert until a module opts in.
@YangAn-microsoft
YangAn-microsoft force-pushed the feature/change-safety-generator-static branch from feef16a to 87c5059 Compare August 3, 2026 02:19
…ndler

Align the Change Safety pipeline delegate with the existing Add...Handler VTable naming convention and give it a change-safety-qualified name. Delegate type AcquirePolicyTokenDelegate -> ChangeSafetyPolicyTokenDelegate.
AutoRest proxy functions use a static param() block, so the inline IDynamicParameters emitted on the private cmdlet never surfaced on the public proxy. Emit a dynamicparam block (only when a wrapped cmdlet implements IDynamicParameters) that forwards the private cmdlet's dynamic parameters, mirroring PowerShell's canonical ProxyCommand pattern. Self-gating: cmdlets without dynamic parameters get no block (zero diff).
…baselines

Option B: the AddChangeSafetyPolicyTokenHandler VTable delegate/property, its Module.cs alias, and the psm1 mapping are now emitted only when project.enableChangeSafety is set. Non-opted management-plane modules therefore have zero Module.cs/Az.*.psm1 diff. The always-present build-time runtime sources (ExportProxyCmdlet.cs dynamicparam forwarding + PsProxyOutputs.cs DynamicParamOutput) still change in every baseline. This baseline refresh also sweeps up the pre-existing Context.cs comment typo fix (PSBoundParamters -> PSBoundParameters, Azure#1514) whose baselines were last regenerated in Azure#1421.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 118 changed files in this pull request and generated no new comments.

Comment thread powershell/generators/psm1.ts Outdated
…k (address review)

Move the AddChangeSafetyPolicyTokenHandler append out of the data-plane else-if and into the control-plane default requestHandler build, gated on enableChangeSafety; the data-plane branch stays a clean override. Output is byte-identical.
@VeryEarly
Yabo Hu (VeryEarly) merged commit 22646ff into Azure:main Aug 10, 2026
3 checks passed
@YangAn-microsoft
YangAn-microsoft deleted the feature/change-safety-generator-static branch August 10, 2026 13:35
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.

3 participants