[Swagger Linter Migration] ConsistentResponseSchemaForPut - #5423
[Swagger Linter Migration] ConsistentResponseSchemaForPut#5423Yuchao Yan (msyyc) wants to merge 2 commits into
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
All changed packages have been documented.
Show changes
|
📦 Package size report✅ No notable package size changes compared to the base branch. 13 package(s) with no notable change
Packed = gzipped |
commit: |
|
You can try these changes here
|
…5424) ## Original Swagger linter linter code: [ConsistentResponseSchemaForPut](https://github.com/Azure/azure-openapi-validator/blob/1198225afecbb818c3050d4d2a91da92e14e56ce/packages/rulesets/src/spectral/functions/consistent-response-schema-for-put.ts) linter doc: [consistent-response-schema-for-put.md](https://github.com/Azure/azure-openapi-validator/blob/1198225afecbb818c3050d4d2a91da92e14e56ce/docs/consistent-response-schema-for-put.md) - [x] Inspect ARM PUT operations, not other HTTP verbs. - [x] Compare exactly the `200` and `201` response schemas. - [x] Ignore an operation if either status or either schema is missing. - [x] Report a mismatch at the `200` response schema. - [x] Do not require `200` or `201`, or compare other success statuses. ## Why this follow-up is needed The original migration landed in #5392. Preparing official ARM promotion #5423 exposed two source-rule false-positive bugs that were not covered by the original completion claim. The user explicitly reopened source repair; this PR publishes those fixes and refreshed evidence rather than repeating the original migration. The source walker reported a concrete `different is Template<int32>` endpoint and its instantiated `Template` source. Also, ordinary `unknown[]` carries the compiler's intrinsic indexer decorator: requiring no decorators prevented it from comparing equal to a tuple even though both emit an unconstrained array schema. The checked-in compliant fixture actually contained that false warning. ## How the Swagger linter works The ARM Spectral registration is staging-only, resolved OpenAPI 2, selector `$.paths.*`. The function reads each path item's PUT operation and requires both `responses["200"].schema` and `responses["201"].schema` before comparing them. Its JavaScript object-identity comparison produces false positives for independently resolved identical references and equal inline objects. Those artifacts must not be recreated in the native rule. For example, DurableTask's `Schedulers_CreateOrUpdatePrivateEndpointConnection` uses the same external `PrivateEndpointConnection` reference for both statuses but the staging validator reports it. Reservations/Quota is genuinely different: its `200` body is `CurrentQuotaLimitBase`, while its `201` body is `QuotaRequestSubmitResponse201`. ## How the migrated TypeSpec linter works The existing `consistent-response-schema-for-put` operation listener uses `getHttpOperation` to select exact PUT/200/201 responses with bodies. Type identity, conservative anonymous-model comparison, and emitted file/string/array categories preserve the original rule's intended contract rather than the validator's object-identity defects. This repair adds two narrowly scoped corrections: - `isTemplateDeclarationOrInstance` filters non-endpoint operation/interface template sources before HTTP inspection. Concrete aliases and inherited operations on concrete interfaces are still checked. Two distinct violating aliases still produce two diagnostics; one violating alias has one operation-targeted diagnostic. - Intrinsic array-indexer identity is obtained from the standard `Array` declaration using `checker.getStdType("Array")` and the public AST `SyntaxKind`. Only that exact decorator function is ignored for unconstrained `unknown[]` normalization. A custom decorator with the same JavaScript name does not bypass the check; named, friendly-named, constrained, and typed arrays remain distinct. Version behavior is unchanged. The full runner observes the source program for this rule; its sole diagnostic belongs to the dataset-selected Quota API version. No older-version diagnostic filtering or count-forcing normalization was introduced. The source's existing ARM provider guard is intentionally unchanged, and its known nested-namespace limitation is explicitly documented. Removing that surplus guard from the official ARM rule is the separately approved destination adaptation in #5423, not an undisclosed source change. The corresponding development/promotion guidance landed in #5421. ## Migration evidence See [migration.md](https://github.com/Azure/typespec-azure/blob/feature/lintdiff-consistent-response-schema-for-put/packages/typespec-lintdiff/test/fixtures/ConsistentResponseSchemaForPut/migration.md) for the focused regressions and fixtures, refreshed full-corpus comparison, selected-version attribution, complete one-sided project lists, raw/deduplicated identities, code-backed gap examples, compile failures, and remaining limitations. The source build, 15 regression tests, all 11 focused fixtures, and changed-file lint/format checks pass. Independent source review found no significant issues. The full 468-project run completed; its six pre-existing compile failures remain excluded and disclosed. Package-wide lint has unrelated baseline warnings; this repair follows the skill's required changed-file lint procedure. Generated corpus data is validation evidence only and is excluded from this PR. The final source rule blob matches the repaired implementation already transplanted into #5423; after publication, that PR can replace its temporary uncommitted-source provenance with this branch and commit. The focused harness filters resolver diagnostics and common-type sources were not provisioned, so fixture success does not prove successful external-reference resolution. The emitted reference pairs were inspected directly; template and tuple/array regression proofs do not depend on external references. This limitation is recorded in the migration evidence rather than hidden by a blanket parity claim. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
The new rule has a couple of concrete correctness/documentation issues (response-body variant handling, intrinsic indexer detection guard, suppression string) that should be fixed before merging.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR migrates the Swagger/ARM guideline ConsistentResponseSchemaForPut into a native @azure-tools/typespec-azure-resource-manager linter rule named put-response-schema-consistency, wires it into the ARM linter package, and documents/registers it (disabled by default in the resource-manager ruleset).
Changes:
- Add
put-response-schema-consistencyrule implementation that compares PUT200vs201response body schemas and reports a warning on mismatch. - Add a comprehensive Vitest suite covering matching/mismatching schemas and various edge cases (templates, arrays/tuples, content types, library exclusions).
- Register the rule in ARM docs/README and in
typespec-azure-rulesetsas disabled (false) by default, with Chronus entries.
File summaries
| File | Description |
|---|---|
| website/src/content/docs/docs/libraries/azure-resource-manager/reference/linter.md | Adds the new rule to the ARM linter reference list. |
| website/src/content/docs/docs/howtos/ARM/arm-rules.md | Maps the new rule to the original Swagger linter doc in the ARM rules guidance table. |
| packages/typespec-azure-rulesets/src/rulesets/resource-manager.ts | Registers the rule in the resource-manager ruleset as disabled by default. |
| packages/typespec-azure-resource-manager/test/rules/put-response-schema-consistency.test.ts | Adds end-to-end rule tests covering many schema equivalence/mismatch scenarios. |
| packages/typespec-azure-resource-manager/src/rules/put-response-schema-consistency.ts | Implements the new linter rule logic and schema-equivalence comparisons. |
| packages/typespec-azure-resource-manager/src/rules/put-response-schema-consistency.md | Adds rule documentation, examples, and suppression guidance. |
| packages/typespec-azure-resource-manager/src/linter.ts | Wires the new rule into the ARM linter’s exported rules list. |
| packages/typespec-azure-resource-manager/README.md | Lists the new rule in the package README rule table. |
| .chronus/changes/put-response-schema-consistency-rulesets.md | Chronus entry for rulesets registration (internal). |
| .chronus/changes/put-response-schema-consistency-arm.md | Chronus entry for adding the ARM rule (feature). |
Review details
- Files reviewed: 10/10 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…5435) ## Original Swagger linter linter code: [ConsistentResponseSchemaForPut](https://github.com/Azure/azure-openapi-validator/blob/6243cb01c16c7535cd3b8df6f45fbeb3c095ed7f/packages/rulesets/src/spectral/functions/consistent-response-schema-for-put.ts) linter doc: [consistent-response-schema-for-put.md](https://github.com/Azure/azure-openapi-validator/blob/6243cb01c16c7535cd3b8df6f45fbeb3c095ed7f/docs/consistent-response-schema-for-put.md) - [x] Apply the ARM, OpenAPI 2, staging-only `RPC-Put-V1-29` check to resolved path items selected by `$.paths.*`. - [x] Ignore null and non-object path items. - [x] Inspect PUT only, not other HTTP verbs. - [x] Look up the exact `201` and `200` response schemas. - [x] Remain silent if either response schema is missing. - [x] Report differing schemas at `put.responses.200.schema`. ## How the Swagger linter works The validator compares the resolved JavaScript schema objects with `!==`, once per PUT path item. It does not compare `202` responses. Object identity can falsely reject equal inline schemas and separately resolved identical external references; the TypeSpec migration intentionally does not reproduce those defects. This follow-up repairs the source after promotion review [comment 3956634943](#5423 (comment)). The review highlighted last-body selection across response variants. Investigation established that AutoRest's `emitResponseObject` makes the same selection but also rejects different body types within one status with `duplicate-body-types`. This is an invalid-input diagnostic problem, not a demonstrated missed check on successfully emitted Swagger. ## How the migrated TypeSpec linter works The existing operation listener uses `getHttpOperation`, considers only exact PUT `200`/`201` bodies, aggregates content types, and compares the existing emitted-schema categories or TypeSpec type identity/conservative anonymous structure. It filters template declarations and instances and reports once on the authored concrete operation, without emitter execution or version projection in production. The user explicitly chose to skip conflicting body-type groups rather than preserve an order-dependent secondary warning. `getResponseBody` now returns no comparable body if two body-bearing variants have different `Type` identities. AutoRest's error remains. No union schema is invented and distinct anonymous types are not structurally merged inside a status. Valid groups still collect every content type: JSON plus binary bytes at `201` remain a byte-string schema, distinct from a binary-only `200` file schema in either variant order. Bodyless variants do not invalidate an available body. Comparisons outside this invalid-input exemption, including the known lintdiff-only provider guard, are unchanged. ## Migration evidence See [migration.md](https://github.com/Azure/typespec-azure/blob/feature/lintdiff-consistent-response-schema-for-put/packages/typespec-lintdiff/test/fixtures/ConsistentResponseSchemaForPut/migration.md) for focused regressions and fixture evidence, the refreshed full-corpus comparison, complete one-sided project lists, selected-version attribution, six pre-existing compile failures, reference-pair examples, and remaining evidence limitations. The source regression tests exercise conflicts at both statuses and assert that AutoRest still rejects the inputs. The new emitted fixture preserves genuine named-schema and JSON/binary mismatches under reversed variant ordering. Independent review found no significant issues. Generated corpus files, temporary fixture junctions, dependencies, and the promotion copy are excluded. This source repair follows merged #5424; promotion #5423 must consume the new source separately. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
The anonymous-schema equivalence currently flags any defaulted property as a mismatch even when defaults are identical, which can produce false-positive diagnostics.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
packages/typespec-azure-resource-manager/src/rules/put-response-schema-consistency.ts:213
arePlainAnonymousTypesEquivalenttreats any presence of a default as a mismatch (defaultValue !== undefined), which will also flag cases where both 200/201 schemas specify the same default. Defaults are part of the schema contract, so identical defaults should not cause a warning; only differing defaults should.
- Files reviewed: 10/10 changed files
- Comments generated: 0 new
- Review effort level: Lite
Original Swagger linter
Linter code: ConsistentResponseSchemaForPut
Linter doc: consistent-response-schema-for-put.md
RPC-Put-V1-29, resolved path items selected by$.paths.*.200and201response schemas only when both exist.How the Swagger linter works
The validator reads
pathItem.put.responses["201"].schemaandresponses["200"].schema, compares resolved objects with!==, and reports atput.responses.200.schema. It does not compare POST or202responses. Object identity is not schema equality: separately resolved references and equivalent inline schemas can produce false positives. The native rule preserves the intended schema contract rather than those identity defects.Source TypeSpec lintdiff rule
Source: consistent-response-schema-for-put.ts.
ConsistentResponseSchemaForPut; local diagnostic:tsp-lintdiff-local-linter/consistent-response-schema-for-put.consistent-response-schema-for-put.feature/lintdiff-migration-new.c0a0c605bdbe9897148f36f0eb08cbf3646c1700; repair head:5e7cc75c1037b81fd1bb100a5d35ad0f80dd77cb.78a6d1857da53097cbbe6e657b951de4ca27f9d8.C:\dev\worktrees\lintdiff-consistent-response-schema-for-put.Promotion previously stopped on source-semantic findings. The user explicitly reopened source repair rather than changing behavior only in the native copy. #5424 repaired template-instance duplicates and tuple/unknown-array handling. #5435 now skips conflicting body-type identities within one response status. The emitter already rejects these cases with
duplicate-body-types; the repair removes order-dependent secondary lint warnings, not a demonstrated missed mismatch on valid emitted Swagger.This promotion was synchronized with the merged #5435 source in
ed7d1bf6eb94d29ce37a05570d99a37ce2ff172f. No lintdiff source, fixtures, snapshots, or migration documentation were changed during this update. Source, fixture, and evidence links use the surviving merged-source branch; the immutable commit and blob above pin the handoff.Destination analysis
The user selected
@azure-tools/typespec-azure-resource-manager. Catalog metadata identifies ARM applicability andRPC-Put-V1-29; the contract concerns create/replace results for ARM PUT APIs.Azure Core's
response-schema-problemis broader, checking all non-error bodies across verbs and success statuses. ARM'sarm-resource-operation-responsechecks lifecycle resource identities, whilearm-put-operation-response-codeschecks status codes. Neither replaces this exact body-schema comparison. Core must not gain an ARM dependency.The concise native name is
put-response-schema-consistency.How the promoted TypeSpec linter works
The operation listener uses
getHttpOperationand compares exact numeric200and201responses for PUT when both have bodies. Multiple content variants sharing one body type contribute all their content types. If either status contains different body-type identities, skip the comparison and leave the emitter's invalid-body error to the author; do not select an arbitrary variant or synthesize an equivalent union.Otherwise compare shared type identity or conservatively compare undecorated anonymous models, including property names/count, optionality, defaults, decorators, and nested types. Distinct named models remain distinct schemas. Constant emitted schema categories cover binary/file bodies, multipart/built-in string bodies, and tuples/inline unconstrained
unknown[]. No emitter dependency, emitted-JSON traversal, or version projections are added. Diagnostics target the authored operation once.Deliberate destination adaptations remain unchanged:
resolveProviderNamespacesearches descendants, not ancestors; nested and unannotated operations are checked when this rule is explicitly enabled.isInternalTypeSpechelper.fileRefdocumentation.isTemplateDeclarationOrInstanceexcludes source templates while preserving concrete aliases and inherited concrete operations. Intrinsic Array metadata is matched by the genuine standard decorator's function identity, not its name. Named/constrained/friendly-named/custom-decorated arrays remain distinct from tuples.The official resource-manager ruleset entry remains plain
false. This PR makes the rule available without enabling new diagnostics for existing service specs. Existing Chronus entries remain separate: ARMfeature, disabled rulesets registrationinternal.Fixture-to-native test mapping
Native file:
packages/typespec-azure-resource-manager/test/rules/put-response-schema-consistency.test.ts. Tests use ARMTesterandcreateLinterRuleTester, not copied harness snapshots. Violations assert exact diagnostic count/message/severity/operation target; compliance is asserted directly. Suppression is exercised through the compiler's actual linter pipeline, with an unsuppressed control.Fixture paths below are relative to the merged fixture directory; each names its
main.tsp.ittitle(s)different-put-responses/main.tspreports different PUT resource response schemas once on the operationsame-put-responses/main.tspallows matching PUT resource response schemasput-200-202-different-schemas/main.tspignores different PUT 200 and 202 schemasput-only-201-response/main.tspallows a PUT with only a 201 responsepost-200-201-different-schemas/main.tspignores different POST 200 and 201 schemasmissing-response-body/main.tspallows either exact status to omit its bodysame-external-reference-body/main.tspallows identical external common-type referencessame-inline-response-bodies/main.tspallows equal inline schemas and shared scalar enum and union typesdifferent-inline-response-bodies/main.tspreports inline metadata defaults content types and constrained schema differencessame-special-response-bodies/main.tspallows equivalent binary multipart tuple string and unknown-array schemassame-multiple-content-types/main.tspallows shared schemas across multiple content variantsdifferent-multiple-content-types/main.tspreports schema differences across valid reordered response groups;aggregates JSON and binary variants at either status regardless of orderAdditional exact regression titles:
ittitle(s)ignores conflicting body types at either exact status in either orderdoes not merge distinct anonymous body variants with equal propertiesallows shared body types across reordered content variantsdoes not treat bodyless variants as conflicting body typessupports suppression with the fully qualified diagnostic codechecks nested and unannotated operations when the ARM rule is enabledignores Azure library declarationscompares nested anonymous properties independent of orderreports anonymous property count name optionality and nested type differencesdoes not equate distinct named schemas with identical propertiesallows a PUT with only a 200 responsereports a versioned operation only once without emitter projectionschecks concrete template uses without reporting the template declarationignores unused operation and interface template declarationschecks concrete interface aliases of operation templateschecks inherited concrete interface operations without duplicate template diagnosticsreports each concrete alias of a shared violating template instancechecks aliases of operations on instantiated interfacesnormalizes explicit Array unknown and reversed tuple response schemaskeeps named constrained friendly-named and typed arrays distinct from tuplesdoes not mistake a custom indexerDecorator function for the compiler intrinsicpreserves equality for the same constrained array typeMigration evidence
See the merged repair's migration.md and rule.md for all 26 source tests, 12 focused fixtures/36 snapshots, fresh one-rule staging scan, and the full 468-project corpus comparison. The repaired full-run index was generated at
2026-09-09T05:17:51.600Z; the process completed successfully at2026-09-09T05:20:31.6454966Z. Detailed population, diagnostic identity, selected-version, one-sided project, and compile-failure evidence stays in that source document rather than being duplicated here. Valid-response behavior and corpus counts were unchanged by the mixed-body repair.Limitations remain explicit: focused harness filtering of
invalid-refand staging resolver-diagnostic retention do not prove successful external-reference resolution. Identical-reference conclusions rest on inspected exact references and the validator's object-identity implementation. The source's nested-namespace guard limitation remains documented; its removal here is the approved destination adaptation. No harness or corpus was rerun for this promotion update.Promotion sync policy
Lintdiff remains the behavioral source of truth. The authorized repairs are now merged through #5424 and #5435. Future source-semantic gaps block promotion until source repair is explicitly reopened; they must not be silently fixed only in the native rule.
Native validation
For update
ed7d1bf6eb94d29ce37a05570d99a37ce2ff172f:Broader validation limitation
The earlier
TYPESPEC_SKIP_WEBSITE_BUILD=true pnpm validate:pr --verboseattempt stopped at the workflow's five-minute no-progress bound during the full-repository build on 2026-09-08. Broad local validation remains incomplete; this was not a native rule-test failure. This narrowly scoped update reran the successful targeted package checks rather than repeating that unrelated broad build. No lockfile, submodule, unrelated build files, or corpus outputs are included. CI's dedicated Website job remains authoritative for the website build.NOTE: heavy