Ciclo di vita dei ticket
Stati dei ticket
Sezione intitolata “Stati dei ticket”| Stato | Stringa memorizzata | Descrizione |
|---|---|---|
backlog |
backlog |
Acquisito, non ancora sottoposto a triage nella coda attiva |
open |
open |
Sottoposto a triage e pronto per essere lavorato, non avviato |
inProgress |
inProgress |
In lavorazione attiva |
blocked |
blocked |
Avviato ma bloccato da qualcosa di esterno |
inReview |
inReview |
Lavoro completato, in attesa di revisione |
done |
done |
Terminato con successo (terminale) |
failed |
failed |
Terminato con errore (terminale) |
cancelled |
cancelled |
Abbandonato (terminale) |
I valori di stato sono memorizzati in camelCase (inProgress, inReview), mai in_progress. Un valore memorizzato null viene interpretato come open; un valore sconosciuto genera un errore invece di essere convertito.
tryParseLoose accetta gli alias forniti dagli agenti, senza distinguere maiuscole e minuscole e ignorando - / _ / spazio:
| Stato | Alias accettati |
|---|---|
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 |
Transizioni di stato
Sezione intitolata “Transizioni di stato”backlog ──→ open ──→ inProgress ──→ inReview ──→ done │ │ │ │ └──→ failed ┘ └──→ blocked ──→ inProgress
Any non-terminal status ──→ cancelled; inReview ──→ failedQuesto grafo vincola solo gli agenti e l’automazione. TicketWorkflowService.transitionStatus lo verifica quando force è false — gli strumenti MCP e i riconciliatori. Una transizione non valida in quel contesto viene registrata e ignorata. Ogni modifica di stato guidata dall’utente nell’interfaccia passa force: true e aggira del tutto il grafo, quindi una persona può spostare un ticket in qualsiasi stato elenchi il selettore (tutti gli stati tranne failed), compreso riaprirne uno terminale. Gli agenti impostano failed con fail_ticket.
Uno stato può sempre transitare verso se stesso. Gli stati terminali non hanno archi in uscita.
| Da | Destinazioni consentite |
|---|---|
backlog |
open, cancelled |
open |
inProgress, blocked, cancelled |
inProgress |
blocked, inReview, done, failed, cancelled |
blocked |
inProgress, cancelled |
inReview |
inProgress, done, failed, cancelled |
done |
— (terminale) |
failed |
— (terminale) |
cancelled |
— (terminale) |
Priorità dei ticket
Sezione intitolata “Priorità dei ticket”Memorizzate come numero intero sulla scala nativa 0–4 di Linear. Un valore sconosciuto viene interpretato come none.
| Priorità | Memorizzata come |
|---|---|
none |
0 |
urgent |
1 |
high |
2 |
medium |
3 |
low |
4 |
Provider dei ticket
Sezione intitolata “Provider dei ticket”Un valore memorizzato sconosciuto o null viene interpretato come local.
| Provider | Stato |
|---|---|
local |
Memorizzato interamente nel database dello spazio di lavoro; è il valore predefinito |
linear |
Di proprietà remota, con mirror locale |
Gli adattatori di sincronizzazione si trovano in packages/cc_infra/lib/src/tickets/sync/ per Linear,
GitHub Issues, Jira Cloud e ClickUp. GitHub Issues è solo un fornitore di adattatori
di sincronizzazione (vendorId: github) — non è un valore di TicketProvider, quindi un
issue GitHub recuperato viene memorizzato con originKind: externalSync e
provider: local predefinito. La mappatura dei fornitori vive su ticket_sync_links.
Origine dei ticket
Sezione intitolata “Origine dei ticket”createTicket scrive manual. Una pull dal fornitore scrive externalSync. I figli
delegati restano manual.
| Valore | Significato |
|---|---|
manual |
Predefinito in createTicket (righe umane, degli agenti e delegate) |
externalSync |
Impostato da TicketSyncEngine durante una pull dal fornitore |
Campi dei ticket
Sezione intitolata “Campi dei ticket”L’aggregato copre due aspetti su un’unica riga. Il mirror (provider, chiavi, titolo, descrizione, priorità, etichette, stati, timestamp) è una cache per un provider remoto e viene riscritto in blocco da un aggiornamento. L’overlay (assegnazione, delega, spazio, padre, progetto, PR collegate) è esclusivo di Control Center e un aggiornamento non lo tocca mai.
| Campo | Tipo | Descrizione |
|---|---|---|
id |
String |
UUID v4 |
workspaceId |
String |
Spazio di lavoro proprietario |
provider |
TicketProvider |
Backend che possiede i dati canonici (predefinito local) |
externalKey |
String? |
Chiave nativa del provider (es. LIN-123). La createTicket locale scrive qui un UUID separato, non l’id della riga |
url |
String? |
URL web sul provider remoto |
title |
String |
Riepilogo breve; non deve essere vuoto |
description |
String? |
Corpo Markdown più lungo |
priority |
TicketPriority |
Predefinita none |
labels |
List<String> |
Etichette libere |
status |
TicketStatus |
Stato normalizzato canonico |
rawStatus |
String? |
Il nome nativo dello stato presso il provider remoto, conservato per una visualizzazione senza perdite |
externalSyncedAt |
DateTime? |
Ultima sincronizzazione multi-fornitore; null fino alla prima. La mappatura per fornitore vive in ticket_sync_links |
parentTicketId |
String? |
Padre nell’albero di delega / scomposizione |
projectId |
String? |
Progetto proprietario; non viene mai inviato a un remoto |
assignedAgentId |
String? |
Id del principale assegnato (solo metadati — l’assegnazione non manda nulla in esecuzione) |
assigneeType |
PrincipalType |
Che tipo di principale indica assignedAgentId — user o agent (predefinito agent) |
createdByType |
PrincipalType? |
Tipo di principale che ha creato il ticket (user o agent); null per le righe legacy/di sistema |
createdById |
String? |
Id del principale che l’ha creato |
assignedTeamId |
String? |
Team assegnato (solo metadati) |
delegatedByAgentId |
String? |
Agente che ha delegato questo ticket |
delegationDepth |
int |
Profondità nella catena di delega; la radice è 0, verificata rispetto al tetto di profondità massima |
delegationRootTicketId |
String? |
Radice della catena di delega; null per un ticket radice |
spaceId |
String? |
Conversazione da cui è nato il ticket, oppure lo spazio dell’agente che delega |
errorMessage |
String? |
Impostato quando status è failed |
linkedPrIds |
List<String> |
Id dei nodi PR a cui questo ticket è collegato |
metadata |
Map<String, dynamic> |
Contenitore libero |
createdAt |
DateTime |
Ora di creazione |
startedAt |
DateTime? |
Quando è iniziato il lavoro |
blockedAt |
DateTime? |
Quando il ticket è stato bloccato |
cancelledAt |
DateTime? |
Quando il ticket è stato annullato |
completedAt |
DateTime? |
Quando il ticket è stato completato con successo |
finishedAt |
DateTime? |
Quando il ticket ha raggiunto un qualsiasi stato terminale |
updatedAt |
DateTime |
Ultima mutazione (aggiornamento del mirror o modifica dell’overlay) |
version |
int |
Contatore di concorrenza ottimistica, incrementato a ogni mutazione |
originKind |
TicketOriginKind |
Come è stato creato il ticket (predefinito manual) |
collaborators |
List<TicketCollaborator> |
Idratati dal repository su richiesta |
Derivati: isTerminal, isRemote (provider != local), isAssignedToUser e displayKey (externalKey ?? id — i ticket locali mostrano quindi l’UUID separato creato al momento della creazione, non l’id della riga).
Relazioni tra ticket
Sezione intitolata “Relazioni tra ticket”Gli archi memorizzati sono righe direzionali in ticket_links:
| Tipo | Stringa memorizzata | Significato |
|---|---|---|
blocks |
blocks |
La sorgente blocca la destinazione |
relatesTo |
relates_to |
Relazione simmetrica |
duplicateOf |
duplicate_of |
La sorgente è un duplicato della destinazione |
Il vocabolario usato dall’interfaccia e da ticket_relation è TicketRelationKind, derivato da un arco memorizzato più l’estremo su cui si trova il soggetto:
| Categoria | Derivato da |
|---|---|
blockedBy |
Una riga blocks in cui il soggetto è la destinazione |
blocking |
Una riga blocks in cui il soggetto è la sorgente |
relatedTo |
Una riga relates_to (simmetrica) |
duplicateOf |
Una riga duplicate_of in cui il soggetto è la sorgente |
duplicatedBy |
Una riga duplicate_of in cui il soggetto è la destinazione |
subIssueOf |
tickets.parent_ticket_id sul soggetto |
parentOf |
tickets.parent_ticket_id sull’altro ticket |
I link genitore e sotto-ticket non sono righe di ticket_links — vivono su tickets.parent_ticket_id.
Eventi di dominio
Sezione intitolata “Eventi di dominio”Queste otto classi vivono in packages/cc_domain/lib/core/domain/events/ticketing_events.dart. Non esistono TicketStarted, TicketDelegated, TicketCollaboratorAdded né ExternalTicketWebhookReceived.
| Evento | Emesso quando |
|---|---|
TicketCreated |
Il ticket viene creato |
TicketAssigned |
Il ticket viene assegnato a un principale o a un team |
TicketCompleted |
Il lavoro termina con successo |
TicketFailed |
Il lavoro fallisce |
TicketCancelled |
Il ticket viene annullato |
TicketStatusChanged |
Qualsiasi modifica di stato |
TicketReassigned |
Il ticket viene riassegnato |
TicketDetailsUpdated |
Cambiano titolo, descrizione, priorità o etichette |
TicketAssigned è un segnale di audit e notifica e un trigger di pipeline. L’assegnazione a un agente non avvia alcuna esecuzione — non esiste alcun dispatcher dei ticket che osservi assignedAgentId. L’assegnazione a un team è l’eccezione: TeamRoutingService manda in esecuzione il leader del team. Aggiungere un collaboratore scrive una riga e non pubblica nulla. Un webhook del fornitore viene applicato tramite TicketSyncEngine senza un evento di dominio.
Il punto di controllo delle mutazioni
Sezione intitolata “Il punto di controllo delle mutazioni”TicketWorkflowService possiede ogni mutazione.
| Meccanismo | Dettaglio |
|---|---|
| Punto di controllo unico | Tutte le mutazioni passano da _mutate, che carica la riga e chiama _assertWorkspace prima di applicare |
| Isolamento dello spazio di lavoro | _assertWorkspace lancia WorkspaceMismatchException quando il workspaceId della riga caricata non corrisponde a quello del chiamante |
| Concorrenza ottimistica | _mutate scrive con expectedVersion impostato alla version appena letta e riprova su ConcurrencyConflictException rileggendo lo stato aggiornato |
| Prenotazione esclusiva | tryCheckout / releaseCheckout; un secondo agente che rivendica un ticket già prenotato riceve una CheckoutConflictException |
| Guardia di transizione | canTransitionTo, applicata solo quando force è false |