Ticket-Lebenszyklus
Ticket-Status
Abschnitt betitelt „Ticket-Status“| Status | Speicherzeichenfolge | Beschreibung |
|---|---|---|
backlog |
backlog |
Erfasst, noch nicht in die aktive Warteschlange triagiert |
open |
open |
Triagiert und bereit zur Bearbeitung, noch nicht begonnen |
inProgress |
inProgress |
Wird gerade bearbeitet |
blocked |
blocked |
Begonnen, aber durch etwas Externes blockiert |
inReview |
inReview |
Arbeit erledigt, wartet auf Review |
done |
done |
Erfolgreich abgeschlossen (Endzustand) |
failed |
failed |
Mit einem Fehler beendet (Endzustand) |
cancelled |
cancelled |
Aufgegeben (Endzustand) |
Statuswerte werden als camelCase gespeichert (inProgress, inReview), nie als in_progress. Ein als null gespeicherter Wert wird als open geparst; ein unbekannter Wert wirft eine Ausnahme, statt still angepasst zu werden.
tryParseLoose akzeptiert Aliasse, die ein Agent liefert, und ignoriert dabei Groß- und Kleinschreibung sowie - / _ / Leerzeichen:
| Status | Akzeptierte Aliasse |
|---|---|
open |
todo, ready, pending, new |
inProgress |
doing, started, active, wip |
blocked |
stuck |
inReview |
review, reviewing |
done |
closed, complete, completed, finished |
failed |
error, errored |
cancelled |
canceled, abandoned, dropped |
Statusübergänge
Abschnitt betitelt „Statusübergänge“backlog ──→ open ──→ inProgress ──→ inReview ──→ done │ │ │ │ └──→ failed ┘ └──→ blocked ──→ inProgress
Any non-terminal status ──→ cancelled; inReview ──→ failedDieser Graph bindet nur Agenten und Automatisierung. TicketWorkflowService.transitionStatus prüft ihn, wenn force falsch ist — die MCP-Tools und die Reconciler. Ein unerlaubter Übergang wird dort protokolliert und ignoriert. Jede Statusänderung aus der Oberfläche übergibt force: true und umgeht den Graphen vollständig, sodass ein Mensch ein Ticket auf jeden Status setzen darf, den die Auswahl anbietet (jeden Status außer failed), und dabei auch einen Endzustand wieder öffnen darf. Agenten setzen failed mit fail_ticket.
Ein Status darf immer in sich selbst übergehen. Endzustände haben keine ausgehenden Kanten.
| Von | Erlaubte Ziele |
|---|---|
backlog |
open, cancelled |
open |
inProgress, blocked, cancelled |
inProgress |
blocked, inReview, done, failed, cancelled |
blocked |
inProgress, cancelled |
inReview |
inProgress, done, failed, cancelled |
done |
— (Endzustand) |
failed |
— (Endzustand) |
cancelled |
— (Endzustand) |
Ticket-Prioritäten
Abschnitt betitelt „Ticket-Prioritäten“Gespeichert als Ganzzahl auf der nativen Skala 0–4 von Linear. Ein unbekannter Wert wird zu none geparst.
| Priorität | Gespeichert als |
|---|---|
none |
0 |
urgent |
1 |
high |
2 |
medium |
3 |
low |
4 |
Ticket-Anbieter
Abschnitt betitelt „Ticket-Anbieter“Ein unbekannter oder als null gespeicherter Wert wird zu local geparst.
| Anbieter | Status |
|---|---|
local |
Liegt vollständig in der Datenbank des Arbeitsbereichs; der Standard |
linear |
Gehört der Gegenstelle, lokal gespiegelt |
Die Sync-Adapter für Linear, GitHub Issues, Jira Cloud und ClickUp liegen unter
packages/cc_infra/lib/src/tickets/sync/. GitHub Issues ist nur ein Anbieter
des Sync-Adapters (vendorId: github), kein Wert von TicketProvider. Ein
abgerufenes GitHub-Issue wird deshalb mit originKind: externalSync und dem
Standard provider: local gespeichert. Die Anbieterzuordnung liegt auf
ticket_sync_links.
Ticket-Herkunft
Abschnitt betitelt „Ticket-Herkunft“createTicket schreibt manual. Ein Abruf vom Anbieter schreibt externalSync. Delegierte
Untertickets bleiben manual.
| Wert | Bedeutung |
|---|---|
manual |
Standard von createTicket (Zeilen von Mensch, Agent und Delegation) |
externalSync |
Von TicketSyncEngine bei einem Abruf vom Anbieter gesetzt |
Ticket-Felder
Abschnitt betitelt „Ticket-Felder“Das Aggregat umfasst zwei Belange in einer Zeile. Der Spiegel (Anbieter, Schlüssel, Titel, Beschreibung, Priorität, Labels, Status, Zeitstempel) ist der Cache eines entfernten Anbieters und wird bei einer Aktualisierung vollständig neu geschrieben. Die Überlagerung (Zuweisung, Delegation, Bereich, Eltern-Ticket, Projekt, verknüpfte PRs) gehört nur Control Center, und eine Aktualisierung fasst sie nie an.
| Feld | Typ | Beschreibung |
|---|---|---|
id |
String |
UUID v4 |
workspaceId |
String |
Arbeitsbereich, dem das Ticket gehört |
provider |
TicketProvider |
Backend, dem die kanonischen Daten gehören (Standard local) |
externalKey |
String? |
Anbietereigener Schlüssel (z. B. LIN-123). Lokales createTicket schreibt hier eine gesonderte UUID, nicht die id der Zeile |
url |
String? |
Web-URL beim entfernten Anbieter |
title |
String |
Kurze Zusammenfassung; darf nicht leer sein |
description |
String? |
Längerer Markdown-Text |
priority |
TicketPriority |
Standard none |
labels |
List<String> |
Freie Labels |
status |
TicketStatus |
Kanonischer, normalisierter Status |
rawStatus |
String? |
Nativer Statusname des entfernten Anbieters, behalten für eine verlustfreie Anzeige |
externalSyncedAt |
DateTime? |
Letzte anbieterübergreifende Synchronisierung; null bis zur ersten. Die Zuordnung je Anbieter liegt in ticket_sync_links |
parentTicketId |
String? |
Eltern-Ticket im Delegations- bzw. Aufteilungsbaum |
projectId |
String? |
Zugehöriges Projekt; wird nie an einen entfernten Anbieter übertragen |
assignedAgentId |
String? |
ID des zugewiesenen Prinzipals (nur Metadaten — die Zuweisung startet nichts) |
assigneeType |
PrincipalType |
Welche Art Prinzipal assignedAgentId benennt — user oder agent (Standard agent) |
createdByType |
PrincipalType? |
Art des Prinzipals, der das Ticket angelegt hat (user oder agent); null bei Bestands- und Systemzeilen |
createdById |
String? |
ID des anlegenden Prinzipals |
assignedTeamId |
String? |
Zugewiesenes Team (nur Metadaten) |
delegatedByAgentId |
String? |
Agent, der dieses Ticket delegiert hat |
delegationDepth |
int |
Tiefe in der Delegationskette; die Wurzel ist 0, geprüft gegen die Tiefenobergrenze |
delegationRootTicketId |
String? |
Wurzel der Delegationskette; null bei einem Wurzel-Ticket |
spaceId |
String? |
Unterhaltung, aus der das Ticket hervorgegangen ist, oder der Bereich des delegierenden Agenten |
errorMessage |
String? |
Gesetzt, wenn status failed ist |
linkedPrIds |
List<String> |
IDs der PR-Knoten, mit denen dieses Ticket verknüpft ist |
metadata |
Map<String, dynamic> |
Freie Sammlung |
createdAt |
DateTime |
Zeitpunkt der Erstellung |
startedAt |
DateTime? |
Wann die Arbeit begonnen hat |
blockedAt |
DateTime? |
Wann das Ticket blockiert wurde |
cancelledAt |
DateTime? |
Wann das Ticket abgebrochen wurde |
completedAt |
DateTime? |
Wann das Ticket erfolgreich abgeschlossen wurde |
finishedAt |
DateTime? |
Wann das Ticket einen beliebigen Endzustand erreicht hat |
updatedAt |
DateTime |
Letzte Mutation (Aktualisierung des Spiegels oder Änderung der Überlagerung) |
version |
int |
Zähler für optimistische Nebenläufigkeit, bei jeder Mutation erhöht |
originKind |
TicketOriginKind |
Wie das Ticket entstanden ist (Standard manual) |
collaborators |
List<TicketCollaborator> |
Vom Repository bei Bedarf nachgeladen |
Abgeleitet: isTerminal, isRemote (provider != local), isAssignedToUser und displayKey (externalKey ?? id — lokale Tickets zeigen deshalb die gesonderte UUID vom Anlegen, nicht die id der Zeile).
Ticket-Beziehungen
Abschnitt betitelt „Ticket-Beziehungen“Gespeicherte Kanten sind gerichtete Zeilen in ticket_links:
| Typ | Speicherzeichenfolge | Bedeutung |
|---|---|---|
blocks |
blocks |
Die Quelle blockiert das Ziel |
relatesTo |
relates_to |
Symmetrische Beziehung |
duplicateOf |
duplicate_of |
Die Quelle ist ein Duplikat des Ziels |
Das Vokabular, das die Oberfläche und ticket_relation sprechen, ist TicketRelationKind, abgeleitet aus einer gespeicherten Kante und dem Endpunkt, an dem das Subjekt sitzt:
| Art | Abgeleitet aus |
|---|---|
blockedBy |
Eine blocks-Zeile, in der das Subjekt das Ziel ist |
blocking |
Eine blocks-Zeile, in der das Subjekt die Quelle ist |
relatedTo |
Eine relates_to-Zeile (symmetrisch) |
duplicateOf |
Eine duplicate_of-Zeile, in der das Subjekt die Quelle ist |
duplicatedBy |
Eine duplicate_of-Zeile, in der das Subjekt das Ziel ist |
subIssueOf |
tickets.parent_ticket_id am Subjekt |
parentOf |
tickets.parent_ticket_id am anderen Ticket |
Eltern- und Unterticket-Verknüpfungen sind keine Zeilen in ticket_links — sie liegen auf tickets.parent_ticket_id.
Domain-Ereignisse
Abschnitt betitelt „Domain-Ereignisse“Diese acht Klassen liegen in packages/cc_domain/lib/core/domain/events/ticketing_events.dart. Es gibt kein TicketStarted, kein TicketDelegated, kein TicketCollaboratorAdded und kein ExternalTicketWebhookReceived.
| Ereignis | Auslöser |
|---|---|
TicketCreated |
Ein Ticket wird angelegt |
TicketAssigned |
Ticket einem Prinzipal oder einem Team zugewiesen |
TicketCompleted |
Die Arbeit endet erfolgreich |
TicketFailed |
Die Arbeit schlägt fehl |
TicketCancelled |
Das Ticket wird abgebrochen |
TicketStatusChanged |
Jede Statusänderung |
TicketReassigned |
Ticket neu zugewiesen |
TicketDetailsUpdated |
Titel, Beschreibung, Priorität oder Labels ändern sich |
TicketAssigned ist ein Audit- und Benachrichtigungssignal und ein Pipeline-Trigger. Die Zuweisung an einen Agenten startet keinen Lauf — es gibt keinen Ticket-Dispatcher, der assignedAgentId beobachtet. Die Ausnahme ist die Zuweisung an ein Team: TeamRoutingService startet den Teamleiter. Das Hinzufügen eines Mitwirkenden schreibt eine Zeile und veröffentlicht nichts. Ein Webhook des Anbieters wird über TicketSyncEngine angewendet, ohne ein Domain-Ereignis.
Mutations-Engpass
Abschnitt betitelt „Mutations-Engpass“TicketWorkflowService besitzt jede Mutation.
| Mechanismus | Detail |
|---|---|
| Ein einziger Engpass | Alle Mutationen laufen über _mutate, das die Zeile lädt und vor dem Anwenden _assertWorkspace aufruft |
| Isolation des Arbeitsbereichs | _assertWorkspace wirft WorkspaceMismatchException, wenn die workspaceId der geladenen Zeile nicht der des Aufrufers entspricht |
| Optimistische Nebenläufigkeit | _mutate schreibt mit expectedVersion auf die gerade gelesene version und wiederholt den Versuch bei ConcurrencyConflictException, indem es den aktuellen Zustand neu liest |
| Exklusiver Anspruch | tryCheckout / releaseCheckout; ein zweiter Agent, der ein bereits beanspruchtes Ticket beansprucht, erhält eine CheckoutConflictException |
| Übergangsprüfung | canTransitionTo, angewendet nur, wenn force falsch ist |