From 2d22520be19d0143649fbe1be01f554c3ee18093 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Tue, 25 Aug 2026 14:14:04 +0800 Subject: [PATCH 1/8] Promote PatchBodyParametersSchema rule Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- ...y-parameters-schema-2026-08-25-05-25-55.md | 8 + .../typespec-azure-resource-manager/README.md | 1 + .../src/linter.ts | 2 + .../src/rules/patch-body-invalid-property.md | 60 ++++ .../src/rules/patch-body-invalid-property.ts | 296 ++++++++++++++++ .../rules/patch-body-invalid-property.test.ts | 324 ++++++++++++++++++ .../src/rulesets/resource-manager.ts | 1 + .../reference/linter.md | 1 + 8 files changed, 693 insertions(+) create mode 100644 .chronus/changes/promote-patch-body-parameters-schema-2026-08-25-05-25-55.md create mode 100644 packages/typespec-azure-resource-manager/src/rules/patch-body-invalid-property.md create mode 100644 packages/typespec-azure-resource-manager/src/rules/patch-body-invalid-property.ts create mode 100644 packages/typespec-azure-resource-manager/test/rules/patch-body-invalid-property.test.ts diff --git a/.chronus/changes/promote-patch-body-parameters-schema-2026-08-25-05-25-55.md b/.chronus/changes/promote-patch-body-parameters-schema-2026-08-25-05-25-55.md new file mode 100644 index 0000000000..525f9ff45f --- /dev/null +++ b/.chronus/changes/promote-patch-body-parameters-schema-2026-08-25-05-25-55.md @@ -0,0 +1,8 @@ +--- +changeKind: feature +packages: + - "@azure-tools/typespec-azure-resource-manager" + - "@azure-tools/typespec-azure-rulesets" +--- + +Add an ARM linter rule that reports required, default-valued, and create-only properties emitted in PATCH request bodies. diff --git a/packages/typespec-azure-resource-manager/README.md b/packages/typespec-azure-resource-manager/README.md index 985a806392..1c07cee36f 100644 --- a/packages/typespec-azure-resource-manager/README.md +++ b/packages/typespec-azure-resource-manager/README.md @@ -64,6 +64,7 @@ Available ruleSets: | [`@azure-tools/typespec-azure-resource-manager/missing-x-ms-identifiers`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/missing-x-ms-identifiers) | Array properties should describe their identifying properties with x-ms-identifiers. Decorate the property with @OpenAPI.extension("x-ms-identifiers", #[id-prop]) where "id-prop" is a list of the names of identifying properties in the item type. | | [`@azure-tools/typespec-azure-resource-manager/no-response-body`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/no-response-body) | Check that the body is empty for 202 and 204 responses, and not empty for other success (2xx) responses. | | [`@azure-tools/typespec-azure-resource-manager/missing-operations-endpoint`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/missing-operations-endpoint) | Check for missing Operations interface. | +| [`@azure-tools/typespec-azure-resource-manager/patch-body-invalid-property`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/patch-body-invalid-property) | ARM PATCH body properties must not be required, have defaults, or be create-only. | | [`@azure-tools/typespec-azure-resource-manager/patch-envelope`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/patch-envelope) | Patch envelope properties should match the resource properties. | | [`@azure-tools/typespec-azure-resource-manager/arm-resource-patch`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/arm-resource-patch) | Validate ARM PATCH operations. | | [`@azure-tools/typespec-azure-resource-manager/resource-name`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/resource-name) | Check the resource name. | diff --git a/packages/typespec-azure-resource-manager/src/linter.ts b/packages/typespec-azure-resource-manager/src/linter.ts index 671fdbea61..2ac4f49eeb 100644 --- a/packages/typespec-azure-resource-manager/src/linter.ts +++ b/packages/typespec-azure-resource-manager/src/linter.ts @@ -33,6 +33,7 @@ import { noReservedResourcePropertyRule } from "./rules/no-reserved-resource-pro import { deleteOperationMissingRule } from "./rules/no-resource-delete-operation.js"; import { noResponseBodyRule } from "./rules/no-response-body.js"; import { operationsInterfaceMissingRule } from "./rules/operations-interface-missing.js"; +import { patchBodyInvalidPropertyRule } from "./rules/patch-body-invalid-property.js"; import { patchEnvelopePropertiesRules } from "./rules/patch-envelope-properties.js"; import { resourceNameRule } from "./rules/resource-name.js"; import { retryAfterRule } from "./rules/retry-after.js"; @@ -81,6 +82,7 @@ const rules = [ missingXmsIdentifiersRule, noResponseBodyRule, operationsInterfaceMissingRule, + patchBodyInvalidPropertyRule, patchEnvelopePropertiesRules, patchOperationsRule, resourceNameRule, diff --git a/packages/typespec-azure-resource-manager/src/rules/patch-body-invalid-property.md b/packages/typespec-azure-resource-manager/src/rules/patch-body-invalid-property.md new file mode 100644 index 0000000000..6672578250 --- /dev/null +++ b/packages/typespec-azure-resource-manager/src/rules/patch-body-invalid-property.md @@ -0,0 +1,60 @@ +--- +title: "patch-body-invalid-property" +--- + +```text title="Full name" +@azure-tools/typespec-azure-resource-manager/patch-body-invalid-property +``` + +ARM PATCH request body properties must be update-safe. A property emitted in an ARM PATCH body must not be required, must not define a default value, and must not be create-only. + +## Impact + +- **Area:** API + +PATCH describes partial updates. Required PATCH body properties, default-valued properties, and create-only properties can make partial updates ambiguous for service authors and SDKs, and can produce ARM OpenAPI that violates PATCH request-body guidance. + +The rule checks the effective emitted PATCH payload. Properties omitted from the PATCH payload, such as `never` properties or create-only properties removed by the PATCH visibility transform, are not reported. A top-level emitted property named `identity` is skipped to match ARM PATCH identity envelope behavior. + +## ❌ Incorrect + +```tsp +@armProviderNamespace +namespace Microsoft.Contoso; + +model WidgetPatchBody { + displayName: string; + enabled?: boolean = false; + + @visibility(Lifecycle.Create) + createdBy?: string; +} + +@route("/widgets/{name}") +@patch +op update(@path name: string, @body body: WidgetPatchBody): void; +``` + +## ✅ Correct + +```tsp +@armProviderNamespace +namespace Microsoft.Contoso; + +model WidgetPatchBody { + displayName?: string; + enabled?: boolean; +} + +@route("/widgets/{name}") +@patch +op update(@path name: string, @body body: WidgetPatchBody): void; +``` + +## LintDiff Equivalent + +This rule corresponds to the LintDiff rule [PatchBodyParametersSchema](https://github.com/Azure/azure-openapi-validator/blob/main/docs/patch-body-parameters-schema.md). + +## Suppression + +Do not suppress this rule for ordinary ARM resource PATCH operations. Fix the PATCH model so updateable properties are optional, do not carry defaults, and exclude create-only properties from the emitted PATCH payload. diff --git a/packages/typespec-azure-resource-manager/src/rules/patch-body-invalid-property.ts b/packages/typespec-azure-resource-manager/src/rules/patch-body-invalid-property.ts new file mode 100644 index 0000000000..0dcc009ee5 --- /dev/null +++ b/packages/typespec-azure-resource-manager/src/rules/patch-body-invalid-property.ts @@ -0,0 +1,296 @@ +import { + createRule, + fileRef, + getDiscriminator, + getLifecycleVisibilityEnum, + getLocationContext, + getVisibilityForClass, + isNeverType, + isNullType, + paramMessage, + resolveEncodedName, + type DiagnosticTarget, + type Model, + type ModelProperty, + type Operation, + type Program, + type Type, +} from "@typespec/compiler"; +import { + createMetadataInfo, + getHttpOperation, + resolveRequestVisibility, + Visibility, + type MetadataInfo, +} from "@typespec/http"; +import { resolveProviderNamespace } from "../namespace.js"; + +export const patchBodyInvalidPropertyRule = createRule({ + name: "patch-body-invalid-property", + docs: fileRef.fromPackageRoot("src/rules/patch-body-invalid-property.md"), + severity: "warning", + description: "ARM PATCH body properties must not be required, have defaults, or be create-only.", + url: "https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/patch-body-invalid-property", + messages: { + required: paramMessage`Properties of a PATCH request body must not be required, property:${"propertyName"}.`, + default: paramMessage`Properties of a PATCH request body must not have default value, property:${"propertyName"}.`, + createOnly: paramMessage`Properties of a PATCH request body must not be x-ms-mutability: ["create"], property:${"propertyName"}.`, + }, + create(context) { + return { + operation: (operation) => { + const namespace = operation.interface?.namespace ?? operation.namespace; + if (resolveProviderNamespace(context.program, namespace) === undefined) { + return; + } + + const [httpOperation] = getHttpOperation(context.program, operation); + if (httpOperation.verb !== "patch") { + return; + } + + const patchBody = httpOperation.parameters.body?.type; + if (patchBody === undefined) { + return; + } + + for (const violation of findViolations(context.program, patchBody, operation)) { + context.reportDiagnostic({ + target: violation.target, + messageId: violation.messageId, + format: { + propertyName: violation.propertyName, + }, + }); + } + }, + }; + }, +}); + +type Violation = { + target: DiagnosticTarget; + propertyName: string; + messageId: "required" | "default" | "createOnly"; +}; + +function findViolations(program: Program, patchBody: Type, operation: Operation): Violation[] { + const violations: Violation[] = []; + const metadataInfo = createMetadataInfo(program, { + canonicalVisibility: Visibility.Read, + canShareProperty: (property) => canSharePropertyUsingReadonlyOrXmsMutability(program, property), + }); + const visibility = resolveRequestVisibility(program, operation, "patch"); + collectNestedViolations( + program, + patchBody, + violations, + [], + new Map(), + operation, + metadataInfo, + visibility, + ); + return violations; +} + +function collectViolations( + program: Program, + model: Model, + violations: Violation[], + path: string[] = [], + visited: Map> = new Map(), + diagnosticTarget: DiagnosticTarget, + metadataInfo: MetadataInfo, + visibility: Visibility, +) { + const schemaVisibility = metadataInfo.isTransformed(model, visibility) + ? visibility + : Visibility.Read; + const visitedVisibilities = visited.get(model); + if (visitedVisibilities?.has(schemaVisibility)) { + return; + } + if (visitedVisibilities === undefined) { + visited.set(model, new Set([schemaVisibility])); + } else { + visitedVisibilities.add(schemaVisibility); + } + + const discriminator = getInheritedDiscriminator(program, model); + if ( + discriminator !== undefined && + getModelProperty(model, discriminator.propertyName) === undefined && + !isTopLevelIdentityProperty([...path, discriminator.propertyName], discriminator.propertyName) + ) { + violations.push({ + target: getLocationContext(program, model).type === "project" ? model : diagnosticTarget, + propertyName: [...path, discriminator.propertyName].join("."), + messageId: "required", + }); + } + + for (const property of getModelProperties(model)) { + const jsonName = resolveEncodedName(program, property, "application/json"); + const propertyPath = [...path, jsonName]; + if (isTopLevelIdentityProperty(propertyPath, jsonName)) { + continue; + } + if (!metadataInfo.isPayloadProperty(property, schemaVisibility)) { + continue; + } + if (isNeverType(property.type)) { + continue; + } + const propertyTarget = + getLocationContext(program, property).type === "project" ? property : diagnosticTarget; + + if ( + !metadataInfo.isOptional(property, schemaVisibility) || + property.name === discriminator?.propertyName + ) { + violations.push({ + target: propertyTarget, + propertyName: propertyPath.join("."), + messageId: "required", + }); + } + + if (property.defaultValue !== undefined) { + violations.push({ + target: propertyTarget, + propertyName: propertyPath.join("."), + messageId: "default", + }); + } + + if (isCreateOnlyMutability(program, property)) { + violations.push({ + target: propertyTarget, + propertyName: propertyPath.join("."), + messageId: "createOnly", + }); + } + + collectNestedViolations( + program, + property.type, + violations, + propertyPath, + visited, + propertyTarget, + metadataInfo, + schemaVisibility, + ); + } +} + +function collectNestedViolations( + program: Program, + type: Type, + violations: Violation[], + path: string[], + visited: Map>, + diagnosticTarget: DiagnosticTarget, + metadataInfo: MetadataInfo, + visibility: Visibility, +) { + if (type.kind === "Model") { + collectViolations( + program, + type, + violations, + path, + visited, + diagnosticTarget, + metadataInfo, + visibility, + ); + return; + } + + if (type.kind === "Union") { + const nonNullVariants = [...type.variants.values()] + .map((variant) => variant.type) + .filter((variant) => !isNullType(variant)); + if (nonNullVariants.length === 1) { + collectNestedViolations( + program, + nonNullVariants[0], + violations, + path, + visited, + diagnosticTarget, + metadataInfo, + visibility, + ); + } + } +} + +function isTopLevelIdentityProperty(propertyPath: string[], jsonName: string): boolean { + return propertyPath.length === 1 && jsonName.toLowerCase() === "identity"; +} + +function isCreateOnlyMutability(program: Program, property: ModelProperty): boolean { + const lifecycle = getLifecycleVisibilityEnum(program); + const create = lifecycle.members.get("Create"); + if (create === undefined) { + return false; + } + + const visibility = getVisibilityForClass(program, property, lifecycle); + return visibility.size === 1 && visibility.has(create); +} + +function getInheritedDiscriminator(program: Program, model: Model) { + for (let current: Model | undefined = model; current !== undefined; current = current.baseModel) { + const discriminator = getDiscriminator(program, current); + if (discriminator !== undefined) { + return discriminator; + } + } + + return undefined; +} + +function canSharePropertyUsingReadonlyOrXmsMutability( + program: Program, + property: ModelProperty, +): boolean { + const lifecycle = getLifecycleVisibilityEnum(program); + const visibility = getVisibilityForClass(program, property, lifecycle); + if (visibility.size === lifecycle.members.size) { + return true; + } + + return ( + visibility.size > 0 && + [...visibility].every((member) => ["Read", "Create", "Update"].includes(member.name)) + ); +} + +function getModelProperty(model: Model, name: string): ModelProperty | undefined { + for (let current: Model | undefined = model; current !== undefined; current = current.baseModel) { + const property = current.properties.get(name); + if (property !== undefined) { + return property; + } + } + + return undefined; +} + +function getModelProperties(model: Model): ModelProperty[] { + const properties = new Map(); + + for (let current: Model | undefined = model; current !== undefined; current = current.baseModel) { + for (const property of current.properties.values()) { + if (!properties.has(property.name)) { + properties.set(property.name, property); + } + } + } + + return [...properties.values()]; +} diff --git a/packages/typespec-azure-resource-manager/test/rules/patch-body-invalid-property.test.ts b/packages/typespec-azure-resource-manager/test/rules/patch-body-invalid-property.test.ts new file mode 100644 index 0000000000..e1a5323852 --- /dev/null +++ b/packages/typespec-azure-resource-manager/test/rules/patch-body-invalid-property.test.ts @@ -0,0 +1,324 @@ +import { Tester } from "#test/tester.js"; +import { + type LinterRuleTester, + type TesterInstance, + createLinterRuleTester, +} from "@typespec/compiler/testing"; +import { beforeEach, describe, it } from "vitest"; +import { patchBodyInvalidPropertyRule } from "../../src/rules/patch-body-invalid-property.js"; + +const ruleCode = "@azure-tools/typespec-azure-resource-manager/patch-body-invalid-property"; + +let runner: TesterInstance; +let tester: LinterRuleTester; + +beforeEach(async () => { + runner = await Tester.createInstance(); + tester = createLinterRuleTester( + runner, + patchBodyInvalidPropertyRule, + "@azure-tools/typespec-azure-resource-manager", + ); +}); + +function patchOperation(bodyType: string): string { + return ` + @armProviderNamespace + namespace Microsoft.TestService; + + @route("/widgets/{name}") + @patch + op update(@path name: string, @body body: ${bodyType}): void; + `; +} + +describe("invalid cases", () => { + it("emits diagnostics for required PATCH body properties", async () => { + await tester + .expect( + ` + ${patchOperation("WidgetPatchBody")} + + model WidgetPatchBody { + displayName: string; + } + `, + ) + .toEmitDiagnostics({ + code: ruleCode, + message: "Properties of a PATCH request body must not be required, property:displayName.", + }); + }); + + it("emits diagnostics for required properties in nullable top-level PATCH bodies", async () => { + await tester + .expect( + ` + ${patchOperation("WidgetPatchBody | null")} + + model WidgetPatchBody { + displayName: string; + } + `, + ) + .toEmitDiagnostics({ + code: ruleCode, + message: "Properties of a PATCH request body must not be required, property:displayName.", + }); + }); + + it("emits diagnostics for required properties in nullable nested PATCH models", async () => { + await tester + .expect( + ` + ${patchOperation("WidgetPatchBody")} + + model WidgetPatchBody { + details?: WidgetPatchDetails | null; + } + + model WidgetPatchDetails { + displayName: string; + } + `, + ) + .toEmitDiagnostics({ + code: ruleCode, + message: + "Properties of a PATCH request body must not be required, property:details.displayName.", + }); + }); + + it("emits diagnostics for discriminator properties that Autorest requires", async () => { + await tester + .expect( + ` + ${patchOperation("WidgetPatchBody")} + + model WidgetPatchBody { + optional?: OptionalDiscriminator; + synthesized?: SynthesizedDiscriminator; + derived?: DerivedDiscriminator; + } + + @discriminator("kind") + model OptionalDiscriminator { + kind?: string; + } + + @discriminator("kind") + model SynthesizedDiscriminator {} + + @discriminator("kind") + model BaseSynthesizedDiscriminator {} + + model DerivedDiscriminator extends BaseSynthesizedDiscriminator { + kind: "derived"; + } + `, + ) + .toEmitDiagnostics([ + { + code: ruleCode, + message: + "Properties of a PATCH request body must not be required, property:optional.kind.", + }, + { + code: ruleCode, + message: + "Properties of a PATCH request body must not be required, property:synthesized.kind.", + }, + { + code: ruleCode, + message: + "Properties of a PATCH request body must not be required, property:derived.kind.", + }, + ]); + }); + + it("emits diagnostics for PATCH body properties with defaults", async () => { + await tester + .expect( + ` + ${patchOperation("WidgetPatchBody")} + + model WidgetPatchBody { + enabled?: boolean = false; + count?: int32 = 0; + label?: string = ""; + mode?: string = "active"; + } + `, + ) + .toEmitDiagnostics([ + { + code: ruleCode, + message: + "Properties of a PATCH request body must not have default value, property:enabled.", + }, + { + code: ruleCode, + message: + "Properties of a PATCH request body must not have default value, property:count.", + }, + { + code: ruleCode, + message: + "Properties of a PATCH request body must not have default value, property:label.", + }, + { + code: ruleCode, + message: "Properties of a PATCH request body must not have default value, property:mode.", + }, + ]); + }); + + it("emits diagnostics for PATCH body properties that are only visible on create", async () => { + await tester + .expect( + ` + ${patchOperation("WidgetPatchBody")} + + model WidgetPatchBody { + @visibility(Lifecycle.Create) + createdBy?: string; + } + `, + ) + .toEmitDiagnostics({ + code: ruleCode, + message: + 'Properties of a PATCH request body must not be x-ms-mutability: ["create"], property:createdBy.', + }); + }); + + it("emits diagnostics for authored identity properties encoded away from top-level identity", async () => { + await tester + .expect( + ` + ${patchOperation("WidgetPatchBody")} + + model WidgetPatchBody { + @encodedName("application/json", "notIdentity") + identity: string; + } + `, + ) + .toEmitDiagnostics({ + code: ruleCode, + message: "Properties of a PATCH request body must not be required, property:notIdentity.", + }); + }); +}); + +describe("valid cases", () => { + it("allows optional PATCH body properties", async () => { + await tester + .expect( + ` + ${patchOperation("WidgetPatchBody")} + + model WidgetPatchBody { + displayName?: string; + } + `, + ) + .toBeValid(); + }); + + it("allows unsupported unions with multiple model variants", async () => { + await tester + .expect( + ` + ${patchOperation("FirstPatchBody | SecondPatchBody")} + + model FirstPatchBody { + first: string; + } + + model SecondPatchBody { + second: string; + } + `, + ) + .toBeValid(); + }); + + it("allows required PATCH body properties omitted because their type is never", async () => { + await tester + .expect( + ` + ${patchOperation("WidgetPatchBody")} + + model WidgetPatchBody { + omitted: never; + } + `, + ) + .toBeValid(); + }); + + it("allows top-level identity PATCH body properties", async () => { + await tester + .expect( + ` + ${patchOperation("WidgetPatchBody")} + + model WidgetPatchBody { + identity: string; + } + `, + ) + .toBeValid(); + }); + + it("allows top-level identity discriminator properties synthesized by Autorest", async () => { + await tester + .expect( + ` + ${patchOperation("WidgetPatchBody")} + + @discriminator("identity") + model WidgetPatchBody {} + `, + ) + .toBeValid(); + }); + + it("allows PATCH body properties encoded as top-level identity", async () => { + await tester + .expect( + ` + ${patchOperation("WidgetPatchBody")} + + model WidgetPatchBody { + @encodedName("application/json", "identity") + tenantIdentity: string; + } + `, + ) + .toBeValid(); + }); + + it("allows required and create-only source properties removed from the emitted PATCH schema", async () => { + await tester + .expect( + ` + @armProviderNamespace + namespace Microsoft.TestService; + + model WidgetProperties { + displayName: string; + @visibility(Lifecycle.Create) + createdBy: string; + } + + #suppress "@typespec/http/deprecated-implicit-optionality" "Test legacy PATCH transform." + @route("/widgets/{name}") + @patch(#{ implicitOptionality: true }) + op update(@path name: string, @body body: WidgetProperties): void; + `, + ) + .toBeValid(); + }); +}); diff --git a/packages/typespec-azure-rulesets/src/rulesets/resource-manager.ts b/packages/typespec-azure-rulesets/src/rulesets/resource-manager.ts index 1167301d2f..635c2a97d9 100644 --- a/packages/typespec-azure-rulesets/src/rulesets/resource-manager.ts +++ b/packages/typespec-azure-rulesets/src/rulesets/resource-manager.ts @@ -96,6 +96,7 @@ export default { "@azure-tools/typespec-azure-resource-manager/missing-x-ms-identifiers": true, "@azure-tools/typespec-azure-resource-manager/no-response-body": true, "@azure-tools/typespec-azure-resource-manager/missing-operations-endpoint": true, + "@azure-tools/typespec-azure-resource-manager/patch-body-invalid-property": true, "@azure-tools/typespec-azure-resource-manager/patch-envelope": true, "@azure-tools/typespec-azure-resource-manager/arm-resource-patch": true, "@azure-tools/typespec-azure-resource-manager/resource-name": true, diff --git a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/linter.md b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/linter.md index 8c63e5f5c0..32fe5b2590 100644 --- a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/linter.md +++ b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/linter.md @@ -58,6 +58,7 @@ Available ruleSets: | [`@azure-tools/typespec-azure-resource-manager/missing-x-ms-identifiers`](../rules/missing-x-ms-identifiers.md) | Array properties should describe their identifying properties with x-ms-identifiers. Decorate the property with @OpenAPI.extension("x-ms-identifiers", #[id-prop]) where "id-prop" is a list of the names of identifying properties in the item type. | | [`@azure-tools/typespec-azure-resource-manager/no-response-body`](../rules/no-response-body.md) | Check that the body is empty for 202 and 204 responses, and not empty for other success (2xx) responses. | | [`@azure-tools/typespec-azure-resource-manager/missing-operations-endpoint`](../rules/missing-operations-endpoint.md) | Check for missing Operations interface. | +| [`@azure-tools/typespec-azure-resource-manager/patch-body-invalid-property`](../rules/patch-body-invalid-property.md) | ARM PATCH body properties must not be required, have defaults, or be create-only. | | [`@azure-tools/typespec-azure-resource-manager/patch-envelope`](../rules/patch-envelope.md) | Patch envelope properties should match the resource properties. | | [`@azure-tools/typespec-azure-resource-manager/arm-resource-patch`](../rules/arm-resource-patch.md) | Validate ARM PATCH operations. | | [`@azure-tools/typespec-azure-resource-manager/resource-name`](../rules/resource-name.md) | Check the resource name. | From 07a3c9f101bc1fe020524aaa08e0cde51005b661 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Tue, 25 Aug 2026 15:50:27 +0800 Subject: [PATCH 2/8] Update resource-manager.ts --- .../typespec-azure-rulesets/src/rulesets/resource-manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typespec-azure-rulesets/src/rulesets/resource-manager.ts b/packages/typespec-azure-rulesets/src/rulesets/resource-manager.ts index 635c2a97d9..ea6b1df86c 100644 --- a/packages/typespec-azure-rulesets/src/rulesets/resource-manager.ts +++ b/packages/typespec-azure-rulesets/src/rulesets/resource-manager.ts @@ -96,7 +96,7 @@ export default { "@azure-tools/typespec-azure-resource-manager/missing-x-ms-identifiers": true, "@azure-tools/typespec-azure-resource-manager/no-response-body": true, "@azure-tools/typespec-azure-resource-manager/missing-operations-endpoint": true, - "@azure-tools/typespec-azure-resource-manager/patch-body-invalid-property": true, + "@azure-tools/typespec-azure-resource-manager/patch-body-invalid-property": false, "@azure-tools/typespec-azure-resource-manager/patch-envelope": true, "@azure-tools/typespec-azure-resource-manager/arm-resource-patch": true, "@azure-tools/typespec-azure-resource-manager/resource-name": true, From 2db9f39351731fea7e13ab7d20d7a31270e8a949 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Mon, 31 Aug 2026 15:57:52 +0800 Subject: [PATCH 3/8] docs(arm): remove generated rule metadata Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: cf125def-85b2-462d-8902-7ac7beb588a3 --- .../src/rules/patch-body-invalid-property.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/typespec-azure-resource-manager/src/rules/patch-body-invalid-property.md b/packages/typespec-azure-resource-manager/src/rules/patch-body-invalid-property.md index 6672578250..c64ec91995 100644 --- a/packages/typespec-azure-resource-manager/src/rules/patch-body-invalid-property.md +++ b/packages/typespec-azure-resource-manager/src/rules/patch-body-invalid-property.md @@ -1,11 +1,3 @@ ---- -title: "patch-body-invalid-property" ---- - -```text title="Full name" -@azure-tools/typespec-azure-resource-manager/patch-body-invalid-property -``` - ARM PATCH request body properties must be update-safe. A property emitted in an ARM PATCH body must not be required, must not define a default value, and must not be create-only. ## Impact From 20ca081df2856919685359f72d1fdafc1909877b Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Wed, 2 Sep 2026 12:57:41 +0800 Subject: [PATCH 4/8] test(arm): cover library PATCH properties Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 727b6828-4146-484f-a492-7b567658ca41 --- .../rules/patch-body-invalid-property.test.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/typespec-azure-resource-manager/test/rules/patch-body-invalid-property.test.ts b/packages/typespec-azure-resource-manager/test/rules/patch-body-invalid-property.test.ts index e1a5323852..aa3416759c 100644 --- a/packages/typespec-azure-resource-manager/test/rules/patch-body-invalid-property.test.ts +++ b/packages/typespec-azure-resource-manager/test/rules/patch-body-invalid-property.test.ts @@ -209,6 +209,36 @@ describe("invalid cases", () => { message: "Properties of a PATCH request body must not be required, property:notIdentity.", }); }); + + it("retargets library property diagnostics to project properties", async () => { + await tester + .expect( + ` + ${patchOperation("WidgetPatchBody")} + + model Item {} + + model WidgetPatchBody { + /*core*/page?: Azure.Core.Page; + /*arm*/managedIdentity?: Azure.ResourceManager.CommonTypes.ManagedServiceIdentity; + identity: Azure.ResourceManager.CommonTypes.ManagedServiceIdentity; + } + `, + ) + .toEmitDiagnostics((x) => [ + { + code: ruleCode, + message: "Properties of a PATCH request body must not be required, property:page.value.", + pos: x.pos.core.pos, + }, + { + code: ruleCode, + message: + "Properties of a PATCH request body must not be required, property:managedIdentity.type.", + pos: x.pos.arm.pos, + }, + ]); + }); }); describe("valid cases", () => { From 526713f6887f3f2dbee0f935d20b21263ac32ecf Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Wed, 2 Sep 2026 13:00:26 +0800 Subject: [PATCH 5/8] docs(arm): catalog PATCH body rule Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 727b6828-4146-484f-a492-7b567658ca41 --- website/src/content/docs/docs/howtos/ARM/arm-rules.md | 1 + 1 file changed, 1 insertion(+) diff --git a/website/src/content/docs/docs/howtos/ARM/arm-rules.md b/website/src/content/docs/docs/howtos/ARM/arm-rules.md index 6dfa1d5aee..c9ebf1a6e0 100644 --- a/website/src/content/docs/docs/howtos/ARM/arm-rules.md +++ b/website/src/content/docs/docs/howtos/ARM/arm-rules.md @@ -129,6 +129,7 @@ The tables below provide guidance to rule authors and ARM reviewers on how to ev | [`no-override-props`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/no-override-props/) | — | **SDK, Tooling.** Violations can crash the breaking-change tool and are unsupported by most languages. | | [`no-resource-delete-operation`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/no-resource-delete-operation/) | [`AllTrackedResourcesMustHaveDelete`](https://github.com/Azure/azure-openapi-validator/blob/main/docs/all-tracked-resources-must-have-delete.md) | **API.** RPC violation. | | [`no-response-body`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/no-response-body/) | — | **API.** A non-empty response, usually for a 202. | +| [`patch-body-invalid-property`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/patch-body-invalid-property/) | [`PatchBodyParametersSchema`](https://github.com/Azure/azure-openapi-validator/blob/main/docs/patch-body-parameters-schema.md) (RPC-Patch-V1-10) | **API.** Required, default-valued, or create-only PATCH properties prevent independent partial updates. | | [`patch-envelope`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/patch-envelope/) | — | **API.** A Patch operation is missing updatable envelope properties. | | [`resource-name`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/resource-name/) | — | **API, SDK.** Invalid characters in a name violate the RPC and create invalid client parameter names, which prevents SDK generation. | | [`secret-prop`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/secret-prop/) | [`XMSSecretInResponse`](https://github.com/Azure/azure-openapi-validator/blob/main/docs/xms-secret-in-response.md) | **API.** RPC violation. | From 64a91f68281261474dc0a099c835530dd6a1a5a5 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Wed, 2 Sep 2026 13:07:28 +0800 Subject: [PATCH 6/8] refactor(arm): rename unsafe PATCH rule Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 727b6828-4146-484f-a492-7b567658ca41 --- ...te-patch-body-parameters-schema-2026-08-25-05-25-55.md | 2 +- packages/typespec-azure-resource-manager/README.md | 2 +- packages/typespec-azure-resource-manager/src/linter.ts | 4 ++-- ...lid-property.md => no-unsafe-patch-body-properties.md} | 2 +- ...lid-property.ts => no-unsafe-patch-body-properties.ts} | 8 ++++---- ...ty.test.ts => no-unsafe-patch-body-properties.test.ts} | 6 +++--- .../src/rulesets/resource-manager.ts | 2 +- website/src/content/docs/docs/howtos/ARM/arm-rules.md | 2 +- .../libraries/azure-resource-manager/reference/linter.md | 2 +- 9 files changed, 15 insertions(+), 15 deletions(-) rename packages/typespec-azure-resource-manager/src/rules/{patch-body-invalid-property.md => no-unsafe-patch-body-properties.md} (89%) rename packages/typespec-azure-resource-manager/src/rules/{patch-body-invalid-property.ts => no-unsafe-patch-body-properties.ts} (97%) rename packages/typespec-azure-resource-manager/test/rules/{patch-body-invalid-property.test.ts => no-unsafe-patch-body-properties.test.ts} (97%) diff --git a/.chronus/changes/promote-patch-body-parameters-schema-2026-08-25-05-25-55.md b/.chronus/changes/promote-patch-body-parameters-schema-2026-08-25-05-25-55.md index 525f9ff45f..2578fc6b76 100644 --- a/.chronus/changes/promote-patch-body-parameters-schema-2026-08-25-05-25-55.md +++ b/.chronus/changes/promote-patch-body-parameters-schema-2026-08-25-05-25-55.md @@ -5,4 +5,4 @@ packages: - "@azure-tools/typespec-azure-rulesets" --- -Add an ARM linter rule that reports required, default-valued, and create-only properties emitted in PATCH request bodies. +Add the `no-unsafe-patch-body-properties` ARM linter rule to report required, default-valued, and create-only properties emitted in PATCH request bodies. diff --git a/packages/typespec-azure-resource-manager/README.md b/packages/typespec-azure-resource-manager/README.md index 1c07cee36f..73b5fb3948 100644 --- a/packages/typespec-azure-resource-manager/README.md +++ b/packages/typespec-azure-resource-manager/README.md @@ -64,7 +64,7 @@ Available ruleSets: | [`@azure-tools/typespec-azure-resource-manager/missing-x-ms-identifiers`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/missing-x-ms-identifiers) | Array properties should describe their identifying properties with x-ms-identifiers. Decorate the property with @OpenAPI.extension("x-ms-identifiers", #[id-prop]) where "id-prop" is a list of the names of identifying properties in the item type. | | [`@azure-tools/typespec-azure-resource-manager/no-response-body`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/no-response-body) | Check that the body is empty for 202 and 204 responses, and not empty for other success (2xx) responses. | | [`@azure-tools/typespec-azure-resource-manager/missing-operations-endpoint`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/missing-operations-endpoint) | Check for missing Operations interface. | -| [`@azure-tools/typespec-azure-resource-manager/patch-body-invalid-property`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/patch-body-invalid-property) | ARM PATCH body properties must not be required, have defaults, or be create-only. | +| [`@azure-tools/typespec-azure-resource-manager/no-unsafe-patch-body-properties`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/no-unsafe-patch-body-properties) | ARM PATCH body properties must not be required, have defaults, or be create-only. | | [`@azure-tools/typespec-azure-resource-manager/patch-envelope`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/patch-envelope) | Patch envelope properties should match the resource properties. | | [`@azure-tools/typespec-azure-resource-manager/arm-resource-patch`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/arm-resource-patch) | Validate ARM PATCH operations. | | [`@azure-tools/typespec-azure-resource-manager/resource-name`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/resource-name) | Check the resource name. | diff --git a/packages/typespec-azure-resource-manager/src/linter.ts b/packages/typespec-azure-resource-manager/src/linter.ts index 2ac4f49eeb..06e2c21c5b 100644 --- a/packages/typespec-azure-resource-manager/src/linter.ts +++ b/packages/typespec-azure-resource-manager/src/linter.ts @@ -32,8 +32,8 @@ import { noOverridePropsRule } from "./rules/no-override-props.js"; import { noReservedResourcePropertyRule } from "./rules/no-reserved-resource-property.js"; import { deleteOperationMissingRule } from "./rules/no-resource-delete-operation.js"; import { noResponseBodyRule } from "./rules/no-response-body.js"; +import { noUnsafePatchBodyPropertiesRule } from "./rules/no-unsafe-patch-body-properties.js"; import { operationsInterfaceMissingRule } from "./rules/operations-interface-missing.js"; -import { patchBodyInvalidPropertyRule } from "./rules/patch-body-invalid-property.js"; import { patchEnvelopePropertiesRules } from "./rules/patch-envelope-properties.js"; import { resourceNameRule } from "./rules/resource-name.js"; import { retryAfterRule } from "./rules/retry-after.js"; @@ -82,7 +82,7 @@ const rules = [ missingXmsIdentifiersRule, noResponseBodyRule, operationsInterfaceMissingRule, - patchBodyInvalidPropertyRule, + noUnsafePatchBodyPropertiesRule, patchEnvelopePropertiesRules, patchOperationsRule, resourceNameRule, diff --git a/packages/typespec-azure-resource-manager/src/rules/patch-body-invalid-property.md b/packages/typespec-azure-resource-manager/src/rules/no-unsafe-patch-body-properties.md similarity index 89% rename from packages/typespec-azure-resource-manager/src/rules/patch-body-invalid-property.md rename to packages/typespec-azure-resource-manager/src/rules/no-unsafe-patch-body-properties.md index c64ec91995..7749e854ef 100644 --- a/packages/typespec-azure-resource-manager/src/rules/patch-body-invalid-property.md +++ b/packages/typespec-azure-resource-manager/src/rules/no-unsafe-patch-body-properties.md @@ -1,4 +1,4 @@ -ARM PATCH request body properties must be update-safe. A property emitted in an ARM PATCH body must not be required, must not define a default value, and must not be create-only. +ARM PATCH request body properties must be safe for partial updates. A property emitted in an ARM PATCH body must not be required, must not define a default value, and must not be create-only. ## Impact diff --git a/packages/typespec-azure-resource-manager/src/rules/patch-body-invalid-property.ts b/packages/typespec-azure-resource-manager/src/rules/no-unsafe-patch-body-properties.ts similarity index 97% rename from packages/typespec-azure-resource-manager/src/rules/patch-body-invalid-property.ts rename to packages/typespec-azure-resource-manager/src/rules/no-unsafe-patch-body-properties.ts index 0dcc009ee5..0d9279c10e 100644 --- a/packages/typespec-azure-resource-manager/src/rules/patch-body-invalid-property.ts +++ b/packages/typespec-azure-resource-manager/src/rules/no-unsafe-patch-body-properties.ts @@ -25,12 +25,12 @@ import { } from "@typespec/http"; import { resolveProviderNamespace } from "../namespace.js"; -export const patchBodyInvalidPropertyRule = createRule({ - name: "patch-body-invalid-property", - docs: fileRef.fromPackageRoot("src/rules/patch-body-invalid-property.md"), +export const noUnsafePatchBodyPropertiesRule = createRule({ + name: "no-unsafe-patch-body-properties", + docs: fileRef.fromPackageRoot("src/rules/no-unsafe-patch-body-properties.md"), severity: "warning", description: "ARM PATCH body properties must not be required, have defaults, or be create-only.", - url: "https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/patch-body-invalid-property", + url: "https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/no-unsafe-patch-body-properties", messages: { required: paramMessage`Properties of a PATCH request body must not be required, property:${"propertyName"}.`, default: paramMessage`Properties of a PATCH request body must not have default value, property:${"propertyName"}.`, diff --git a/packages/typespec-azure-resource-manager/test/rules/patch-body-invalid-property.test.ts b/packages/typespec-azure-resource-manager/test/rules/no-unsafe-patch-body-properties.test.ts similarity index 97% rename from packages/typespec-azure-resource-manager/test/rules/patch-body-invalid-property.test.ts rename to packages/typespec-azure-resource-manager/test/rules/no-unsafe-patch-body-properties.test.ts index aa3416759c..fea063898f 100644 --- a/packages/typespec-azure-resource-manager/test/rules/patch-body-invalid-property.test.ts +++ b/packages/typespec-azure-resource-manager/test/rules/no-unsafe-patch-body-properties.test.ts @@ -5,9 +5,9 @@ import { createLinterRuleTester, } from "@typespec/compiler/testing"; import { beforeEach, describe, it } from "vitest"; -import { patchBodyInvalidPropertyRule } from "../../src/rules/patch-body-invalid-property.js"; +import { noUnsafePatchBodyPropertiesRule } from "../../src/rules/no-unsafe-patch-body-properties.js"; -const ruleCode = "@azure-tools/typespec-azure-resource-manager/patch-body-invalid-property"; +const ruleCode = "@azure-tools/typespec-azure-resource-manager/no-unsafe-patch-body-properties"; let runner: TesterInstance; let tester: LinterRuleTester; @@ -16,7 +16,7 @@ beforeEach(async () => { runner = await Tester.createInstance(); tester = createLinterRuleTester( runner, - patchBodyInvalidPropertyRule, + noUnsafePatchBodyPropertiesRule, "@azure-tools/typespec-azure-resource-manager", ); }); diff --git a/packages/typespec-azure-rulesets/src/rulesets/resource-manager.ts b/packages/typespec-azure-rulesets/src/rulesets/resource-manager.ts index b8765c32e3..cd7b75ddca 100644 --- a/packages/typespec-azure-rulesets/src/rulesets/resource-manager.ts +++ b/packages/typespec-azure-rulesets/src/rulesets/resource-manager.ts @@ -97,7 +97,7 @@ export default { "@azure-tools/typespec-azure-resource-manager/missing-x-ms-identifiers": true, "@azure-tools/typespec-azure-resource-manager/no-response-body": true, "@azure-tools/typespec-azure-resource-manager/missing-operations-endpoint": true, - "@azure-tools/typespec-azure-resource-manager/patch-body-invalid-property": false, + "@azure-tools/typespec-azure-resource-manager/no-unsafe-patch-body-properties": false, "@azure-tools/typespec-azure-resource-manager/patch-envelope": true, "@azure-tools/typespec-azure-resource-manager/arm-resource-patch": true, "@azure-tools/typespec-azure-resource-manager/resource-name": true, diff --git a/website/src/content/docs/docs/howtos/ARM/arm-rules.md b/website/src/content/docs/docs/howtos/ARM/arm-rules.md index c9ebf1a6e0..5e0c1be249 100644 --- a/website/src/content/docs/docs/howtos/ARM/arm-rules.md +++ b/website/src/content/docs/docs/howtos/ARM/arm-rules.md @@ -129,7 +129,7 @@ The tables below provide guidance to rule authors and ARM reviewers on how to ev | [`no-override-props`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/no-override-props/) | — | **SDK, Tooling.** Violations can crash the breaking-change tool and are unsupported by most languages. | | [`no-resource-delete-operation`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/no-resource-delete-operation/) | [`AllTrackedResourcesMustHaveDelete`](https://github.com/Azure/azure-openapi-validator/blob/main/docs/all-tracked-resources-must-have-delete.md) | **API.** RPC violation. | | [`no-response-body`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/no-response-body/) | — | **API.** A non-empty response, usually for a 202. | -| [`patch-body-invalid-property`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/patch-body-invalid-property/) | [`PatchBodyParametersSchema`](https://github.com/Azure/azure-openapi-validator/blob/main/docs/patch-body-parameters-schema.md) (RPC-Patch-V1-10) | **API.** Required, default-valued, or create-only PATCH properties prevent independent partial updates. | +| [`no-unsafe-patch-body-properties`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/no-unsafe-patch-body-properties/) | [`PatchBodyParametersSchema`](https://github.com/Azure/azure-openapi-validator/blob/main/docs/patch-body-parameters-schema.md) (RPC-Patch-V1-10) | **API.** Required, default-valued, or create-only PATCH properties prevent independent partial updates. | | [`patch-envelope`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/patch-envelope/) | — | **API.** A Patch operation is missing updatable envelope properties. | | [`resource-name`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/resource-name/) | — | **API, SDK.** Invalid characters in a name violate the RPC and create invalid client parameter names, which prevents SDK generation. | | [`secret-prop`](https://azure.github.io/typespec-azure/docs/libraries/azure-resource-manager/rules/secret-prop/) | [`XMSSecretInResponse`](https://github.com/Azure/azure-openapi-validator/blob/main/docs/xms-secret-in-response.md) | **API.** RPC violation. | diff --git a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/linter.md b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/linter.md index 32fe5b2590..f21642d116 100644 --- a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/linter.md +++ b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/linter.md @@ -58,7 +58,7 @@ Available ruleSets: | [`@azure-tools/typespec-azure-resource-manager/missing-x-ms-identifiers`](../rules/missing-x-ms-identifiers.md) | Array properties should describe their identifying properties with x-ms-identifiers. Decorate the property with @OpenAPI.extension("x-ms-identifiers", #[id-prop]) where "id-prop" is a list of the names of identifying properties in the item type. | | [`@azure-tools/typespec-azure-resource-manager/no-response-body`](../rules/no-response-body.md) | Check that the body is empty for 202 and 204 responses, and not empty for other success (2xx) responses. | | [`@azure-tools/typespec-azure-resource-manager/missing-operations-endpoint`](../rules/missing-operations-endpoint.md) | Check for missing Operations interface. | -| [`@azure-tools/typespec-azure-resource-manager/patch-body-invalid-property`](../rules/patch-body-invalid-property.md) | ARM PATCH body properties must not be required, have defaults, or be create-only. | +| [`@azure-tools/typespec-azure-resource-manager/no-unsafe-patch-body-properties`](../rules/no-unsafe-patch-body-properties.md) | ARM PATCH body properties must not be required, have defaults, or be create-only. | | [`@azure-tools/typespec-azure-resource-manager/patch-envelope`](../rules/patch-envelope.md) | Patch envelope properties should match the resource properties. | | [`@azure-tools/typespec-azure-resource-manager/arm-resource-patch`](../rules/arm-resource-patch.md) | Validate ARM PATCH operations. | | [`@azure-tools/typespec-azure-resource-manager/resource-name`](../rules/resource-name.md) | Check the resource name. | From 7f6971c7c81e2e4a29fd03fa52c6f43a4719e67a Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Wed, 2 Sep 2026 13:15:56 +0800 Subject: [PATCH 7/8] test(arm): use recognized PATCH test wording Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 727b6828-4146-484f-a492-7b567658ca41 --- .../test/rules/no-unsafe-patch-body-properties.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typespec-azure-resource-manager/test/rules/no-unsafe-patch-body-properties.test.ts b/packages/typespec-azure-resource-manager/test/rules/no-unsafe-patch-body-properties.test.ts index fea063898f..c8ac622b90 100644 --- a/packages/typespec-azure-resource-manager/test/rules/no-unsafe-patch-body-properties.test.ts +++ b/packages/typespec-azure-resource-manager/test/rules/no-unsafe-patch-body-properties.test.ts @@ -210,7 +210,7 @@ describe("invalid cases", () => { }); }); - it("retargets library property diagnostics to project properties", async () => { + it("reports library property diagnostics on project properties", async () => { await tester .expect( ` From 70527cbc3ca50d0c83e6bd68c2d90b3a58a25100 Mon Sep 17 00:00:00 2001 From: Yuchao Yan Date: Tue, 8 Sep 2026 10:09:41 +0800 Subject: [PATCH 8/8] Fix promotion changelog classification Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- ...ote-patch-body-parameters-schema-2026-08-25-05-25-55.md | 1 - .../register-no-unsafe-patch-body-properties-ruleset.md | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .chronus/changes/register-no-unsafe-patch-body-properties-ruleset.md diff --git a/.chronus/changes/promote-patch-body-parameters-schema-2026-08-25-05-25-55.md b/.chronus/changes/promote-patch-body-parameters-schema-2026-08-25-05-25-55.md index 2578fc6b76..20b6048601 100644 --- a/.chronus/changes/promote-patch-body-parameters-schema-2026-08-25-05-25-55.md +++ b/.chronus/changes/promote-patch-body-parameters-schema-2026-08-25-05-25-55.md @@ -2,7 +2,6 @@ changeKind: feature packages: - "@azure-tools/typespec-azure-resource-manager" - - "@azure-tools/typespec-azure-rulesets" --- Add the `no-unsafe-patch-body-properties` ARM linter rule to report required, default-valued, and create-only properties emitted in PATCH request bodies. diff --git a/.chronus/changes/register-no-unsafe-patch-body-properties-ruleset.md b/.chronus/changes/register-no-unsafe-patch-body-properties-ruleset.md new file mode 100644 index 0000000000..9772186386 --- /dev/null +++ b/.chronus/changes/register-no-unsafe-patch-body-properties-ruleset.md @@ -0,0 +1,7 @@ +--- +changeKind: internal +packages: + - "@azure-tools/typespec-azure-rulesets" +--- + +Register the ARM `no-unsafe-patch-body-properties` lint rule as disabled in the resource manager ruleset.