Skip to content

fix: handle Scalar types in getLroMetadata for LRO final results#4089

Draft
Copilot wants to merge 10 commits intomainfrom
copilot/fix-getlrometadata-response
Draft

fix: handle Scalar types in getLroMetadata for LRO final results#4089
Copilot wants to merge 10 commits intomainfrom
copilot/fix-getlrometadata-response

Conversation

Copy link
Contributor

Copilot AI commented Mar 23, 2026

getLroMetadata fails to recognize scalar types (e.g., string) as LRO final results. When a status monitor's @lroResult property has a scalar type, the metadata pipeline drops it at multiple points because it only checks for Model and IntrinsicType, causing finalResult, finalEnvelopeResult, and related fields to be incorrect. Additionally, the @finalLocation decorator did not store Scalar types, preventing ARM templates like ArmResourceActionAsync<Widget, void, string> from correctly resolving scalar final results.

typespec-azure-core

  • @finalLocation decorator: Now handles Scalar types so ARM templates correctly store and resolve scalar final results
  • @lroResult validation: Added diagnostic lro-status-monitor-invalid-result-property-type that warns when an @lroResult property has an unsupported type (e.g., enum) — validates that only Model, Scalar, or unknown types are valid
  • filterSuccessType: Accepts and returns Scalar types alongside Model and IntrinsicType
  • resolveOperationLocation: Returns Scalar types from @finalLocation overrides
  • getTargetModelInformation: Added Scalar to the type check alongside Model and UnknownType when reading @lroResult property types; returns Scalar directly from link resolution
  • getStatusMonitorInfo: Skips Scalar status monitor types (a status monitor must be a Model)
  • createStatusMonitorPollingData: Renamed getModelgetModelOrScalar, now returns Scalar types from success properties
  • createLroMetadata: finalResult/finalEnvelopeResult assignment now accepts model.kind === "Scalar"; logicalResult falls back to the polling response model when the result is a Scalar
  • getFinalStateVia: Uses context.finalStep.responseModel directly since responseModel now carries the Scalar type
  • processStatusMonitorLink/processStatusMonitorReference: Assigns success type directly as responseModel for pollingSuccessProperty
  • Interfaces: LogicalOperationStep.responseModel widened to Model | Scalar | UnknownType | VoidType; PollingSuccessProperty.responseModel widened to Model | Scalar; LroMetadata.finalResult and finalEnvelopeResult widened to include Scalar; StatusMonitorInfo.successType and StatusMonitorLinkData.finalModel widened to include Scalar

typespec-client-generator-core

  • SdkLroServiceMetadata.finalResult and finalEnvelopeResult widened to include SdkBuiltInType
  • SdkLroServiceFinalResponse.envelopeResult and result widened to include SdkBuiltInType
  • SdkLogicalOperationStep.responseModel and SdkPollingSuccessProperty.responseModel widened to include SdkBuiltInType
  • updateUsageOrAccessForLroComponent accepts Scalar parameter type
  • pollingSuccessProperty conversion handles scalar responseModel via getClientTypeWithDiagnostics

typespec-autorest

  • getFinalStateSchema creates a proper named schema definition entry for scalar final results with a PascalCase name derived from getOpenAPITypeName (e.g., stringString, Ns1.customResultNs1.CustomResult), producing unique #/definitions/ references from final-state-schema
  • Added tests verifying:
    • final-state-schema references a correctly named scalar schema definition for ARM operations using ArmResourceActionAsync<Widget, void, string>
    • string as finalResult in two operations reuses the same #/definitions/String schema
    • Two custom scalars with different names produce two separate schemas (#/definitions/WidgetId, #/definitions/WidgetTag)
    • Two custom scalars with the same name in different namespaces produce two distinct schema references

Reproducer

model PollingStatus {
  @Azure.Core.lroStatus
  statusValue: "Succeeded" | "Canceled" | "Failed" | "Running";

  @Azure.Core.lroResult
  result?: string;  // Scalar — previously dropped, finalResult incorrectly became "void"
}
Original prompt

This section details on the original issue you should resolve

<issue_title>[Bug]: When response is string, the final result from getLroMetadata is not correct.</issue_title>
<issue_description>### Describe the bug

For the below playground, move async action has a string response as the final result. But we get ArmAcceptedLroResponse as the final result as well as the envelop result from getLroMetadata API.

Image

Reproduction

Playground.

Checklist

  • Follow our Code of Conduct
  • Check that this issue is about the Azure libraries for typespec. For bug in the typespec language or core libraries file it in the TypeSpec repo
  • Check that there isn't already an issue that request the same bug to avoid creating a duplicate.
  • The provided reproduction is a minimal reproducible example of the bug.</issue_description>

<agent_instructions>There are likely two problems here:

  • One of the component calls used by getLroMetadata does not allow scalar types
  • Once this is fixed, typespec-autorest does not emit a schema in the definitions section for a scalar type that is used in the finalResult.

Note that, once this is fixed, you shoudl check if typespec-cplient-generator-core will support a final result of type string in its usage of getLroMetadata.

Once you have analyzed the issue, write tests that will fail due to this issue, ensuring that tests cover all important cases, then, after you have verified test failure, fix the issue and verify that these tests pass and all other tests also pass.

Ensure that any changes in packages are documented in a changeset, that all files are formatted correctly (pnpm format) and that there are no misspellings (pnpm cspell). Finally, ensure that any PR you create is up to date with the main branch.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


💬 Send tasks to Copilot coding agent from Slack and Teams to turn conversations into code. Copilot posts an update in your thread when it's finished.

…RO final results

Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Azure/typespec-azure/sessions/c187047b-516b-4607-9d9e-5a5a4faf12ea
@microsoft-github-policy-service microsoft-github-policy-service bot added the lib:azure-core Issues for @azure-tools/typespec-azure-core library label Mar 23, 2026
… types in LRO metadata consumers

Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Azure/typespec-azure/sessions/c187047b-516b-4607-9d9e-5a5a4faf12ea
@microsoft-github-policy-service microsoft-github-policy-service bot added emitter:autorest Issues for @azure-tools/typespec-autorest emitter lib:tcgc Issues for @azure-tools/typespec-client-generator-core library labels Mar 23, 2026
Copilot AI changed the title [WIP] Fix incorrect response type in getLroMetadata fix: handle Scalar types in getLroMetadata for LRO final results Mar 23, 2026
Copilot AI requested a review from markcowl March 23, 2026 22:48
Copy link
Member

@markcowl markcowl left a comment

Choose a reason for hiding this comment

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

@copilot please pull the latest from main, and make these changes. In some cases, a few properttiers that did not need to have their type changed were changed, please ensure that when these changes are undone, this is also reflected in consuming libraries and emitters, as appropriate.

Please ensure that all tests pass, and that all files are correctly formatted

metadata.finalResult !== "void" &&
metadata.finalResult.name.length > 0
) {
// Scalar types (e.g., string) are emitted inline and don't have a definitions entry
Copy link
Member

Choose a reason for hiding this comment

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

This is not an acceptable solution. In this instance, we need to create a schema in definitions specifically for this case that we can reference from the final-state-schema.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now creating an actual schema definition entry for scalar types using pendingSchemas.getOrAdd(), same approach as for Model types. The schema will be emitted in the definitions section and referenced from final-state-schema. See commit 445591d.

export interface PollingSuccessProperty extends LogicalOperationStep {
kind: "pollingSuccessProperty";
responseModel: Model;
responseModel: Model | Scalar;
Copy link
Member

Choose a reason for hiding this comment

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

This should not change - a polling endpoint must still have a Model response.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reverted PollingSuccessProperty.responseModel back to Model. When the success type is a Scalar, we now use the containing status monitor model as the responseModel, and get the actual scalar type from target.type in getFinalStateVia. See commit 445591d.


/** The model representing important data returned on a success - clients will want to return this model */
logicalResult: Model;
logicalResult: Model | Scalar;
Copy link
Member

Choose a reason for hiding this comment

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

This value also should not change

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reverted logicalResult back to Model. When the actual result type is a Scalar, logicalResult now uses context.pollingStep.responseModel (the polling response model). See commit 445591d.

Copilot AI and others added 2 commits March 23, 2026 23:33
…ate schema for scalar final results

- Revert LogicalOperationStep.responseModel to Model | UnknownType | VoidType
- Revert PollingSuccessProperty.responseModel to Model
- Revert LroMetadata.logicalResult to Model
- Revert StatusMonitorLinkData.finalModel to exclude Scalar
- Fix createLroMetadata to use polling response model for logicalResult when actual result is Scalar
- Fix getFinalStateVia to get scalar type from target.type for pollingSuccessProperty
- Fix processStatusMonitorLink/Reference to use monitor type as responseModel for scalar success types
- Create actual schema definition in typespec-autorest for scalar final results
- Revert SdkLroServiceMetadata.logicalResult, SdkLogicalOperationStep.responseModel, SdkPollingSuccessProperty.responseModel back to SdkModelType

Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Azure/typespec-azure/sessions/261c8e6e-53b6-4d06-aa69-1845ad1b74b1
Copy link
Contributor Author

Copilot AI commented Mar 23, 2026

@copilot please pull the latest from main, and make these changes. In some cases, a few properttiers that did not need to have their type changed were changed, please ensure that when these changes a...

Pulled latest from main and addressed all review feedback in commit 445591d:

  • PollingSuccessProperty.responseModel reverted to Model
  • LroMetadata.logicalResult reverted to Model
  • LogicalOperationStep.responseModel reverted to Model | UnknownType | VoidType
  • StatusMonitorLinkData.finalModel reverted to exclude Scalar
  • Corresponding TCGC types (SdkLogicalOperationStep.responseModel, SdkPollingSuccessProperty.responseModel, SdkLroServiceMetadata.logicalResult) reverted to SdkModelType
  • typespec-autorest now creates proper schema definition entries for scalar final results

All tests pass (276 azure-core, 1218 TCGC, 455 autorest, 263 ARM), formatting and spell check clean.

Copilot AI requested a review from markcowl March 23, 2026 23:53
Copy link
Member

@markcowl markcowl left a comment

Choose a reason for hiding this comment

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

Make the changes and add an additional test to typespec-autorest that generates long-running-operation-options.final-state-schema and verifies that a correct, named references is created for a scalar final schema.

As always, pull the latest from main, ensure that all tests pass and that formatting is correct for all changed documents.

) {
// Scalar types (e.g., string) need a schema definition entry
if (metadata.finalResult.kind === "Scalar") {
const pending = pendingSchemas.getOrAdd(metadata.finalResult, Visibility.Read, () => ({
Copy link
Member

Choose a reason for hiding this comment

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

Here you need to create a unique name for the schema, based on the scalar name, in Pascal case

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in bb77a6d. The scalar schema now gets a PascalCase name (e.g., stringString) via getSchemaNameOverride, producing #/definitions/String with { type: "string" } in the definitions section. Added an autorest test that verifies the named reference for an ARM action returning string.

@@ -271,10 +271,10 @@
/** The model representing important data returned on a success - clients will want to return this model. If undefined,
* then clients would want to return nothing.
Copy link
Member

Choose a reason for hiding this comment

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

ResponseModel should also allow a scalar type

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in bb77a6d. PollingSuccessProperty.responseModel is now Model | Scalar, and LogicalOperationStep.responseModel is Model | Scalar | UnknownType | VoidType. The success type is assigned directly without falling back to the monitor type.

resOp?.operation === undefined ||
resOp.operation !== "createOrReplace")
) {
model = context.finalStep.responseModel;
Copy link
Member

Choose a reason for hiding this comment

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

context.FinalStep.responseModel should also allow a scalar.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in bb77a6d. Simplified to just model = context.finalStep.responseModel since responseModel now carries the Scalar directly. Also fixed the @finalLocation decorator to store Scalar types and filterSuccessType/resolveOperationLocation to propagate them — this was needed for ARM templates like ArmResourceActionAsync<Widget, void, string> to work correctly.

…ar final results, fix @finalLocation for scalars

- LogicalOperationStep.responseModel and PollingSuccessProperty.responseModel now accept Scalar
- getFinalStateVia simplified: model = context.finalStep.responseModel directly
- processStatusMonitorLink/Reference: assign successType directly as responseModel
- @finalLocation decorator: handle Scalar types so ARM templates correctly store scalar final results
- filterSuccessType: accept and return Scalar types
- resolveOperationLocation: return Scalar types from @finalLocation overrides
- getStatusMonitorInfo: skip Scalar status monitor types (must be Model)
- getTargetModelInformation: return Scalar from link resolution
- StatusMonitorLinkData.finalModel: widened to include Scalar
- typespec-autorest: create PascalCase named schema definition for scalar final results
- typespec-autorest: add test for scalar final-state-schema with ARM operations
- TCGC: SdkLogicalOperationStep.responseModel and SdkPollingSuccessProperty.responseModel accept SdkBuiltInType
- TCGC: handle Scalar responseModel in pollingSuccessProperty conversion

Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Azure/typespec-azure/sessions/c6ec5b71-c1a0-4c8d-9655-49f32e7f5288
Copy link
Contributor Author

Copilot AI commented Mar 24, 2026

Make the changes and add an additional test to typespec-autorest that generates long-running-operation-options.final-state-schema and verifies that a correct, named references is created for a scalar ...

Done in bb77a6d. Key changes:

  1. responseModel now allows Scalar in LogicalOperationStep and PollingSuccessProperty
  2. Fixed @finalLocation decorator to handle Scalar types — this was the root cause for ARM templates not resolving scalar final results
  3. getFinalStateVia simplified to use responseModel directly
  4. Added autorest test "Creates a named final-state-schema definition for scalar final results" using ArmResourceActionAsync<Widget, void, string> — verifies "final-state-schema": "#/definitions/String" and definitions["String"] === { type: "string" }
  5. Scalar schemas get PascalCase names via getSchemaNameOverride
  6. TCGC types updated to match

All tests pass (276 azure-core, 456 autorest, 1218 TCGC, 263 ARM), formatting and spell check clean.

Copilot AI requested a review from markcowl March 24, 2026 02:36
Copy link
Member

@markcowl markcowl left a comment

Choose a reason for hiding this comment

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

Fix the suggested changes, and also add a test to ensure that if an invalid final result type (for example, an enum) is attempted, an appropriate diagnostic is emitted.

As always, please pull the latest from main, ensure that all tests are passing and ensure that all files are correctly formatted (pnpm format)

});
});

it("Creates a named final-state-schema definition for scalar final results", async () => {
Copy link
Member

Choose a reason for hiding this comment

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

Please enhance this test or add additional tests to ensure that the generated schema name for a unique schema is unique when the schema is unique. Some things that might create a challenge:

  • two custom scalars with the same name but different schemas in different namespaces as the final result schema for two operations should result in 2 schemas)
  • two custom scalars based on string with different names as the final result schema (should result in two schemas)
  • string as the finalResult schema in two operations uses the same schema.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 9753973. Added all three test cases:

  1. "Reuses the same schema when string is the finalResult in two operations" — two ArmResourceActionAsync<Widget, void, string> operations both reference #/definitions/String with a single schema entry
  2. "Creates separate schemas for two custom scalars with different names"widgetId extends string and widgetTag extends string produce #/definitions/WidgetId and #/definitions/WidgetTag respectively
  3. "Creates separate schemas for custom scalars with the same name in different namespaces"Ns1.customResult extends string and Ns2.customResult extends int32 produce two distinct schema references

Also fixed getSchemaNameOverride to use the getOpenAPITypeName-based name with PascalCase (handles namespace-qualified names like Ns1.CustomResult) instead of just the scalar's simple name.

…schema naming tests

- Add diagnostic 'lro-status-monitor-invalid-result-property-type' for @lroResult
  properties with unsupported types (e.g., enums) — validates that only Model,
  Scalar, or 'unknown' types are valid
- Add azure-core test: 'emits diagnostic for invalid @lroResult property type (enum)'
- Fix scalar schema naming to use getOpenAPITypeName-based name with PascalCase
  instead of just the scalar's simple name (supports namespace-qualified names)
- Add autorest test: 'Reuses the same schema when string is the finalResult in two operations'
- Add autorest test: 'Creates separate schemas for two custom scalars with different names'
- Add autorest test: 'Creates separate schemas for custom scalars with the same name in different namespaces'

All tests pass: 277 azure-core, 459 autorest, 1218 TCGC, 263 ARM

Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Azure/typespec-azure/sessions/c33a9ff2-ed21-4590-9516-347e7a0a9875
Copy link
Contributor Author

Copilot AI commented Mar 24, 2026

Fix the suggested changes, and also add a test to ensure that if an invalid final result type (for example, an enum) is attempted, an appropriate diagnostic is emitted.

As always, please pull the lat...

Done in 9753973. Added diagnostic lro-status-monitor-invalid-result-property-type that warns when an @lroResult property has an unsupported type like an enum. Also added a test in azure-core that verifies the diagnostic is emitted for enum types, plus the three autorest scalar schema naming tests. All tests pass (277 azure-core, 459 autorest, 1218 TCGC, 263 ARM).

Copilot AI requested a review from markcowl March 24, 2026 03:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

emitter:autorest Issues for @azure-tools/typespec-autorest emitter lib:azure-core Issues for @azure-tools/typespec-azure-core library lib:tcgc Issues for @azure-tools/typespec-client-generator-core library

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: When response is string, the final result from getLroMetadata is not correct.

2 participants