| name | mpg-migration |
| description | Handles Azure SDK for .NET management-plane migrations from AutoRest/Swagger to TypeSpec; use for MPG, mgmt migration, or Azure.ResourceManager.* migration requests. |
MPG Migration Workflow
Use for Azure SDK for .NET management-plane migrations from AutoRest/Swagger to TypeSpec for packages named Azure.ResourceManager.*.
Inputs
- SDK package path:
sdk/<service>/Azure.ResourceManager.<Service>/
- Local
azure-rest-api-specs path if not at ../azure-rest-api-specs
- Branches in both repos, normally
<service>-mpg-migration from main
Non-Negotiables
- Never hand-edit
src/Generated/ or metadata.json.
- Never add ApiCompat baselines or disable ApiCompat/package validation, except targeted
WirePathAttribute removal entries in the centralized baseline file.
- Prefer
client.tsp decorators with "csharp" scope before SDK custom code.
- Root-cause every ApiCompat/API diff before adding custom code.
- SDK custom code is only for backward compatibility or cases decorators cannot express.
- Do not overwrite dirty user work in either repo.
Ask the user only when the spec repo path is missing, a fix requires files beyond client.tsp, or a generator bug has no safe workaround.
Setup
- If resuming, read the draft SDK PR description first and continue from its phase/blockers.
- Check
git status in SDK and spec repos, then sync both to latest main without disturbing unrelated dirty files.
- Fresh migration only: inventory and remove old SDK custom code (
src/Custom/, src/Customization/, src/Customized/, hand-written partials, old shims) before first generation. Re-add only proven-needed compatibility shims later.
- Remove AutoRest config: delete
<IncludeAutorestDependency>true</IncludeAutorestDependency> and remove/replace src/autorest.md.
- Set
tsp-location.yaml to emitterPackageJsonPath: eng/azure-typespec-http-client-csharp-mgmt-emitter-package.json.
- Generate with saved inputs so
tspCodeModel.json is preserved for hierarchy checks. -Services must be the exact package folder name, not service directory:
pwsh eng/packages/http-client-csharp-mgmt/eng/scripts/RegenSdkLocal.ps1 -Services "Azure.ResourceManager.<Service>" -LocalSpecRepoPath <path> -SaveInputs
Alternative from the package src/ directory: dotnet build /t:GenerateCode /p:SaveInputs=true.
- Build after first generation. Errors are expected; classify them in the build-fix loop.
Resource Hierarchy Gate
Before the build-fix loop, compare the generated hierarchy with the previous GA SDK. The previous GA DLL is restored automatically by ApiCompat from <ApiCompatVersion>. Write JSON outputs to temp/package-local scratch and do not commit them.
$scripts = "eng/packages/http-client-csharp-mgmt/eng/scripts"
pwsh $scripts/Get-PreviousGaResourceHierarchy.ps1 -ProjectPath sdk/<svc>/Azure.ResourceManager.<Svc>/src -OutFile ga-hierarchy.json
pwsh $scripts/Get-ResourceHierarchyFromTspCodeModel.ps1 -TspCodeModelPath sdk/<svc>/Azure.ResourceManager.<Svc>/src -GeneratedDir sdk/<svc>/Azure.ResourceManager.<Svc>/src/Generated -OutFile new-hierarchy.json
pwsh $scripts/Compare-ResourceHierarchy.ps1 -GAJson ga-hierarchy.json -NewJson new-hierarchy.json
- Exit
0: every GA resource exists in the new SDK with the same ResourceType, parent set, scope, and singleton flag; continue.
- Exit
1: structural drift. Fix TypeSpec resource shape first (@parentResource, @singleton, scopes/templates). Do not enter ApiCompat mitigation yet.
- Exit
2: class-name renames only. Continue, but record and handle during naming/API compatibility work.
For C# base-model/base-type compatibility, do not use @@hierarchyBuilding; use SDK custom code after the generated surface is stable.
Build-Fix Loop
Repeat: build → classify → fix → regenerate if needed → rebuild.
Always verify zero Swagger diff after spec changes. Keep a running status list grouped by issue category so real migration breaks, compatibility shims, and generator bugs stay distinct.
Classify each issue before fixing:
| Category | Preferred action |
|---|
| Spec shape: names, types, paging, visibility, grouping | Fix in client.tsp with scoped decorators |
| SDK compatibility gap: missing/changed shipped API | Add minimal SDK shim only after root cause is known |
| Customization drift | Remove stale custom code; re-add only proven shims |
| Generated-code drift | Regenerate/export API; do not hand-edit generated files |
| Generator bug | Stop, minimize repro, file/link issue |
Common decorator fixes:
| Problem | Decorator |
|---|
| Wrong property type | @@alternateType(Model.prop, targetType, "csharp") |
| Wrong operation parameter type | @@alternateType(Interface.op::parameters.param, targetType, "csharp") |
| Wrong name | @@clientName(target, "NewName", "csharp") |
| Model should be input and output | @@usage(Model, Usage.input, "csharp"); decorator appends, so specify only the missing flag |
| Needs pageable return type | @@markAsPageable(Interface.op, "csharp") |
| Flatten properties envelope | @@flattenProperty(Model.properties, "csharp") |
| Operation name collision | @@clientLocation(Interface.op, "GroupName", "csharp") |
SDK custom code goes in the package's existing customization folder (src/Custom/, src/Customization/, or src/Customized/). Use MCP tools for deterministic edits when available, then hand-write only remaining shim logic. Useful MCP edits include add_using_directive, remove_using_directive, regex_replacement, nullable_annotation_fix, rename_codegen_type, and add_codegen_suppress. Regenerate when CodeGen* attributes change.
Every customization file or significant custom member needs a root-cause comment explaining what generated differently and why SDK-side customization is required. Avoid vague comments like "for backward compatibility". Obsolete custom members do not need a separate justification comment when the [Obsolete] message already clearly explains the reason and replacement.
Model factory compatibility overloads should translate renamed parameters or enum/value types, then delegate to generated public model-factory overloads. Do not construct generated models through internal constructors, internal Properties bags, or private Core helpers just to preserve an old factory signature. First look for a public generated factory overload or public model surface that can receive the same values.
SDK-side fix patterns when decorators cannot help:
| Problem | Fix |
|---|
| Base model/base type changed | Add a custom partial model declaring the intended base model; use [CodeGenType] only when the custom type name differs from the generated/TypeSpec name, then regenerate. Do not use @@hierarchyBuilding. |
| Flattened properties lost on a polymorphic type | Shim properties delegating to the Properties bag |
| Protected constructor missing on a discriminated base | Add a protected constructor in the partial class |
Property lost due to @@alternateType model swap | Add the property in a partial class |
Breaking Changes And New APIs
- Export API:
pwsh eng/scripts/Export-API.ps1 <service>.
- Diff against the previous stable API surface from
ApiCompatVersion / latest stable package tag. Use origin/main only if it matches that baseline.
- Fix each break using the same classify-first loop, then regenerate/export API/re-diff until clean. Never suppress with an ApiCompat baseline except for
WirePathAttribute removal diffs.
For every new public API, classify it before keeping it:
| Category | Action |
|---|
| Real service/API-version addition | Keep and note source API version in PR status |
| Rename of existing shipped API | Prefer @@clientName; otherwise suppress/add minimal shim. Do not keep both names without approval |
| Generator convenience/drift | Investigate operation id, route, resource type, and prior API before keeping |
Compare each new member against the previous GA public API listing or restored ApiCompat assembly, operation id and request path in generated XML docs/source, resource type/parent hierarchy, and the TypeSpec or Swagger API version where the operation/model first appears. If a new API is actually a rename of an existing API, fix the rename instead of documenting it as additive.
Generator Bugs
If generated code is structurally wrong after stale customizations are removed, stop and report a generator bug. Create a minimum TypeSpec repro, document any approved workaround in client.tsp or SDK custom code with an issue link, and pause migration if no safe workaround exists.
Finalize
- Review locally with
mpg-migration-pr-review rules.
- Ensure
CHANGELOG.md has a short migration note and ci.mgmt.yml exists if needed.
- Format TypeSpec before opening the spec PR.
- Push spec changes to a fork and open draft spec PR titled
Add csharp customizations for <Service> migration.
- Update
tsp-location.yaml to point to the fork spec commit, then push SDK changes to a fork and open draft SDK PR titled [Mgmt] <PackageName>: Migrate to TypeSpec.
- Keep the SDK PR description as the status tracker: current phase, blockers, remaining breaks. If pausing before a draft PR exists, create one and use its description as the tracker.
- Before final SDK review, point
tsp-location.yaml to the final spec commit, regenerate once without LocalSpecRepoPath, run pre-commit-checks, and verify build.
Done When
- Build is clean.
- ApiCompat is clean without baselines, except approved
WirePathAttribute removal entries.
- New APIs are triaged.
- Required PR/status/review steps are complete.
tsp-location.yaml points to the final spec commit.