Transitions
Statuswechsel laufen über eine generische, schema-übergreifende API. Ein
Aufruf gegen /api/v1/<resource>/<id>/transitions/... wertet den
Workflow der Entity aus, prüft Guards, fragt
ggf. einen Dialog an, legt ggf. einen Approval-Request an, und schreibt am
Ende den neuen Status atomar mitsamt Audit-Eintrag.
Hintergrund und Begriffe stehen unter Status-Maschine & Transitions.
Resources
| Methode | Pfad | Zweck |
|---|---|---|
GET | /api/v1/<resource>/<id>/transitions | Erlaubte Transitions ab aktuellem Status |
POST | /api/v1/<resource>/<id>/transitions/<transitionKey> | Transition ausführen (empfohlen) |
POST | /api/v1/<resource>/<id>/transition | Legacy-Form mit toStatus im Body |
<resource> ist die Plural-Collection (z. B. tickets), <transitionKey>
ist der Zielstatus oder der Edge-Key des Custom-Workflows.
Verfügbare Transitions abfragen
curl "https://os.codemeta.de/api/v1/tickets/<id>/transitions" \
-H "X-Tenant-Id: $TENANT" \
-b cookies.txt
Antwort:
{
"data": [
{
"key": "IN_PROGRESS",
"label": "In Bearbeitung",
"from": "OPEN",
"to": "IN_PROGRESS",
"guards": [{ "type": "requiredFields", "ok": true }],
"available": true
},
{
"key": "RESOLVED",
"label": "Gelöst",
"from": "OPEN",
"to": "RESOLVED",
"guards": [{ "type": "requireComment", "ok": false, "reason": "Kein Kommentar" }],
"available": false
}
]
}
Die Engine evaluiert Guards bereits gegen den aktuellen Entity-State (inkl. Linked-Checklists, Pending-Approvals, Comment-Count, Time-Entries, Sub-Tasks). UIs nutzen das, um nicht-verfügbare Buttons zu deaktivieren.
Status-Field-Override
Manche Entities (z. B. zukünftige Multi-Status-Workflows) tragen mehrere
Statusfelder. ?statusField=<name> wählt das Feld, das bewertet werden soll.
Default: status.
Transition ausführen
curl -X POST "https://os.codemeta.de/api/v1/tickets/<id>/transitions/RESOLVED" \
-H "X-Tenant-Id: $TENANT" \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{}'
Optionaler Body
{
"comment": "Behoben durch Patch X.Y",
"fields": { "resolution": "fixed" },
"dialogResponse": { "closeReason": "duplicate" }
}
| Feld | Bedeutung |
|---|---|
comment | Optionaler Begleit-Kommentar (wird in die Audit-Historie gehängt) |
fields | Zusätzliche Feld-Patches, die mit dem Statuswrite zusammen laufen |
dialogResponse | Antwort auf einen Dialog-Guard (siehe unten) |
Erfolg
{
"data": { "_id": "0192f2c0-…", "status": "RESOLVED", "version": 8, … },
"transition": {
"key": "RESOLVED",
"fromState": "OPEN",
"toState": "RESOLVED",
"executedActions": []
}
}
Die Antwort ist die volle Entity nach dem Statuswrite. SSE-Listener
(/api/v1/<resource>/<id>/stream) bekommen ein entity-updated mit
payload.action: 'transitioned' und from/to.
Dialog-Gates — 428 Precondition Required
Verlangt ein Guard ein Dialog (confirmDialog, inputDialog, formDialog,
signatureDialog), antwortet der Server 428:
HTTP/1.1 428 Precondition Required
{
"type": "dialog_required",
"title": "Dialog Required",
"status": 428,
"detail": "This transition requires user interaction before proceeding.",
"dialogConfig": {
"type": "input",
"title": "Closing reason",
"inputLabel": "Reason",
"inputRequired": true,
"targetField": "closeReason"
},
"guardType": "inputDialog"
}
Der Client rendert den Dialog, erhebt die Antwort und ruft die Transition
erneut auf — diesmal mit dialogResponse im Body. Der Server schreibt
den Wert automatisch in das targetField; eine doppelte Übergabe in
fields ist nicht nötig.
Approval-Gates — 202 Accepted
Ein requireApproval-Guard öffnet einen Approval-Request:
HTTP/1.1 202 Accepted
{
"type": "approval_required",
"title": "Approval Required",
"status": 202,
"detail": "Transition requires approval. A request has been sent.",
"approvalRequestId": "0192f2c0-…",
"approverType": "role",
"approverValue": "manager"
}
Der Status wechselt noch nicht. Der Approver bestätigt über die
Plattform-UI bzw. den Magic-Link — erst dann zieht die Transition durch
(oder zieht bei Ablehnung in rejectionTargetStatus, sofern konfiguriert).
Guard-Failures — 422 Unprocessable Entity
Schlägt ein Guard fehl, der nicht durch Dialog/Approval auflösbar ist:
HTTP/1.1 422 Unprocessable Entity
{
"type": "https://docs.codemeta.os/errors/transition-guard-failed",
"title": "Transition Not Allowed",
"status": 422,
"detail": "The transition 'RESOLVED' cannot be executed in the current state.",
"errorCode": "REQUIRE_COMMENT",
"failedGuards": [
{ "type": "FIELD_REQUIRED", "message": "Mindestens ein Kommentar erforderlich" }
]
}
Mappen Sie Ihren Client auf errorCode (stabil), nicht auf detail.
Hook-Failures — 422 mit transition_hook_failed
Async-Hooks (sendEmail, sendNotification, webhook) laufen vor dem
Statuswrite. Schlägt einer fehl, wird die Transition abgebrochen:
HTTP/1.1 422 Unprocessable Entity
{
"type": "transition_hook_failed",
"title": "Transition Hook Failed",
"status": 422,
"detail": "SMTP connection refused",
"errorCode": "EMAIL_SEND_FAILED",
"instance": "/tickets/<id>/transitions/RESOLVED"
}
Das Entity bleibt im alten Status — kein Halb-Schreib-Zustand.
Legacy-Form mit toStatus
curl -X POST "https://os.codemeta.de/api/v1/tickets/<id>/transition" \
-H "X-Tenant-Id: $TENANT" \
-H "Content-Type: application/json" \
-b cookies.txt \
-d '{"toStatus": "IN_PROGRESS"}'
Funktional identisch zur /transitions/<key>-Variante; bleibt aus
Kompatibilitätsgründen erhalten. Bevorzugen Sie die Pfad-Variante.
Audit & SSE
- Jede erfolgreiche Transition erzeugt einen Eintrag in der
Audit-Historie mit
from→to. - Detail-Streams (
GET /api/v1/<resource>/<id>/stream) liefernentity-updated-Events mitpayload.action: 'transitioned'.
Verwandt
- Status-Maschine & Transitions — Hintergrund
- Konventionen — generische CRUD-Verträge
- Workflows — der Layer darüber (Trigger + Steps)
- Audit-Historie