Sari la conținut

Ciclul de viață al tichetelor

Stare Șir stocat Descriere
backlog backlog Înregistrat, încă netriat în coada activă
open open Triat și pregătit de lucru, dar neînceput
inProgress inProgress Se lucrează efectiv la el
blocked blocked Început, dar blocat de ceva extern
inReview inReview Munca este gata, în așteptarea revizuirii
done done Finalizat cu succes (stare finală)
failed failed Finalizat cu o eroare (stare finală)
cancelled cancelled Abandonat (stare finală)

Valorile de stare sunt stocate în camelCase (inProgress, inReview), niciodată in_progress. O valoare stocată null este interpretată ca open; o valoare necunoscută aruncă o excepție în loc să fie convertită.

tryParseLoose acceptă aliasuri furnizate de agent, ignorând majusculele și caracterele - / _ / spațiu:

Stare Aliasuri acceptate
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

Acest graf îi leagă doar pe agenți și pe automatizări. TicketWorkflowService.transitionStatus îl verifică atunci când force este false — instrumentele MCP și reconciliatorii. O tranziție ilegală acolo este înregistrată în jurnal și ignorată. Orice schimbare de stare făcută de utilizator din interfață transmite force: true și ocolește complet graful, așa că un om poate muta un tichet în orice stare listată de selector (orice stare în afară de failed), inclusiv redeschiderea uneia finale. Agenții marchează failed cu fail_ticket.

O stare poate trece oricând în ea însăși. Stările finale nu au muchii de ieșire.

Din Ținte permise
backlog open, cancelled
open inProgress, blocked, cancelled
inProgress blocked, inReview, done, failed, cancelled
blocked inProgress, cancelled
inReview inProgress, done, failed, cancelled
done — (finală)
failed — (finală)
cancelled — (finală)

Stocată ca întreg pe scara nativă 0–4 a Linear. O valoare necunoscută este interpretată ca none.

Prioritate Stocat ca
none 0
urgent 1
high 2
medium 3
low 4

O valoare stocată necunoscută sau null este interpretată ca local.

Furnizor Stare
local Stocat în întregime în baza de date a spațiului de lucru; valoarea implicită
linear Deținut la distanță, oglindit local

Adaptoarele de sincronizare se află în packages/cc_infra/lib/src/tickets/sync/ pentru Linear, GitHub Issues, Jira Cloud și ClickUp. GitHub Issues este doar un furnizor de adaptor de sincronizare (vendorId: github) — nu este o valoare TicketProvider, așa că un issue GitHub preluat este stocat cu originKind: externalSync și cu provider: local implicit. Maparea furnizorului se află în ticket_sync_links.

createTicket scrie manual. O preluare de la un furnizor scrie externalSync. Copiii delegați rămân manual.

Valoare Semnificație
manual Implicit la createTicket (rânduri umane, ale agenților și cele delegate)
externalSync Setat de TicketSyncEngine la o preluare de la un furnizor

Agregatul acoperă două preocupări pe un singur rând. Oglinda (furnizor, chei, titlu, descriere, prioritate, etichete, stări, marcaje temporale) este un cache pentru un furnizor de la distanță și este rescrisă în întregime de o reîmprospătare. Suprapunerea (atribuire, delegare, spațiu, părinte, proiect, PR-uri legate) există doar în Control Center și o reîmprospătare nu o atinge niciodată.

Câmp Tip Descriere
id String UUID v4
workspaceId String Spațiul de lucru proprietar
provider TicketProvider Backend-ul care deține datele canonice (implicit local)
externalKey String? Cheie nativă a furnizorului (de ex. LIN-123). createTicket local scrie aici un UUID separat, nu id-ul rândului
url String? URL web la furnizorul de la distanță
title String Rezumat scurt; nu poate fi gol
description String? Conținut Markdown mai lung
priority TicketPriority Implicit none
labels List<String> Etichete libere
status TicketStatus Starea normalizată canonică
rawStatus String? Numele stării native al furnizorului de la distanță, păstrat pentru afișare fără pierderi
externalSyncedAt DateTime? Ultima sincronizare multi-furnizor; null până la prima. Maparea per furnizor se află în ticket_sync_links
parentTicketId String? Părintele din arborele de delegare / descompunere
projectId String? Proiectul proprietar; nu este trimis niciodată către un sistem de la distanță
assignedAgentId String? Id-ul principalului atribuit (doar metadate — atribuirea nu trimite nimic)
assigneeType PrincipalType Ce fel de principal numește assignedAgentId — user sau agent (implicit agent)
createdByType PrincipalType? Felul principalului care a creat tichetul (user sau agent); null pentru rândurile vechi/de sistem
createdById String? Id-ul principalului care a creat tichetul
assignedTeamId String? Echipa atribuită (doar metadate)
delegatedByAgentId String? Agentul care a delegat acest tichet
delegationDepth int Adâncimea în lanțul de delegare; rădăcina este 0, verificată față de plafonul de adâncime maximă
delegationRootTicketId String? Rădăcina lanțului de delegare; null pentru un tichet rădăcină
spaceId String? Conversația din care a provenit tichetul sau spațiul agentului care deleagă
errorMessage String? Setat când status este failed
linkedPrIds List<String> Id-urile nodurilor PR la care este legat acest tichet
metadata Map<String, dynamic> Colecție liberă de valori
createdAt DateTime Momentul creării
startedAt DateTime? Când a început munca
blockedAt DateTime? Când a fost blocat tichetul
cancelledAt DateTime? Când a fost anulat tichetul
completedAt DateTime? Când s-a finalizat cu succes tichetul
finishedAt DateTime? Când tichetul a ajuns în orice stare finală
updatedAt DateTime Ultima mutație (reîmprospătare a oglinzii sau modificare a suprapunerii)
version int Contor de concurență optimistă, incrementat la fiecare mutație
originKind TicketOriginKind Cum a fost creat tichetul (implicit manual)
collaborators List<TicketCollaborator> Populate de către depozit la cerere

Derivate: isTerminal, isRemote (provider != local), isAssignedToUser și displayKey (externalKey ?? id — tichetele locale afișează așadar UUID-ul separat de la creare, nu id-ul rândului).

Muchiile stocate sunt rânduri direcționate în ticket_links:

Tip Șir stocat Semnificație
blocks blocks Sursa o blochează pe cea țintă
relatesTo relates_to Relație simetrică
duplicateOf duplicate_of Sursa este un duplicat al țintei

Vocabularul folosit de interfață și de ticket_relation este TicketRelationKind, derivat dintr-o muchie stocată plus capătul pe care se află subiectul:

Fel Derivat din
blockedBy Un rând blocks în care subiectul este ținta
blocking Un rând blocks în care subiectul este sursa
relatedTo Un rând relates_to (simetric)
duplicateOf Un rând duplicate_of în care subiectul este sursa
duplicatedBy Un rând duplicate_of în care subiectul este ținta
subIssueOf tickets.parent_ticket_id pe subiect
parentOf tickets.parent_ticket_id pe celălalt tichet

Legăturile de părinte și de sub-sarcină nu sunt rânduri ticket_links — ele se află în tickets.parent_ticket_id.

Aceste opt clase se află în packages/cc_domain/lib/core/domain/events/ticketing_events.dart. Nu există TicketStarted, TicketDelegated, TicketCollaboratorAdded sau ExternalTicketWebhookReceived.

Eveniment Declanșat când
TicketCreated Tichetul este creat
TicketAssigned Tichetul este atribuit unui principal sau unei echipe
TicketCompleted Munca se încheie cu succes
TicketFailed Munca eșuează
TicketCancelled Tichetul este anulat
TicketStatusChanged Orice schimbare de stare
TicketReassigned Tichetul este reatribuit
TicketDetailsUpdated Titlul, descrierea, prioritatea sau etichetele se schimbă

TicketAssigned este un semnal de audit și de notificare și un trigger de pipeline. Atribuirea către un agent nu pornește nicio rulare — nu există niciun dispecer de tichete care să supravegheze assignedAgentId. Atribuirea către o echipă este excepția: TeamRoutingService îl trimite pe liderul echipei. Adăugarea unui colaborator scrie un rând și nu publică nimic. Un webhook de la un furnizor este aplicat prin TicketSyncEngine fără un eveniment de domeniu.

TicketWorkflowService deține fiecare mutație.

Mecanism Detaliu
Punct de trecere unic Toate mutațiile trec prin _mutate, care încarcă rândul și apelează _assertWorkspace înainte de a aplica
Izolarea spațiilor de lucru _assertWorkspace aruncă WorkspaceMismatchException când workspaceId al rândului încărcat nu corespunde celui al apelantului
Concurență optimistă _mutate scrie cu expectedVersion setat la version-ul tocmai citit și reîncearcă la ConcurrencyConflictException, recitind starea proaspătă
Revendicare exclusivă tryCheckout / releaseCheckout; un al doilea agent care revendică un tichet deja rezervat primește CheckoutConflictException
Garda de tranziție canTransitionTo, aplicată doar când force este false