parlov docs

Forcing 409 State-Transition Violation

Resources with a state machine reject invalid transitions with 409, and the server must load the resource to read its current state.

Implemented

Mechanism: Resources with a state machine (e.g., draft → published → archived) reject invalid transitions with 409. The server must load the resource to read its current state.

Isolated Variable: The request body contains a single state-change field set to an invalid transition value.

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

PUT — Existing Resource (Re-publish Archived)

PUT /api/articles/art-500 HTTP/1.1
Host: target.com
Content-Type: application/json
Content-Length: 64

{"title": "Test Article", "status": "published"}

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

{"error": "Conflict", "detail": "Cannot publish an archived article"}

PUT — Non-Existing Resource (Re-publish Archived)

PUT /api/articles/art-999 HTTP/1.1
Host: target.com
Content-Type: application/json
Content-Length: 64

{"title": "Test Article", "status": "published"}

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

{"error": "Not Found"}

💡 Blind transition probing: Guessing a backwards transition (e.g., "status": "draft") has a reasonable chance of triggering 409. The error message often leaks the current state.

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

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