Skip to content

Update PostResponseCodes: enforce 202+default-only for LRO POST with mandatory x-ms-long-running-operation-options#841

Draft
Copilot wants to merge 2 commits intomainfrom
copilot/update-post-response-codes
Draft

Update PostResponseCodes: enforce 202+default-only for LRO POST with mandatory x-ms-long-running-operation-options#841
Copilot wants to merge 2 commits intomainfrom
copilot/update-post-response-codes

Conversation

Copy link
Contributor

Copilot AI commented Mar 6, 2026

LRO POST operations previously allowed 200 or 204 as direct response codes alongside 202. The new requirement mandates only 202 + default on the operation itself, with final-state conveyed exclusively via x-ms-long-running-operation-options.final-state-via.

Rule changes (post-response-codes.ts)

  • LRO valid response set narrowed from ["202","200","default"] / ["202","204","default"] to ["202","default"] only
  • New mandatory check: x-ms-long-running-operation-options must be present with final-state-via set to "location" or "azure-async-operation"
  • Removed LR_NO_SCHEMA_ERROR_OK check (200-without-schema) — 200 is no longer a valid LRO response code
  • Moved LR_SCHEMA_ERROR_NO_CONTENT (204-with-schema) into the sync branch only; 204 on async is already caught by the response-code check
  • Updated error messages and EmptyResponse_ERROR to reflect new requirements

Valid LRO POST pattern:

responses:
  "202":
    description: Accepted
  default:
    description: Error
x-ms-long-running-operation: true
x-ms-long-running-operation-options:
  final-state-via: location   # or azure-async-operation

Test changes (post-response-codes.test.ts)

  • Updated 7 existing tests: two previously-passing tests (202+200+default, 202+204+default) now expect errors; five error-case tests updated for new error counts/messages (adding LR_MISSING_OPTIONS_ERROR where applicable)
  • Replaced LR_NO_SCHEMA_ERROR_OK references with LR_ERROR + LR_MISSING_OPTIONS_ERROR
  • Added 6 new tests covering: valid location / azure-async-operation options, missing options, options with no final-state-via, and 200/204 present in LRO responses
Original prompt

Summary

The PostResponseCodes rule and its tests need to be updated to reflect new requirements for POST operation response codes.

New Requirements

Sync POST (no x-ms-long-running-operation: true)

  • No change: must have 200 + default (when body present) or 204 + default (when no body). No other codes allowed.

Async POST (x-ms-long-running-operation: true)

  • Changed: The valid response codes are now only 202 + default. The 200 and 204 final-state responses must NOT be present as explicit response codes on the operation itself.
  • The final-state response is conveyed via the x-ms-long-running-operation-options extension with "final-state-via" property (values: "location" or "azure-async-operation"). This extension is mandatory for all LRO POST operations.
  • When a body is expected in the final response → x-ms-long-running-operation-options must be present (with final-state-via set to "location" or "azure-async-operation")
  • When no body is expected in the final response → x-ms-long-running-operation-options must still be present

In short: x-ms-long-running-operation-options with final-state-via is mandatory for all LRO POST operations.

Files to Modify

1. packages/rulesets/src/spectral/functions/post-response-codes.ts

Current file (full content for reference):

// Synchronous POST must have 200 & 204 return codes and LRO POST must have 202 & 200 or 202 & 204 or 202, 200 & 204 return codes.

const SYNC_POST_RESPONSES_OK = ["200", "default"]
const SYNC_POST_RESPONSES_NO_CONTENT = ["204", "default"]
const LR_POST_RESPONSES_OK = ["202", "200", "default"]
const LR_POST_RESPONSES_NO_CONTENT = ["202", "204", "default"]
const HTTP_STATUS_CODE_OK = "200"
const HTTP_STATUS_CODE_ACCEPTED = "202"
const HTTP_STATUS_CODE_NO_CONTENT = "204"

const SYNC_ERROR =
  "Synchronous POST operations must have one of the following combinations of responses - 200 and default ; 204 and default. No other response codes are permitted."
const LR_ERROR =
  "Long-running POST operations must initially return 202 with a default response and no schema. The final response must be 200 with a schema if one is required, or 204 with no schema if not. No other response codes are permitted."
const LR_NO_SCHEMA_ERROR_OK =
  "200 return code does not have a schema specified. LRO POST must have a 200 return code if only if the final response is intended to have a schema, if not the 200 return code must not be specified."
const LR_SCHEMA_ERROR_ACCEPTED = "202 response for a LRO POST operation must not have a response schema specified."
const LR_SCHEMA_ERROR_NO_CONTENT = "204 response for a Sync/LRO POST operation must not have a response schema specified."
const EmptyResponse_ERROR =
  "POST operation response codes must be non-empty. Synchronous POST operation must have response codes 200 and default or 204 and default. LRO POST operations must have response codes 202 and default. They must also have a 200 return code if only if the final response is intended to have a schema, if not the 200 return code must not be specified."

export const PostResponseCodes = (postOp: any, _opts: any, ctx: any) => {
  if (postOp === null || typeof postOp !== "object") {
    return []
  }
  const path = ctx.path
  const errors = []

  const responses = Object.keys(postOp?.responses ?? {})

  if (responses.length == 0) {
    errors.push({
      message: EmptyResponse_ERROR,
      path: path,
    })
    return errors
  }

  const isAsyncOperation =
    postOp.responses[HTTP_STATUS_CODE_ACCEPTED] ||
    (postOp["x-ms-long-running-operation"] && postOp["x-ms-long-running-operation"] === true) ||
    postOp["x-ms-long-running-operation-options"]

  if (isAsyncOperation) {
    if (!postOp["x-ms-long-running-operation"] || postOp["x-ms-long-running-operation"] !== true) {
      errors.push({
        message: "An async POST operation must set '\"x-ms-long-running-operation\" : true'.",
        path: path,
      })
      return errors
    }

    const respSet = new Set(responses)
    const setEquals = (target: string[]) => target.length === respSet.size && target.every((v) => respSet.has(v))
    const matchesOk = setEquals(LR_POST_RESPONSES_OK)
    const matchesNoContent = setEquals(LR_POST_RESPONSES_NO_CONTENT)

    if (!(matchesOk || matchesNoContent)) {
      errors.push({
        message: LR_ERROR,
        path: path,
      })
    }
  } else {
    const responseSet = new Set(responses)
    const setEquals = (target: string[]) => target.length === responseSet.size && target.every((v) => responseSet.has(v))

    const matchesOk = setEquals(SYNC_POST_RESPONSES_OK)
    const matchesNoContent = setEquals(SYNC_POST_RESPONSES_NO_CONTENT)

    if (!(matchesOk || matchesNoContent)) {
      errors.push({
        message: SYNC_ERROR,
        path: path,
      })
    }
  }

  if (postOp.responses[HTTP_STATUS_CODE_OK] && !postOp.responses[HTTP_STATUS_CODE_OK]?.schema) {
 ...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

*This pull request was created from Copilot chat.*
>

<!-- START COPILOT CODING AGENT TIPS -->
---

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs.

…th x-ms-long-running-operation-options

Co-authored-by: raosuhas <11067581+raosuhas@users.noreply.github.com>
Copilot AI changed the title [WIP] Update PostResponseCodes rule for new requirements Update PostResponseCodes: enforce 202+default-only for LRO POST with mandatory x-ms-long-running-operation-options Mar 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants