parlov docs

Forcing 409 State-Transition Violation

Many resources have a state machine, and requesting an invalid transition produces 409 because the operation conflicts with the resource's current state.

Implemented

Mechanism: Many resources have a state machine (e.g., draft → published → archived). Requesting an invalid transition produces 409 because the operation conflicts with the resource's current state. The server must load the resource to read its current state.

Isolated Variable: The request body contains a single state-change field set to a value that would be invalid given the resource's current state.

Oracle Signal: 409 (exists, state machine rejected transition) vs 404 (does not exist).

PATCH — Existing Resource (Invalid Transition)

PATCH /api/orders/order-1001 HTTP/1.1
Host: target.com
Content-Type: application/json
Content-Length: 22

{"status": "pending"}

HTTP/1.1 409 Conflict
Content-Type: application/json

{"error": "Conflict", "detail": "Cannot transition from 'delivered' to 'pending'"}

PATCH — Non-Existing Resource (Invalid Transition)

PATCH /api/orders/order-9999 HTTP/1.1
Host: target.com
Content-Type: application/json
Content-Length: 22

{"status": "pending"}

HTTP/1.1 404 Not Found
Content-Type: application/json

{"error": "Not Found"}

💡 Blind transition probing: You don't need to know the resource's actual current state. Guessing a backwards transition (e.g., "status": "draft") has a reasonable chance of triggering 409. The error message often leaks the current state (e.g., "Cannot transition from 'delivered' to 'draft'"), providing a secondary oracle.

💡 409 vs 422 ambiguity: Some servers return 422 instead of 409 for invalid state transitions. The oracle logic is identical — the differential is 422/409 vs 404.

Mitigation: Return 404 for non-existent resources before evaluating state-machine transitions.