Přeskočit na obsah

Životní cyklus ticketu

Stav Uložený řetězec Popis
backlog backlog Zachycený, ještě nezařazený do aktivní fronty
open open Zařazený a připravený k práci, ještě nezahájený
inProgress inProgress Aktivně se na něm pracuje
blocked blocked Zahájený, ale zablokovaný něčím vnějším
inReview inReview Práce je hotová a čeká na kontrolu
done done Úspěšně dokončený (koncový)
failed failed Dokončený s chybou (koncový)
cancelled cancelled Opuštěný (koncový)

Hodnoty stavů se ukládají v camelCase (inProgress, inReview), nikdy jako in_progress. Null uložená hodnota se parsuje jako open; neznámá hodnota vyhodí výjimku a na jiný stav se nepřinutí.

tryParseLoose přijímá aliasy dodané agentem a ignoruje velikost písmen i - / _ / mezeru:

Stav Přijímané aliasy
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

Tenhle graf váže jen agenty a automatizaci. TicketWorkflowService.transitionStatus ho kontroluje, když je force false: nástroje MCP a rekoncileři. Nepovolený přechod se tam zapíše do logu a ignoruje. Každá změna stavu, kterou člověk udělá v rozhraní, posílá force: true a graf úplně obejde, takže člověk smí ticket přesunout na jakýkoli stav, který výběr nabízí (všechny stavy kromě failed), včetně znovuotevření koncového. Agenti označí failed přes fail_ticket.

Stav smí vždy přejít sám na sebe. Koncové stavy nemají odchozí hrany.

Z Povolené cíle
backlog open, cancelled
open inProgress, blocked, cancelled
inProgress blocked, inReview, done, failed, cancelled
blocked inProgress, cancelled
inReview inProgress, done, failed, cancelled
done — (koncový)
failed — (koncový)
cancelled — (koncový)

Ukládají se jako celé číslo na nativní stupnici Linear 0–4. Neznámá hodnota se parsuje na none.

Priorita Uloženo jako
none 0
urgent 1
high 2
medium 3
low 4

Neznámá nebo null hodnota se parsuje na local.

Poskytovatel Stav
local Celý uložený v databázi pracovního prostoru; výchozí
linear Vlastní ho vzdálená strana, místně se zrcadlí

Synchronizační adaptéry žijí pod packages/cc_infra/lib/src/tickets/sync/ pro Linear, GitHub Issues, Jira Cloud a ClickUp. GitHub Issues je jen dodavatel synchronizačního adaptéru (vendorId: github), ne hodnota TicketProvider, takže stažený issue z GitHub se uloží s originKind: externalSync a výchozím provider: local. Mapování dodavatele žije na ticket_sync_links.

createTicket zapíše manual. Stažení od dodavatele zapíše externalSync. Delegovaní potomci zůstanou manual.

Hodnota Význam
manual Výchozí u createTicket (řádky člověka, agenta i delegované)
externalSync Nastaví TicketSyncEngine při stažení od dodavatele

Agregát nese na jednom řádku dvě věci. Zrcadlo (poskytovatel, klíče, název, popis, priorita, štítky, stavy, časová razítka) je cache vzdáleného poskytovatele a obnovení ho přepíše celé. Překryv (přiřazení, delegování, prostor, rodič, projekt, propojené PR) patří jen Control Center a obnovení se ho nedotkne.

Pole Typ Popis
id String UUID v4
workspaceId String Vlastnící pracovní prostor
provider TicketProvider Backend, který vlastní kanonická data (výchozí local)
externalKey String? Nativní klíč poskytovatele (např. LIN-123). Místní createTicket sem zapíše samostatné UUID, ne řádkové id
url String? Webová URL u vzdáleného poskytovatele
title String Krátké shrnutí; nesmí být prázdné
description String? Delší tělo v Markdownu
priority TicketPriority Výchozí none
labels List<String> Volné štítky
status TicketStatus Kanonický normalizovaný stav
rawStatus String? Nativní název stavu u vzdáleného poskytovatele, ponechaný kvůli bezztrátovému zobrazení
externalSyncedAt DateTime? Poslední synchronizace napříč dodavateli; null do první. Mapování po dodavatelích žije v ticket_sync_links
parentTicketId String? Rodič ve stromu delegování a rozpadu
projectId String? Vlastnící projekt; na vzdálenou stranu se nikdy neposílá
assignedAgentId String? Id přiřazeného principála (jen metadata: přiřazení nic neodešle)
assigneeType PrincipalType Jaký druh principála assignedAgentId označuje, user nebo agent (výchozí agent)
createdByType PrincipalType? Druh principála, který ticket vytvořil (user nebo agent); null u starších a systémových řádků
createdById String? Id principála, který ticket vytvořil
assignedTeamId String? Přiřazený tým (jen metadata)
delegatedByAgentId String? Agent, který tenhle ticket delegoval
delegationDepth int Hloubka v řetězci delegování; kořen je 0, kontroluje se proti stropu hloubky
delegationRootTicketId String? Kořen řetězce delegování; null u kořenového ticketu
spaceId String? Konverzace, ze které ticket vznikl, nebo prostor delegujícího agenta
errorMessage String? Nastaví se, když je status failed
linkedPrIds List<String> Id uzlů PR, ke kterým je ticket propojený
metadata Map<String, dynamic> Volná mapa
createdAt DateTime Čas vytvoření
startedAt DateTime? Kdy práce začala
blockedAt DateTime? Kdy byl ticket zablokovaný
cancelledAt DateTime? Kdy byl ticket zrušený
completedAt DateTime? Kdy se ticket úspěšně dokončil
finishedAt DateTime? Kdy ticket dosáhl libovolného koncového stavu
updatedAt DateTime Poslední mutace (obnovení zrcadla, nebo změna překryvu)
version int Čítač optimistické souběžnosti; při každé mutaci se zvýší
originKind TicketOriginKind Jak ticket vznikl (výchozí manual)
collaborators List<TicketCollaborator> Repozitář je doplní na vyžádání

Odvozené: isTerminal, isRemote (provider != local), isAssignedToUser a displayKey (externalKey ?? id: místní tickety proto ukazují samostatné UUID z okamžiku založení, ne řádkové id).

Uložené hrany jsou orientované řádky v ticket_links:

Typ Uložený řetězec Význam
blocks blocks Zdroj blokuje cíl
relatesTo relates_to Symetrická vazba
duplicateOf duplicate_of Zdroj je duplikát cíle

Slovník, kterým mluví rozhraní a ticket_relation, je TicketRelationKind. Odvozuje se z uložené hrany a z toho, na kterém konci subjekt sedí:

Druh Odvozeno z
blockedBy Řádek blocks, kde je subjekt cíl
blocking Řádek blocks, kde je subjekt zdroj
relatedTo Řádek relates_to (symetrický)
duplicateOf Řádek duplicate_of, kde je subjekt zdroj
duplicatedBy Řádek duplicate_of, kde je subjekt cíl
subIssueOf tickets.parent_ticket_id na subjektu
parentOf tickets.parent_ticket_id na druhém ticketu

Vazby rodiče a podticketu nejsou řádky ticket_links. Žijí na tickets.parent_ticket_id.

Těch osm tříd žije v packages/cc_domain/lib/core/domain/events/ticketing_events.dart. TicketStarted, TicketDelegated, TicketCollaboratorAdded ani ExternalTicketWebhookReceived neexistují.

Událost Kdy se publikuje
TicketCreated Ticket se vytvoří
TicketAssigned Ticket se přiřadí principálovi nebo týmu
TicketCompleted Práce úspěšně skončí
TicketFailed Práce selže
TicketCancelled Ticket se zruší
TicketStatusChanged Jakákoli změna stavu
TicketReassigned Ticket se přeřadí
TicketDetailsUpdated Změní se název, popis, priorita nebo štítky

TicketAssigned je signál pro audit a oznámení a zároveň spouštěč pipeline. Přiřazení agentovi nespustí žádný běh: žádný dispečer ticketů nesleduje assignedAgentId. Výjimka je tým: TeamRoutingService odešle vedoucího týmu. Přidání spolupracovníka zapíše řádek a nic nepublikuje. Webhook dodavatele se uplatní přes TicketSyncEngine a doménová událost u toho nevznikne.

TicketWorkflowService vlastní každou mutaci.

Mechanismus Detail
Jediný průchod Všechny mutace jdou přes _mutate, které načte řádek a před uplatněním zavolá _assertWorkspace
Izolace pracovního prostoru _assertWorkspace vyhodí WorkspaceMismatchException, když workspaceId načteného řádku nesedí s volajícím
Optimistická souběžnost _mutate zapisuje s expectedVersion nastaveným na version, kterou právě přečetlo, a při ConcurrencyConflictException to zkusí znovu po načtení čerstvého stavu
Výhradní převzetí tryCheckout / releaseCheckout; druhý agent, který si nárokuje už převzatý ticket, dostane CheckoutConflictException
Pojistka přechodu canTransitionTo, uplatní se jen když je force false