Zum Inhalt springen

Ticket-Lebenszyklus

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
backlog ──→ open ──→ inProgress ──→ inReview ──→ done
│ │ │
│ └──→ failed ┘
└──→ blocked ──→ inProgress
Any non-terminal status ──→ cancelled; inReview ──→ failed

Dieser 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)

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

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.

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

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).

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.

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.

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