Ciclul de viață al tichetelor
Stările tichetelor
Secțiune intitulată „Stările 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 |
Tranzițiile de stare
Secțiune intitulată „Tranzițiile de stare”backlog ──→ open ──→ inProgress ──→ inReview ──→ done │ │ │ │ └──→ failed ┘ └──→ blocked ──→ inProgress
Any non-terminal status ──→ cancelled; inReview ──→ failedAcest 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ă) |
Prioritățile tichetelor
Secțiune intitulată „Prioritățile tichetelor”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 |
Furnizorii de tichete
Secțiune intitulată „Furnizorii de tichete”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.
Proveniența tichetelor
Secțiune intitulată „Proveniența tichetelor”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 |
Câmpurile tichetului
Secțiune intitulată „Câmpurile tichetului”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).
Relațiile dintre tichete
Secțiune intitulată „Relațiile dintre tichete”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.
Evenimente de domeniu
Secțiune intitulată „Evenimente de domeniu”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.
Punctul de trecere al mutațiilor
Secțiune intitulată „Punctul de trecere al mutațiilor”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 |