Repository navigation
Description
Description
The C# generator's new date-time normalization should apply to new APIs, but it currently renames properties that were already shipped in a GA package under names produced by the Azure management-plane generator's previous naming convention.
This was found in Azure/azure-sdk-for-net#62490 after updating @typespec/http-client-csharp from 1.0.0-alpha.20260819.14 to 1.0.0-alpha.20260825.9 (source commit 210f84c / #11751).
For example:
Spec/input name: StartTime
Previous MPG output: StartOn
New MTG canonical output: StartsOn
GA contract contains: StartOn
Current output: StartsOn
Desired output: StartOn
The same issue occurs for other historical management-plane names:
| Spec/input name | Previous GA name | New canonical name |
|---|---|---|
StartTime |
StartOn |
StartsOn |
EndTime |
EndOn |
EndsOn |
ExpirationTime |
ExpireOn |
ExpiresOn |
AccessTierChangeTime |
AccessTierChangeOn |
AccessTierChangedOn |
LastSyncTimestamp |
LastSyncTimestamp |
LastSyncOn |
Root cause
PropertyProvider currently checks whether the last contract contains the raw input-derived identifier before applying date-time normalization. Simplified:
var identifierName = inputProperty.Name.ToIdentifierName();
var lastContractProperties = enclosingType.LastContractView?.Properties;
if (lastContractProperties is null ||
!lastContractProperties.Any(p => p.Name == identifierName))
{
identifierName = identifierName.NormalizeCSharpAcronyms(
inputProperty.Type.IsDateTimeInputType());
}For StartTime, this looks for StartTime in the last contract. The GA assembly contains StartOn, because that was the name produced by MPG's former date-time naming visitor. The lookup therefore misses the shipped property and normalizes it to StartsOn.
The existing last-contract check preserves a raw name that was shipped unchanged, but it does not preserve names produced by an earlier generator naming convention.
Expected behavior
The configured GA ApiCompatVersion should determine whether the old name is retained:
| Last contract | Expected output |
|---|---|
Contains StartOn |
Preserve StartOn |
Contains StartsOn |
Preserve StartsOn |
| Contains neither | Use the new canonical StartsOn |
A property that exists only in a newer preview and not in the configured GA baseline may adopt the new canonical name.
Suggested direction
When a date-time input matches a normalization rule, consider both the new canonical name and known previous generated-name candidate(s) when consulting LastContractView. If the GA contract contains one of those candidates, retain the exact GA name; otherwise use the new canonical name.
Conceptually:
var canonicalName = NormalizeDateTimeName(inputName);
var previousGeneratedNames = GetPreviousDateTimeNames(inputName);
var previousProperty = lastContractProperties?.FirstOrDefault(
p => p.Name == inputName ||
p.Name == canonicalName ||
previousGeneratedNames.Contains(p.Name));
var finalName = previousProperty?.Name ?? canonicalName;The preservation should occur while the property provider is initially constructed, before constructor parameters and serialization providers are materialized.
If the historical names are Azure-management-specific, MTG could expose a narrow hook for the generator plugin to contribute previous generated-name candidates while MTG owns the last-contract preservation decision. MPG's active date-time naming visitor can then be removed separately, leaving core normalization as the single naming policy for new APIs.
Impact
The Azure SDK regeneration CI reported approximately:
- 372 distinct removed GA properties
- 478 ApiCompat
CP0002member errors when getters and setters are counted separately - 57 affected GA management-plane projects
Representative confirmed baselines:
Azure.ResourceManager.StorageMover1.4.1 containsStorageMoverScheduleInfo.StartOnandEndOn; it does not containStartsOnorEndsOn.Azure.ResourceManager.AppService1.5.0 containsWebAppBackupSchedule.StartOn; it does not containStartsOn.
This is therefore a shipped binary API compatibility issue, not only API-listing drift.
Regression coverage
Please cover at least:
-
Previously shipped management name
- Input:
StartTime - GA contract:
StartOn - Output:
StartOn
- Input:
-
New property
- Input:
StartTime - No GA property
- Output:
StartsOn
- Input:
-
Already canonical GA name
- Input:
StartTime - GA contract:
StartsOn - Output:
StartsOn
- Input:
-
Historical
EndOn,ExpireOn, compoundChangeOn, and unchanged timestamp forms. -
A flattened or resource-data projected property, such as
DiskEncryptionSetData.LastKeyRotationTimestamp, because its public GA property may be exposed on a different generated provider than the original wire-model property.
The invalid MRW deserialization that can accompany these renames is tracked separately by #11770.
- by copilot
Activity
github-actions commentedon Aug 27, 2026
🎯 Issue Triage
The C# client emitter's new date-time naming normalization (introduced in #11751) breaks binary API compatibility for GA management-plane libraries by renaming previously shipped properties (e.g.,
StartOn→StartsOn), causing ~372 removed GA properties across 57 projects.Category: Bug
Area:
emitter:client:csharp🔍 Analysis
PropertyProvideronly checks the raw input-derived identifier (e.g.,StartTime) againstLastContractView.Properties. Since the GA assembly shipped properties under MPG-generated names (StartOnnotStartTime), the lookup misses them and applies the new canonical normalization (StartsOn), breaking binary compatibility.PropertyProviderin@typespec/http-client-csharp/ MTGApiCompat CP0002errors across 57 GA management-plane projects inazure-sdk-for-net1.0.0-alpha.20260825.9(commit210f84c0, PR Fix C# date-time naming feedback #11751)The fix requires extending the last-contract lookup to also check known previous generated-name candidates (e.g., both
StartOnandStartsOnwhen input isStartTime), and preserving whichever name the GA contract contains.📝 Suggested next steps
PropertyProviderto compute previous generated-name candidates for date-time inputs and include them in theLastContractViewlookup[http-client-csharp] Preserve shipped date-time property names (#11773)
live1206 commentedon Sep 2, 2026
The full Azure SDK management-plane regeneration shows that this is not fully fixed for real Storage integration scenarios, even with the package containing #11773.
Validation environment:
@azure-typespec/http-client-csharp-mgmt:1.0.0-alpha.20260901.1@azure-typespec/http-client-csharp:1.0.0-alpha.20260828.4@typespec/http-client-csharp:1.0.0-alpha.20260828.11Azure.ResourceManager.Storage1.7.0Unsupported cases found:
Projected resource-data property
AccessTierChangeTimeFileShareData.AccessTierChangeOnFileShareData.AccessTierChangedOnReflection against the configured
1.7.0baseline confirms thatAccessTierChangeOnis present andAccessTierChangedOnis absent. The generatedFileShareDataproperty projects fromFileShareProperties.AccessTierChangedOn, so the real projected-provider association is not preserving the GA name despite the projected-provider regression coverage added by [http-client-csharp] Preserve shipped date-time property names #11773.Existing customization combined with a preserved historical name
StorageTableAccessPolicyshipped bothExpireOnandExpiresOn. Existing custom code supplies the hiddenExpiresOnalias and expects the primaryExpireOnproperty to remain generated. Regeneration instead selectedExpiresOnand omittedExpireOn, leaving the customization referring to a missing property and causing compilation failures.A temporary Storage-specific customization workaround was added in Azure/azure-sdk-for-net#62505 so the emitter version bump and full regeneration can proceed. That workaround should be removable once these integration cases are handled by the generator.
The model-factory parameter rename observed in the same package is separate and remains associated with the #11770 back-compat method/serialization path.
- by copilot
live1206 commentedon Sep 2, 2026
Closing this issue for MTG. The MTG regression originally reported here was fixed by #11773: when MTG constructs an input-backed public property, it consults the configured
LastContractViewand preserves the shipped date-time name.The remaining Storage findings do not require another MTG fix:
FileShareData.AccessTierChangeOnis synthesized later by the management-plane flattening path from the already-normalized internalFileShareProperties.AccessTierChangedOnproperty. MTG is given a synthesized property name rather than the original input-backed public-property association, so this needs to be handled by the management generator when it projects the property onto the public provider.StorageTableAccessPolicyshipped bothExpireOnandExpiresOn. MTG can generate one canonical member, while Storage customization supplies the additional compatibility alias/member. This library-specific dual-member arrangement does not warrant general MTG alias inference.The synthetic MTG-only regressions explored for these cases were removed because they would impose flattening- or library-specific behavior on MTG’s general property construction.
- by copilot