Add arm-resource-required-operations linter rule#4380
Conversation
Agent-Logs-Url: https://github.com/Azure/typespec-azure/sessions/a7d5b431-f91c-48a7-8277-5f09cc7a4018 Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
…rce-required-operations-rule Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
Agent-Logs-Url: https://github.com/Azure/typespec-azure/sessions/ebe47644-6824-4c23-869d-b9d738f7d9a0 Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
markcowl
left a comment
There was a problem hiding this comment.
Please pull the latest from main, make the changes requested, ensure that all tests are passing, all files are formatted correctly, that samples rebuild correctly and that the PR diff contains only changes associated with this pr
| return { | ||
| model: (model: Model) => { | ||
| if (isInternalTypeSpec(context.program, model)) return; | ||
| const armResource = getArmResources(context.program).find((r) => r.typespecType === model); |
There was a problem hiding this comment.
getArmResources is an expensive operation. We shgoulr un this once for the prgram and filter the results for each resource model.
Also, we should use resolveArmResources instead of getArmResources, as it handles both standard and legacy resource operations.
There was a problem hiding this comment.
Switched to resolveArmResources, called once per program and cached in a Map<Model, ResolvedResource[]> closure that's reused for every model visit (5b963c9).
| ): RequiredOperation[] { | ||
| const kind = armResource.kind; | ||
| if (kind !== "Tracked" && kind !== "Proxy" && kind !== "Extension") { | ||
| return []; |
There was a problem hiding this comment.
All resources that are not singletons should have read, list-by-parent
There was a problem hiding this comment.
Unified the requirements so every non-singleton resource needs read + createOrUpdate + delete + list-by-parent. Tracked resources additionally need list-by-subscription, and a list-by-resource-group operation satisfies the list-by-parent requirement at resource-group scope (5b963c9).
…rce-required-operations-rule Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
…rg test Agent-Logs-Url: https://github.com/Azure/typespec-azure/sessions/6338dc59-3d54-4c13-8b8c-cc154adc2ff5 Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
Merged latest |
markcowl
left a comment
There was a problem hiding this comment.
Pull the latest from main, make the requested changes, validate that all tests pass and all files are formatted correctly, that your branch is up to date with main and that all changes in the PR diff are part of this PR.
| }, | ||
| create(context) { | ||
| // Resolve resources once for the program and reuse for every model visit. | ||
| let resourcesByModel: Map<Model, ResolvedResource[]> | undefined; |
There was a problem hiding this comment.
This is the wrong way to use the result of resolveARmResources. Each ResolvedResource is a resource with its operations and you should use this rather than making other calls to to assemble resource definitions.
There was a problem hiding this comment.
The 'model' does not define a resource in some scenarios - some scenarios use a single model to describe multiple resources, so using the returned ResolvedResource array from resolveArmResources uniquely defines each resource, and you should be able to get the resource kind from this as well.
There was a problem hiding this comment.
Restructured to iterate ResolvedResource entries from resolveArmResources via a root: listener and read kind, singleton, and operations directly off each entry — no auxiliary lookups for those. Because a single declared resource can produce multiple ResolvedResource entries (ARM operation templates emit extra paths for createOrUpdate when a nested resource exists), entries are grouped by model identity and one diagnostic is reported per logical resource using the union of their operations (38144d7).
There was a problem hiding this comment.
Switched to iterating each ResolvedResource returned by resolveArmResources (root listener). Kind/singleton are now read from the ResolvedResource itself. Multiple entries that the resolver produces for the same model (path-doubling for createOrUpdate under nested resources) are grouped per model and merged into a single logical-resource check, using the canonical (shortest resourceType.types) entry for kind/singleton/name (38144d7).
Agent-Logs-Url: https://github.com/Azure/typespec-azure/sessions/4c07b4b6-5c3e-40c7-b572-022659ae1a4e Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
Branch is up to date with |
|
@copilot there is a merge conflict. Pull the latest from main, merge your changes, validate that all tests pass and all files are formatted correctly, that your branch is up to date with main and that all changes in the PR diff are part of this PR. |
…rce-required-operations-rule Agent-Logs-Url: https://github.com/Azure/typespec-azure/sessions/f98c08fb-bae8-4462-a33e-47862ea7bfed Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
|
|
||
| Every ARM resource that provides a create operation must also provide a delete operation. | ||
|
|
||
| > **See also:** This rule has been superseded by |
There was a problem hiding this comment.
Let's keep this rule around and not check for this condition in the new rule
There was a problem hiding this comment.
Reverted no-resource-delete-operation.md to its original content and removed the "createOrUpdate must also define delete" check from arm-resource-required-operations — that condition is now solely enforced by no-resource-delete-operation. The proxy-with-PUT-without-delete test is now toBeValid for this rule (2fc6eb9).
…rce-required-operations-rule Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
…lete-operation Agent-Logs-Url: https://github.com/Azure/typespec-azure/sessions/f6f7a756-775c-47a1-bffc-8970e27398f3 Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
Merged latest |
markcowl
left a comment
There was a problem hiding this comment.
Pull the latest from main, create tests validating the suggested changes, make the changes, ensure all tests pass and all files are formatted correctly. Ensure that only changes associated with this PR are in the PR diff.
| - "@azure-tools/typespec-azure-rulesets" | ||
| --- | ||
|
|
||
| Add new `arm-resource-required-operations` linting rule that ensures every ARM resource declares the complete set of required lifecycle and list operations (singleton-aware; supersedes `no-resource-delete-operation`). |
There was a problem hiding this comment.
| Add new `arm-resource-required-operations` linting rule that ensures every ARM resource declares the complete set of required lifecycle and list operations (singleton-aware; supersedes `no-resource-delete-operation`). | |
| Add new `arm-resource-required-operations` linting rule that ensures every ARM resource declares the complete set of required lifecycle and list operations (singleton-aware; complements`no-resource-delete-operation`). |
| severity: "warning", | ||
| url: "https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/arm-resource-required-operations", | ||
| description: | ||
| "ARM resources must define their required operations: tracked resources need the full lifecycle and list set, other resources need a read, and any resource defining createOrUpdate must also define delete.", |
There was a problem hiding this comment.
| "ARM resources must define their required operations: tracked resources need the full lifecycle and list set, other resources need a read, and any resource defining createOrUpdate must also define delete.", | |
| "ARM resources must define their required operations: tracked resources need the full lifecycle and list set, other resources need a read operation.", |
| // operations) and read kind / singleton / operations directly from the | ||
| // returned ResolvedResource entries. | ||
| // | ||
| // A single declared resource can produce multiple ResolvedResource |
There was a problem hiding this comment.
A single resource model ,may represent multiple actual resources. We should not group resources this way, and should ensure that the diagnostic locationf or this rule is the interface that contains the resource operations and not the resource itself.
There was a problem hiding this comment.
Reworked: the rule now iterates each ResolvedResource (no grouping by Model) and targets the @armResourceOperations interface containing the resource's operations. Pure resolver path-doubling artifacts (where a model appears as its own ancestor) are detected and their operations merged into the canonical entry's present-op set so they don't produce spurious duplicate diagnostics (b6ee6db).
…rce-required-operations-rule Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
…date description and changelog Agent-Logs-Url: https://github.com/Azure/typespec-azure/sessions/35057102-f07f-45ec-a141-adc327a4aabf Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
markcowl
left a comment
There was a problem hiding this comment.
Pull the latest from main, make these changes, ensure that all tests pass and all files are formatted correctly, then ensure that only files associated with this pull request are in the pr diff.
| // they can be merged into the canonical entry's present-operation set | ||
| // without producing duplicate diagnostics for the same logical | ||
| // resource. | ||
| const extraPresentByModel = new Map<Model, Set<RequiredOperation>>(); |
There was a problem hiding this comment.
This makes this check inaccurate for extension resources. If a resource has operations at multiple scopes, then each scope is considered a different resource with its own operation requirements, regardless of what operations are available at any other scope for this resource model.
Just remove the concept of extraPrenstByModel, update your tests to test this - using an extension operation that has the required operation at one scope and does not have it at another (which should result in a diagnostic) and by having a case where all required operations are available at all scopes.
There was a problem hiding this comment.
Removed the Model-keyed extraPresentByModel merging. Each ResolvedResource is now validated independently against its own required-operations set. Resolver path-doubling artifacts (entries whose parent chain loops back to an entry of the same model) are still merged, but only into their specific canonical ancestor entry — not across all entries sharing the same model — so genuine multi-scope extension resources are checked independently per scope. Added two tests: extension at multiple scopes with read at every scope (valid), and extension at multiple scopes where one scope is missing read (emits missingGet for that scope) (aaefb2e).
…cestor entry Agent-Logs-Url: https://github.com/Azure/typespec-azure/sessions/b37c44f0-bedf-4b7b-a57b-a4f7ec574919 Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
Merged latest |
⚡ Benchmark Results
Full details – comparing
|
| Metric | Baseline | Current | Change |
|---|---|---|---|
| total | 🔴 714.1ms | 🔴 777.4ms | +8.9% 🔴 |
| loader | 🟢 138.4ms | 🟢 144.4ms | +4.4% |
| resolver | 🟢 16.1ms | 🟢 21.8ms | +34.9% 🔴 |
| checker | 🟢 167.8ms | 🟢 186.0ms | +10.8% 🔴 |
| validation | 🟢 38.9ms | 🟢 41.6ms | +6.8% 🔴 |
| ↳ validation/@azure-tools/typespec-azure-core | 🟢 5.5ms | 🟢 5.8ms | +6.6% |
| ↳ validation/@typespec/http | 🟢 5.1ms | 🟢 5.1ms | -0.1% |
| ↳ validation/@typespec/rest | 🟢 0.5ms | 🟢 0.8ms | +50.8% |
| ↳ validation/@typespec/versioning | 🔴 26.1ms | 🔴 27.5ms | +5.3% 🔴 |
| ↳ validation/compiler | 🟢 1.4ms | 🟢 1.7ms | +21.8% |
| linter | 🟢 116.4ms | 🟢 126.2ms | +8.3% 🔴 |
| ↳ linter/@azure-tools/typespec-azure-core/auth-required | 🟢 0.0ms | 🟢 0.0ms | +30.0% |
| ↳ linter/@azure-tools/typespec-azure-core/bad-record-type | 🟢 0.2ms | 🟢 0.2ms | +5.5% |
| ↳ linter/@azure-tools/typespec-azure-core/byos | 🟢 5.1ms | 🟢 5.5ms | +7.8% |
| ↳ linter/@azure-tools/typespec-azure-core/casing-style | 🟢 0.5ms | 🟢 0.6ms | +8.2% |
| ↳ linter/@azure-tools/typespec-azure-core/composition-over-inheritance | 🟢 0.1ms | 🟢 0.1ms | +26.6% |
| ↳ linter/@azure-tools/typespec-azure-core/documentation-required | 🟢 0.7ms | 🟢 0.9ms | +18.8% |
| ↳ linter/@azure-tools/typespec-azure-core/friendly-name | 🟢 0.5ms | 🟢 0.6ms | +9.4% |
| ↳ linter/@azure-tools/typespec-azure-core/key-visibility-required | 🟢 0.1ms | 🟢 0.2ms | +17.9% |
| ↳ linter/@azure-tools/typespec-azure-core/known-encoding | 🟢 0.2ms | 🟢 0.2ms | +0.5% |
| ↳ linter/@azure-tools/typespec-azure-core/long-running-polling-operation-required | 🟢 0.3ms | 🟢 0.3ms | +13.2% |
| ↳ linter/@azure-tools/typespec-azure-core/no-case-mismatch | 🟢 0.2ms | 🟢 0.2ms | +24.9% |
| ↳ linter/@azure-tools/typespec-azure-core/no-closed-literal-union | 🟢 0.2ms | 🟢 0.3ms | +32.8% |
| ↳ linter/@azure-tools/typespec-azure-core/no-enum | 🟢 0.0ms | 🟢 0.0ms | +22.0% |
| ↳ linter/@azure-tools/typespec-azure-core/no-error-status-codes | 🟢 0.1ms | 🟢 0.1ms | +7.4% |
| ↳ linter/@azure-tools/typespec-azure-core/no-explicit-routes-resource-ops | 🟢 0.1ms | 🟢 0.1ms | +23.7% |
| ↳ linter/@azure-tools/typespec-azure-core/no-format | 🟢 0.4ms | 🟢 0.5ms | +10.0% |
| ↳ linter/@azure-tools/typespec-azure-core/no-generic-numeric | 🟢 0.4ms | 🟢 0.4ms | +25.4% |
| ↳ linter/@azure-tools/typespec-azure-core/no-header-explode | 🟡 16.0ms | 🟡 17.1ms | +7.3% 🔴 |
| ↳ linter/@azure-tools/typespec-azure-core/no-legacy-usage | 🟢 1.0ms | 🟢 1.1ms | +4.5% |
| ↳ linter/@azure-tools/typespec-azure-core/no-multiple-discriminator | 🟢 0.1ms | 🟢 0.1ms | +16.9% |
| ↳ linter/@azure-tools/typespec-azure-core/no-nullable | 🟢 0.2ms | 🟢 0.2ms | +16.3% |
| ↳ linter/@azure-tools/typespec-azure-core/no-offsetdatetime | 🟢 1.1ms | 🟢 1.3ms | +16.6% |
| ↳ linter/@azure-tools/typespec-azure-core/no-openapi | 🟢 1.7ms | 🟢 1.9ms | +10.2% |
| ↳ linter/@azure-tools/typespec-azure-core/no-private-usage | 🟢 1.7ms | 🟢 2.0ms | +20.1% |
| ↳ linter/@azure-tools/typespec-azure-core/no-query-explode | 🟡 16.8ms | 🟡 17.0ms | +1.6% |
| ↳ linter/@azure-tools/typespec-azure-core/no-response-body | 🔴 21.4ms | 🔴 22.0ms | +3.1% |
| ↳ linter/@azure-tools/typespec-azure-core/no-rest-library-interfaces | 🟢 0.0ms | 🟢 0.0ms | +32.9% |
| ↳ linter/@azure-tools/typespec-azure-core/no-route-parameter-name-mismatch | 🟢 4.4ms | 🟢 4.6ms | +6.5% |
| ↳ linter/@azure-tools/typespec-azure-core/no-rpc-path-params | 🟢 0.2ms | 🟢 0.2ms | +19.5% |
| ↳ linter/@azure-tools/typespec-azure-core/no-string-discriminator | 🟢 0.0ms | 🟢 0.0ms | +24.6% |
| ↳ linter/@azure-tools/typespec-azure-core/no-unknown | 🟢 0.1ms | 🟢 0.2ms | +4.7% |
| ↳ linter/@azure-tools/typespec-azure-core/no-unnamed-union | 🟢 0.3ms | 🟢 0.4ms | +25.8% |
| ↳ linter/@azure-tools/typespec-azure-core/operation-missing-api-version | 🟢 0.2ms | 🟢 0.2ms | +33.3% |
| ↳ linter/@azure-tools/typespec-azure-core/request-body-problem | 🟢 0.2ms | 🟢 0.2ms | +13.8% |
| ↳ linter/@azure-tools/typespec-azure-core/require-versioned | 🟢 0.0ms | 🟢 0.0ms | +38.2% |
| ↳ linter/@azure-tools/typespec-azure-core/response-schema-problem | 🟡 19.0ms | 🔴 20.2ms | +6.0% 🔴 |
| ↳ linter/@azure-tools/typespec-azure-core/rpc-operation-request-body | 🟢 0.3ms | 🟢 0.3ms | +9.0% |
| ↳ linter/@azure-tools/typespec-azure-core/spread-discriminated-model | 🟢 0.2ms | 🟢 0.2ms | +10.0% |
| ↳ linter/@azure-tools/typespec-azure-core/use-standard-names | 🟢 4.2ms | 🟢 4.4ms | +3.8% |
| ↳ linter/@azure-tools/typespec-azure-core/use-standard-operations | 🟢 0.1ms | 🟢 0.1ms | +11.0% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-common-types-version | 🟢 3.6ms | 🟢 3.8ms | +6.2% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-custom-resource-no-key | 🟢 0.1ms | 🟢 0.1ms | +7.0% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-custom-resource-usage-discourage | 🟢 0.1ms | 🟢 0.1ms | +20.2% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-delete-operation-response-codes | 🟢 4.6ms | 🟢 5.0ms | +7.5% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-no-path-casing-conflicts | 🟢 4.1ms | 🟢 4.5ms | +8.7% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-no-record | 🟢 0.3ms | 🟢 0.3ms | +1.1% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-post-operation-response-codes | 🟢 0.4ms | 🟢 0.4ms | +6.2% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-put-operation-response-codes | 🟢 0.0ms | 🟢 0.0ms | +24.6% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-action-no-segment | 🟢 0.2ms | 🟢 0.2ms | +8.6% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-duplicate-property | 🟢 0.1ms | 🟢 0.1ms | -0.7% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-interface-requires-decorator | 🟢 0.0ms | 🟢 0.0ms | +47.4% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-invalid-action-verb | 🟢 0.1ms | 🟢 0.1ms | +25.8% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-invalid-envelope-property | 🟢 0.1ms | 🟢 0.1ms | +4.8% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-invalid-version-format | 🟢 0.0ms | 🟢 0.0ms | +7.0% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-key-invalid-chars | 🟢 0.2ms | 🟢 0.2ms | +3.9% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-name-pattern | 🟢 0.0ms | 🟢 0.0ms | +24.0% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-operation | 🟢 0.1ms | 🟢 0.2ms | +23.9% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-operation-response | 🟢 4.1ms | 🟢 4.5ms | +8.7% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-patch | 🟢 0.3ms | 🟢 0.3ms | +7.9% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-path-segment-invalid-chars | 🟢 0.2ms | 🟢 0.2ms | +4.8% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-provisioning-state | 🟢 0.1ms | 🟢 0.1ms | +9.5% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/arm-resource-required-operations | 🟢 0.0ms | 🟢 5.6ms | +100.0% 🔴 |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/beyond-nesting-levels | 🟢 0.1ms | 🟢 0.1ms | +7.9% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/empty-updateable-properties | 🟢 0.1ms | 🟢 0.1ms | +17.6% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/improper-subscription-list-operation | 🟢 0.0ms | 🟢 0.0ms | +105.5% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/lro-location-header | 🟡 12.7ms | 🟡 12.4ms | -1.8% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/missing-operations-endpoint | 🟢 0.0ms | 🟢 0.0ms | +35.1% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/missing-x-ms-identifiers | 🟢 0.3ms | 🟢 0.3ms | +16.9% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/no-empty-model | 🟢 0.1ms | 🟢 0.1ms | +24.9% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/no-resource-delete-operation | 🟢 0.2ms | 🟢 0.2ms | +8.4% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/no-response-body | 🟡 18.8ms | 🔴 20.2ms | +7.5% 🔴 |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/patch-envelope | 🟢 0.1ms | 🟢 0.1ms | +6.5% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/resource-name | 🟢 0.1ms | 🟢 0.1ms | +7.0% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/secret-prop | 🟢 2.2ms | 🟢 2.9ms | +32.3% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/unsupported-type | 🟢 0.3ms | 🟢 0.4ms | +11.7% |
| ↳ linter/@azure-tools/typespec-azure-resource-manager/version-progression | 🟢 0.0ms | 🟢 0.0ms | +12.1% |
| ↳ linter/@azure-tools/typespec-client-generator-core/property-name-conflict | 🟢 0.9ms | 🟢 1.0ms | +8.4% |
| ↳ linter/@azure-tools/typespec-client-generator-core/require-client-suffix | 🟢 0.2ms | 🟢 0.2ms | +30.4% |
| emit | 🟡 232.2ms | 🟡 251.2ms | +8.2% 🔴 |
| ↳ emit/@azure-tools/typespec-autorest | 🟢 144.4ms | 🟢 157.3ms | +9.0% 🔴 |
| ↳ emit/@typespec/openapi3 | 🟢 129.9ms | 🟢 140.9ms | +8.4% 🔴 |
| ↳ emit/@typespec/openapi3/compute | 🟢 115.7ms | 🟢 126.3ms | +9.2% 🔴 |
| ↳ emit/@typespec/openapi3/write | 🟢 13.6ms | 🟢 14.7ms | +8.6% 🔴 |
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)
…rule Adds #suppress directives to 136 TypeSpec files across 57 ARM resource manager specs that violate the new arm-resource-required-operations rule from Azure/typespec-azure#4380. These suppressions allow the external integration check to pass while the specs are updated to include their required operations. Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
markcowl
left a comment
There was a problem hiding this comment.
@copilot The samples are failing to rebuild. Please pull the latest, rebuild the samples, and add any necessary suppressions to ensure samples build without violations. Then make sure all files are correctly formatted and only files that are part of this PR are in the PR diff.
Co-authored-by: markcowl <1054056+markcowl@users.noreply.github.com>
Adds a new
arm-resource-required-operationslinter rule for@azure-tools/typespec-azure-resource-managerthat validates ARM resources declare their required operations.Required operations by resource kind
read,createOrUpdate,delete,list-by-parent(satisfied bylist-by-resource-group), andlist-by-subscription(only for top-level resource-group-scoped resources).read,createOrUpdateonly.readis required.The "any resource with
createOrUpdate(PUT) must also definedelete" condition continues to be enforced by the existingno-resource-delete-operationrule and is intentionally not duplicated here.Resources derived from
Azure.ResourceManager.CommonTypes.NetworkSecurityPerimeterConfiguration,PrivateLinkResource, orPrivateEndpointConnectionare exempt.The rule iterates each
ResolvedResourceentry returned byresolveArmResources(called once per program) and treats each entry as an independent resource — when an extension resource has operations at multiple scopes, each scope is checked independently against the required-operation set, so an operation present at one scope does not satisfy the requirement at another scope. Operations contributed by additional resolver entries sharing the same canonical ancestor path (pure path-doubling artifacts, where a model appears as its own ancestor) are merged into that specific canonical entry's operation set only.kind,singleton, andoperationsare read directly off eachResolvedResource. A single default diagnostic is emitted listing every missing operation when more than one is missing; specific message IDs (missingGet,missingCreateOrUpdate,missingDelete,missingListByParent,missingListBySubscription) are used when only one operation is missing. The diagnostic target is the resource's@armResourceOperationsinterface (where the operations are declared), and code fixes insert template-based operation declarations into that interface.Changes
no-resource-delete-operation, missing-read emitsmissingGet), multi-scope extension resources (all required operations present at every scope = valid; required operation present at one scope but missing at another = diagnostic),@armVirtualResourceskip, and the NSP / PrivateLink / PrivateEndpoint exemptions.listBySubscriptionto theWidgetinterfaces inlegacy/optional-bodyandlegacy/rename-operations(and refreshed their snapshots).