Anchor ARM resource identity to read and create operations - #4851
Anchor ARM resource identity to read and create operations#4851Dapeng Zhang (ArcturusZhang) wants to merge 24 commits into
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
commit: |
|
All changed packages have been documented.
Show changes
|
|
You can try these changes here
|
⚡ Benchmark Results
Full details – comparing
|
| Metric | Baseline | Current | Change |
|---|---|---|---|
| total | 🔴 1.01s | 🔴 1.05s | +3.4% |
| loader | 🟡 307.6ms | 🟡 318.5ms | +3.5% |
| resolver | 🟢 40.5ms | 🟢 41.2ms | +1.8% |
| checker | 🟡 365.7ms | 🟡 379.9ms | +3.9% |
| validation | 🟢 85.1ms | 🟢 86.9ms | +2.1% |
| ↳ validation/@azure-tools/typespec-azure-core | 🟡 12.6ms | 🟡 13.3ms | +5.3% |
| ↳ validation/@typespec/http | 🟡 15.5ms | 🟡 16.3ms | +4.7% |
| ↳ validation/@typespec/rest | 🟢 1.6ms | 🟢 1.7ms | +2.7% |
| ↳ validation/@typespec/versioning | 🔴 51.4ms | 🔴 51.6ms | +0.4% |
| ↳ validation/compiler | 🟢 3.9ms | 🟢 3.9ms | +1.7% |
| linter | 🟡 215.3ms | 🟡 222.6ms | +3.4% |
| ↳ linter/@azure-tools/typespec-azure-core/auth-required | 🟢 0.1ms | 🟢 0.1ms | +7.4% |
| ↳ linter/@azure-tools/typespec-azure-core/bad-record-type | 🟢 0.6ms | 🟢 0.6ms | +1.7% |
| ↳ linter/@azure-tools/typespec-azure-core/byos | 🟢 8.4ms | 🟢 8.6ms | +2.8% |
| ↳ linter/@azure-tools/typespec-azure-core/casing-style | 🟢 1.5ms | 🟢 1.5ms | +0.9% |
| ↳ linter/@azure-tools/typespec-azure-core/composition-over-inheritance | 🟢 0.2ms | 🟢 0.2ms | +1.6% |
| ↳ linter/@azure-tools/typespec-azure-core/documentation-required | 🟢 1.9ms | 🟢 2.0ms | +2.2% |
| ↳ linter/@azure-tools/typespec-azure-core/friendly-name | 🟢 1.4ms | 🟢 1.4ms | +2.0% |
| ↳ linter/@azure-tools/typespec-azure-core/key-visibility-required | 🟢 0.4ms | 🟢 0.4ms | +3.2% |
| ↳ linter/@azure-tools/typespec-azure-core/known-encoding | 🟢 0.5ms | 🟢 0.5ms | +3.0% |
| ↳ linter/@azure-tools/typespec-azure-core/long-running-polling-operation-required | 🟢 0.8ms | 🟢 0.8ms | +3.4% |
| ↳ linter/@azure-tools/typespec-azure-core/no-case-mismatch | 🟢 0.7ms | 🟢 0.7ms | +2.6% |
| ↳ linter/@azure-tools/typespec-azure-core/no-closed-literal-union | 🟢 1.0ms | 🟢 1.0ms | +0.1% |
| ↳ linter/@azure-tools/typespec-azure-core/no-enum | 🟢 0.2ms | 🟢 0.3ms | +2.5% |
| ↳ linter/@azure-tools/typespec-azure-core/no-error-status-codes | 🟢 0.3ms | 🟢 0.3ms | +0.5% |
| ↳ linter/@azure-tools/typespec-azure-core/no-explicit-routes-resource-ops | 🟢 0.2ms | 🟢 0.2ms | +1.5% |
| ↳ linter/@azure-tools/typespec-azure-core/no-format | 🟢 0.8ms | 🟢 0.8ms | +2.1% |
| ↳ linter/@azure-tools/typespec-azure-core/no-generic-numeric | 🟢 0.8ms | 🟢 0.9ms | +2.6% |
| ↳ linter/@azure-tools/typespec-azure-core/no-header-explode | 🔴 26.7ms | 🔴 28.2ms | +5.3% 🔴 |
| ↳ linter/@azure-tools/typespec-azure-core/no-legacy-usage | 🟢 2.1ms | 🟢 2.1ms | +1.3% |
| ↳ linter/@azure-tools/typespec-azure-core/no-multiple-discriminator | 🟢 0.2ms | 🟢 0.2ms | +5.9% |
| ↳ linter/@azure-tools/typespec-azure-core/no-nullable | 🟢 0.4ms | 🟢 0.4ms | +4.3% |
| ↳ linter/@azure-tools/typespec-azure-core/no-offsetdatetime | 🟢 2.0ms | 🟢 2.0ms | +1.8% |
| ↳ linter/@azure-tools/typespec-azure-core/no-openapi | 🟢 2.3ms | 🟢 2.4ms | +1.6% |
| ↳ linter/@azure-tools/typespec-azure-core/no-private-usage | 🟢 3.2ms | 🟢 3.2ms | +1.0% |
| ↳ linter/@azure-tools/typespec-azure-core/no-query-explode | 🔴 27.8ms | 🔴 29.5ms | +6.0% 🔴 |
| ↳ linter/@azure-tools/typespec-azure-core/no-response-body | 🔴 33.4ms | 🔴 35.0ms | +4.7% |
| ↳ linter/@azure-tools/typespec-azure-core/no-rest-library-interfaces | 🟢 0.1ms | 🟢 0.1ms | +1.6% |
| ↳ linter/@azure-tools/typespec-azure-core/no-route-parameter-name-mismatch | 🟢 7.7ms | 🟢 8.1ms | +5.3% |
| ↳ linter/@azure-tools/typespec-azure-core/no-rpc-path-params | 🟢 0.4ms | 🟢 0.4ms | +2.6% |
| ↳ linter/@azure-tools/typespec-azure-core/no-string-discriminator | 🟢 0.1ms | 🟢 0.2ms | +5.7% |
| ↳ linter/@azure-tools/typespec-azure-core/no-unknown | 🟢 0.4ms | 🟢 0.4ms | +3.2% |
| ↳ linter/@azure-tools/typespec-azure-core/no-unnamed-union | 🟢 0.8ms | 🟢 0.8ms | +1.5% |
| ↳ linter/@azure-tools/typespec-azure-core/operation-missing-api-version | 🟢 0.4ms | 🟢 0.4ms | -0.4% |
| ↳ linter/@azure-tools/typespec-azure-core/request-body-problem | 🟢 0.5ms | 🟢 0.5ms | +0.8% |
| ↳ linter/@azure-tools/typespec-azure-core/require-versioned | 🟢 0.1ms | 🟢 0.1ms | +2.6% |
| ↳ linter/@azure-tools/typespec-azure-core/response-schema-problem | 🔴 32.6ms | 🔴 33.6ms | +2.9% |
| ↳ linter/@azure-tools/typespec-azure-core/rpc-operation-request-body | 🟢 0.7ms | 🟢 0.7ms | +4.9% |
| ↳ linter/@azure-tools/typespec-azure-core/spread-discriminated-model | 🟢 0.5ms | 🟢 0.5ms | +0.7% |
| ↳ linter/@azure-tools/typespec-azure-core/use-standard-names | 🟢 7.6ms | 🟢 8.0ms | +5.3% |
| ↳ linter/@azure-tools/typespec-azure-core/use-standard-operations | 🟢 0.3ms | 🟢 0.3ms | +1.1% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-agent-base-type-child-resources | 🔴 21.0ms | 🔴 22.0ms | +4.8% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-agent-base-type-lifecycle-operations | 🟢 0.3ms | 🟢 0.3ms | +9.2% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-common-types-version | 🟡 12.1ms | 🟡 12.5ms | +3.1% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-custom-resource-no-key | 🟢 0.2ms | 🟢 0.2ms | -0.4% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-custom-resource-usage-discourage | 🟢 0.1ms | 🟢 0.1ms | +1.8% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-delete-operation-response-codes | 🟢 5.0ms | 🟢 4.6ms | -6.7% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-feature-file-usage-discourage | 🟢 0.1ms | 🟢 0.1ms | -4.5% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-no-path-casing-conflicts | 🟡 17.1ms | 🟡 17.0ms | -0.5% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-no-record | 🟢 0.6ms | 🟢 0.6ms | -1.2% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-post-operation-response-codes | 🟢 1.4ms | 🟢 1.4ms | +2.7% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-put-operation-response-codes | 🟢 0.2ms | 🟢 0.2ms | -4.2% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-action-no-segment | 🟢 0.4ms | 🟢 0.4ms | -3.1% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-duplicate-property | 🟢 0.4ms | 🟢 0.4ms | +1.5% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-interface-requires-decorator | 🟢 0.1ms | 🟢 0.1ms | -0.8% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-invalid-action-verb | 🟢 0.2ms | 🟢 0.2ms | -4.4% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-invalid-envelope-property | 🟢 0.3ms | 🟢 0.3ms | +4.8% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-invalid-version-format | 🟢 0.2ms | 🟢 0.2ms | +0.7% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-key-invalid-chars | 🟢 0.5ms | 🟢 0.5ms | +1.4% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-name-pattern | 🟢 0.1ms | 🟢 0.1ms | +3.7% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-operation | 🟢 0.5ms | 🟢 0.6ms | +2.1% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-operation-response | 🟢 9.0ms | 🟢 9.1ms | +1.0% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-patch | 🟢 0.8ms | 🟢 0.8ms | -4.4% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-path-segment-invalid-chars | 🟢 0.4ms | 🟢 0.4ms | +0.3% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-provisioning-state | 🟢 0.4ms | 🟢 0.4ms | +2.7% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/beyond-nesting-levels | 🟢 0.2ms | 🟢 0.2ms | +2.8% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/empty-updateable-properties | 🟢 0.4ms | 🟢 0.4ms | +0.8% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/improper-subscription-list-operation | 🟢 0.1ms | 🟢 0.1ms | +13.0% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/lro-location-header | 🟡 19.7ms | 🔴 20.6ms | +5.0% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/missing-operations-endpoint | 🟢 0.1ms | 🟢 0.1ms | +1.9% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/missing-x-ms-identifiers | 🟢 1.0ms | 🟢 1.0ms | -1.9% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/no-empty-model | 🟢 0.3ms | 🟢 0.3ms | +2.4% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/no-override-props | 🟢 0.3ms | 🟢 0.3ms | -1.3% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/no-reserved-resource-property | 🟢 0.2ms | 🟢 0.2ms | -0.1% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/no-resource-delete-operation | 🟢 0.5ms | 🟢 0.5ms | -0.9% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/no-response-body | 🔴 29.8ms | 🔴 30.7ms | +3.1% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/patch-envelope | 🟢 0.4ms | 🟢 0.4ms | +1.2% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/resource-name | 🟢 0.4ms | 🟢 0.4ms | -1.3% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/secret-prop | 🟢 5.4ms | 🟢 5.1ms | -6.0% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/unsupported-type | 🟢 0.7ms | 🟢 0.7ms | -2.8% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/version-progression | 🟢 0.3ms | 🟢 0.3ms | -2.1% |
| ↳ linter/@azure-tools/typespec-client-generator-core/property-name-conflict | 🟢 2.3ms | 🟢 2.3ms | +0.8% |
| ↳ linter/@azure-tools/typespec-client-generator-core/require-client-suffix | 🟢 1.7ms | 🟢 1.7ms | +1.4% |
| emit | 🔴 6.04s | 🔴 6.28s | +3.9% |
| ↳ emit/Azure-Tools | 🟢 0.0ms | 🟢 0.0ms | +0.0% |
| ↳ emit/@azure-tools/typespec-autorest | 🟢 125.4ms | 🟢 129.9ms | +3.6% |
| ↳ emit/@azure-tools/typespec-python | 🔴 2.25s | 🔴 2.35s | +4.1% |
| ↳ emit/typespec | 🟢 0.0ms | 🟢 0.0ms | +0.0% |
| ↳ emit/@typespec/http-client-js | 🔴 571.9ms | 🔴 587.6ms | +2.7% |
| ↳ emit/@typespec/openapi3 | 🟢 107.9ms | 🟢 112.4ms | +4.1% |
| ↳ emit/@typespec/openapi3/compute | 🟢 93.8ms | 🟢 97.7ms | +4.2% |
| ↳ emit/@typespec/openapi3/write | 🟢 13.9ms | 🟢 14.4ms | +3.7% |
Averaged across 3 specs (azure-arm-resource-manager, azure-core-dataplane, azure-full).
Threshold: changes > ±5% are highlighted.
🟢 Fast · 🟡 Moderate (stages >200ms, rules >10ms) · 🔴 Slow (stages >400ms, rules >20ms)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…resource-identity
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…resource-identity
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…resource-identity
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10e564a2-4523-48ae-821e-a1c3e878765f
| { operationGroup: "Employees", name: "listBySubscription", kind: "list" }, | ||
| { operationGroup: "Employees", name: "listByResourceGroup", kind: "list" }, | ||
| ], | ||
| lists: [{ operationGroup: "Employees", name: "listByResourceGroup", kind: "list" }], |
There was a problem hiding this comment.
not sure if this is an issue.
There was a problem hiding this comment.
seems like we need both
There was a problem hiding this comment.
yeah I am thinking the same thing right now.
I will see where it goes wrong and get it back.
There was a problem hiding this comment.
Agreed. The strict prefix rule was too narrow for list operations at wider scopes. I opened #5080 to track the complete matching-rule design, and added an issue-linked TODO in the matcher.
In this PR I am applying the focused case we know is valid: subscription-list operations can attach to resource-group-scoped resources when the concrete provider/type path is the same. That brings this test back to expecting both listBySubscription and listByResourceGroup, without letting list operations create resource identities.
🤖 arcturus-copilot
There was a problem hiding this comment.
Follow-up: refined in 9ef00922 to avoid a scope-specific exception. The matcher now prefers direct list-path prefix matches first; if none exists, it falls back to matching by concrete provider/type path. The resource side reuses parseArmResourceInstancePath, while list/collection paths use a small collection-path parser.
🤖 arcturus-copilot
There was a problem hiding this comment.
Follow-up in 553cf2bc: getListMatchDistance now trims the ARM scope prefix before comparing path lengths, so prefix-match ranking is based on the resource-type tail rather than subscription/resource-group/tenant scope length.
🤖 arcturus-copilot
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10e564a2-4523-48ae-821e-a1c3e878765f
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10e564a2-4523-48ae-821e-a1c3e878765f
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10e564a2-4523-48ae-821e-a1c3e878765f
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10e564a2-4523-48ae-821e-a1c3e878765f
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10e564a2-4523-48ae-821e-a1c3e878765f
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10e564a2-4523-48ae-821e-a1c3e878765f
…resource-identity
…resource-identity
Mark Cowlishaw (markcowl)
left a comment
There was a problem hiding this comment.
I need a little more time to go over the test changes, but will add in the external integration ci job as well.
| @@ -0,0 +1,92 @@ | |||
| # Linter candidates for `resolveArmResources` | |||
There was a problem hiding this comment.
I think we should probably make this into an issue Most of this validation is being added as part of the new linters for typespec suppression, but it would be nice to have this to track
There was a problem hiding this comment.
sure I could create an issue to track these candidates. do you prefer one per one candidate or we have a overall issue to contain all of these?
|
|
||
| `resolveArmResources(program)` should produce a complete, deterministic, version-aware description of the ARM provider shape represented by the TypeSpec program. | ||
|
|
||
| The API should make the version dimension explicit. For an unversioned TypeSpec program, it should return one provider result. For a versioned TypeSpec program, it should return provider results for each declared service version so downstream emitters and analyzers can reason about the resource shape that exists in each API version. |
There was a problem hiding this comment.
I am skeptical of this requiremntt for two reasons:
- It assumes that every consumer wants a version-projection for each version in the program
- it assumes that every consumer does not want a version-independent canonical view of the resources (this was the original reason for this API, in fact).
Instead, I would suggest that either a versioned program is passed in (for which helpers could be provided) or there is a mechanism that allows projecting the result.
There was a problem hiding this comment.
yeah I agree. but per my investigation in this issue, currently passing in a version mutated program does not really change the result, just FYI.
There was a problem hiding this comment.
Addressed in caf191e by removing the per-version result requirement from the proposal and documenting that
esolveArmResources uses the view represented by the input program; version-specific analysis should be layered on top with version-projected programs/helpers.
🤖 arcturus-copilot
| | "Subscription" | ||
| | "ResourceGroup" | ||
| | "ManagementGroup" | ||
| | "Scope" |
There was a problem hiding this comment.
We should add "ServiceGroup"
There was a problem hiding this comment.
Addressed in caf191e: added ServiceGroup to the proposal's ArmResourceScope union and added code-side ArmResourceScope typing including ServiceGroup.
🤖 arcturus-copilot
|
|
||
| ## High-level flow | ||
|
|
||
| 1. Find the ARM provider namespace with `resolveProviderNamespace(program)`. |
There was a problem hiding this comment.
versioning doesn't seem to be mentioned here, how will we handle versions?
There was a problem hiding this comment.
I think our design is:
this API just uses the program which has been (or not) mutated by versioning. therefore this API does not do anything about versioning explicitly.
is this understanding correct?
if so, we do not need to mention versioning here at all.
There was a problem hiding this comment.
Addressed in caf191e. The high-level flow remains focused on resource resolution, and versioning is now described earlier as an input-program/projection concern rather than as a resolver step.
🤖 arcturus-copilot
| } | ||
| for (let i = 0; i < resourceSegments.length; i += 2) { | ||
| const typeSegment = resourceSegments[i]; | ||
| const nameSegment = resourceSegments[i + 1]; |
There was a problem hiding this comment.
what if the name segment isn't a variable segment?
There was a problem hiding this comment.
then it should be a singleton resource. the handling of singleton resource is not covered (or not fully covered) in this PR yet.
there are two kinds of singleton resources:
- the resource's name is a constant
- the resource's name is not a constant but an enum - we should expect fixed enum.
last time we did a change in the resource result schema to let this api to support both - the singletonresourcename can take a single value or an array of names.
FYI I have to stop the modification in some point to prevent this PR to contain every change we would like, because it might make the PR very hard to review. What do you think?
I was trying to split the changes we would like to have when making the change, but those changes kind of are linked with each other, therefore the PR's size just grew and grew
| { operationGroup: "Employees", name: "listBySubscription", kind: "list" }, | ||
| { operationGroup: "Employees", name: "listByResourceGroup", kind: "list" }, | ||
| ], | ||
| lists: [{ operationGroup: "Employees", name: "listByResourceGroup", kind: "list" }], |
There was a problem hiding this comment.
seems like we need both
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10e564a2-4523-48ae-821e-a1c3e878765f
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10e564a2-4523-48ae-821e-a1c3e878765f
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10e564a2-4523-48ae-821e-a1c3e878765f
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10e564a2-4523-48ae-821e-a1c3e878765f
…resource-identity
…resource-identity Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 10e564a2-4523-48ae-821e-a1c3e878765f
📦 Package size report1 package changed size, +841 B (+0.0%) packed overall.
12 package(s) with no notable change
Packed = gzipped |
Summary
resolveArmResourcesdesign proposal and related linter candidatesDesigned resource detection flow
Collect candidate resource model details from
listArmResources(program). These are registered TypeSpec resource models, not yet concrete ARM resources.Find identity operations for each candidate model:
readandcreateOrUpdate.list,action,update,delete, andcheckExistence.readandcreateOrUpdate.Use identity operation paths as candidate resource instance paths. The resolver should trust the registered ARM operation kind; HTTP verb/response-shape correctness should be enforced by linters.
read/createOrUpdatepaths can establish resource identity.Parse and validate each candidate path as an ARM resource instance path.
getResourcePathElementswas removed and replaced with strictparseArmResourceInstancePath, which requires a literal/providers/{namespace}segment except for known providerless exceptions such as tenant, subscription, resource group, and explicit generic resource-id parameter paths.Group candidates by normalized resource identity: provider namespace, resource type segments, literal path segments case-insensitively, and variable path segments by position while ignoring variable names.
Create one concrete
ResolvedResourceper identity group.read+createOrUpdateon the same normalized path become one resource; different valid paths become separate resources;createOrUpdatealone can create a resource;list/actionalone cannot.listandactionoperations could create aResolvedResource.Derive stable resource facts from the canonical instance path:
resourceInstancePath,resourceType, provider namespace, scope, resource name parameter or singleton key, and parent candidate information.resourceInstancePath/resourceTypenow come from strict identity paths. Path-based singleton cleanup remains future work.Append remaining operations only to already-detected resources: lifecycle operations by matching normalized instance path, list operations by detected-resource-ID prefix, and action operations under the detected resource instance path.
Leave unmatched operations for provider/non-resource handling. They should not invent new resources.
providerOperations.Resolve parent and scope relationships after the full resource set is known, using normalized path comparison. Missing parents should be skipped rather than synthesized as empty resources.
ExternalResourcefor ambiguous external scopes.ResolvedResourceentries, while ambiguous/generic external scopes use"ExternalResource"as an unresolved-external sentinel. We should decide whether to keep that sentinel, replace it withundefinedor a structured unresolved scope, and remove the unusedparent/scopefields from the internalResolvedResourceOperationsstaging type if they remain unused.Validation
pnpm --filter @azure-tools/typespec-azure-resource-manager exec tsc -p tsconfig.build.json --noEmitpnpm --filter @azure-tools/typespec-azure-resource-manager exec vitest run test/resource-resolution.test.ts --testNamePattern "parseArmResourceInstancePath"pnpm --filter @azure-tools/typespec-azure-resource-manager exec vitest run test/resource-resolution.test.tsis currently blocked by baselineexperimental-featurewarnings from compiler/agent TypeSpec files