Ciclo de vida de tickets
Estados de ticket
Secção intitulada “Estados de ticket”| Estado | Valor armazenado | Descrição |
|---|---|---|
backlog |
backlog |
Capturado, ainda não triado para a fila ativa |
open |
open |
Triado e pronto para ser trabalhado, ainda não iniciado |
inProgress |
inProgress |
A ser trabalhado ativamente |
blocked |
blocked |
Iniciado, mas bloqueado por algo externo |
inReview |
inReview |
Trabalho concluído, à espera de revisão |
done |
done |
Concluído com êxito (terminal) |
failed |
failed |
Terminado com um erro (terminal) |
cancelled |
cancelled |
Abandonado (terminal) |
Os valores de estado são armazenados em camelCase (inProgress, inReview), nunca in_progress. Um valor armazenado nulo é interpretado como open; um valor desconhecido lança uma exceção em vez de ser coagido.
O tryParseLoose aceita aliases fornecidos pelos agentes, ignorando a distinção entre maiúsculas e minúsculas e - / _ / espaço:
| Estado | Aliases aceites |
|---|---|
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 |
Transições de estado
Secção intitulada “Transições de estado”backlog ──→ open ──→ inProgress ──→ inReview ──→ done │ │ │ │ └──→ failed ┘ └──→ blocked ──→ inProgress
Any non-terminal status ──→ cancelled; inReview ──→ failedEste grafo vincula apenas os agentes e a automatização. O TicketWorkflowService.transitionStatus verifica-o quando force é false — as ferramentas MCP e os reconciliadores. Uma transição ilegal aí é registada e ignorada. Todas as alterações de estado feitas pelo utilizador na interface passam force: true e contornam o grafo por completo, pelo que uma pessoa pode mover um ticket para qualquer estado que o seletor apresente (todos os estados exceto failed), incluindo reabrir um estado terminal. Os agentes marcam failed com fail_ticket.
Um estado pode transitar sempre para si próprio. Os estados terminais não têm arestas de saída.
| De | Destinos permitidos |
|---|---|
backlog |
open, cancelled |
open |
inProgress, blocked, cancelled |
inProgress |
blocked, inReview, done, failed, cancelled |
blocked |
inProgress, cancelled |
inReview |
inProgress, done, failed, cancelled |
done |
— (terminal) |
failed |
— (terminal) |
cancelled |
— (terminal) |
Prioridades de ticket
Secção intitulada “Prioridades de ticket”Armazenadas como um número inteiro na escala nativa 0–4 do Linear. Um valor desconhecido é interpretado como none.
| Prioridade | Armazenada como |
|---|---|
none |
0 |
urgent |
1 |
high |
2 |
medium |
3 |
low |
4 |
Fornecedores de tickets
Secção intitulada “Fornecedores de tickets”Um valor armazenado desconhecido ou nulo é interpretado como local.
| Fornecedor | Estado |
|---|---|
local |
Armazenado inteiramente na base de dados do espaço de trabalho; a predefinição |
linear |
Gerido remotamente, espelhado localmente |
Os adaptadores de sincronização vivem em packages/cc_infra/lib/src/tickets/sync/ para o Linear,
o GitHub Issues, o Jira Cloud e o ClickUp. O GitHub Issues é apenas um fornecedor de
adaptador de sincronização (vendorId: github) — não é um valor de TicketProvider, pelo
que um issue do GitHub obtido por recolha é armazenado com originKind: externalSync e
provider: local predefinido. O mapeamento de fornecedores vive em ticket_sync_links.
Origem do ticket
Secção intitulada “Origem do ticket”O createTicket escreve manual. Uma recolha de um fornecedor escreve externalSync. Os
filhos delegados permanecem manual.
| Valor | Significado |
|---|---|
manual |
Predefinição no createTicket (linhas humanas, de agentes e delegadas) |
externalSync |
Definido pelo TicketSyncEngine numa recolha de um fornecedor |
Campos do ticket
Secção intitulada “Campos do ticket”O agregado abrange dois aspetos na mesma linha. O espelho (fornecedor, chaves, título, descrição, prioridade, etiquetas, estados, marcas temporais) é uma cache para um fornecedor remoto e é reescrito por completo por uma atualização. A sobreposição (atribuição, delegação, espaço, ascendente, projeto, PRs associadas) é exclusiva do Control Center e uma atualização nunca lhe toca.
| Campo | Tipo | Descrição |
|---|---|---|
id |
String |
UUID v4 |
workspaceId |
String |
Espaço de trabalho que detém o ticket |
provider |
TicketProvider |
Backend que detém os dados canónicos (predefinição local) |
externalKey |
String? |
Chave nativa do fornecedor (por exemplo, LIN-123). O createTicket local escreve aqui um UUID separado, não o id da linha |
url |
String? |
URL Web no fornecedor remoto |
title |
String |
Resumo curto; não pode estar vazio |
description |
String? |
Corpo Markdown mais longo |
priority |
TicketPriority |
Predefinição none |
labels |
List<String> |
Etiquetas de formato livre |
status |
TicketStatus |
Estado normalizado canónico |
rawStatus |
String? |
O nome do estado nativo do fornecedor remoto, mantido para uma apresentação sem perdas |
externalSyncedAt |
DateTime? |
Última sincronização multi-fornecedor; nulo até à primeira. O mapeamento por fornecedor vive em ticket_sync_links |
parentTicketId |
String? |
Ascendente na árvore de delegação / decomposição |
projectId |
String? |
Projeto que detém o ticket; nunca é enviado para um remoto |
assignedAgentId |
String? |
Id do principal atribuído (apenas metadados — atribuir não despacha nada) |
assigneeType |
PrincipalType |
Que tipo de principal o assignedAgentId nomeia — user ou agent (predefinição agent) |
createdByType |
PrincipalType? |
Tipo de principal que criou o ticket (user ou agent); nulo para linhas antigas ou de sistema |
createdById |
String? |
Id do principal que criou |
assignedTeamId |
String? |
Equipa atribuída (apenas metadados) |
delegatedByAgentId |
String? |
Agente que delegou este ticket |
delegationDepth |
int |
Profundidade na cadeia de delegação; a raiz é 0, verificada em relação ao limite máximo de profundidade |
delegationRootTicketId |
String? |
Raiz da cadeia de delegação; nulo para um ticket raiz |
spaceId |
String? |
Conversa da qual o ticket derivou, ou o espaço do agente que delegou |
errorMessage |
String? |
Definido quando status é failed |
linkedPrIds |
List<String> |
Ids dos nós de PR aos quais este ticket está associado |
metadata |
Map<String, dynamic> |
Saco de formato livre |
createdAt |
DateTime |
Hora de criação |
startedAt |
DateTime? |
Quando o trabalho começou |
blockedAt |
DateTime? |
Quando o ticket foi bloqueado |
cancelledAt |
DateTime? |
Quando o ticket foi cancelado |
completedAt |
DateTime? |
Quando o ticket foi concluído com êxito |
finishedAt |
DateTime? |
Quando o ticket atingiu um estado terminal |
updatedAt |
DateTime |
Última mutação (atualização do espelho ou alteração da sobreposição) |
version |
int |
Contador de concorrência otimista, incrementado a cada mutação |
originKind |
TicketOriginKind |
Como o ticket foi criado (predefinição manual) |
collaborators |
List<TicketCollaborator> |
Hidratado pelo repositório a pedido |
Derivados: isTerminal, isRemote (provider != local), isAssignedToUser e displayKey (externalKey ?? id — por isso, os tickets locais apresentam o UUID separado criado no momento da criação, e não o id da linha).
Relações entre tickets
Secção intitulada “Relações entre tickets”As arestas armazenadas são linhas direcionais em ticket_links:
| Tipo | Valor armazenado | Significado |
|---|---|---|
blocks |
blocks |
A origem bloqueia o destino |
relatesTo |
relates_to |
Relação simétrica |
duplicateOf |
duplicate_of |
A origem é um duplicado do destino |
O vocabulário que a interface e o ticket_relation utilizam é TicketRelationKind, derivado de uma aresta armazenada e da extremidade em que o sujeito se situa:
| Tipo | Derivado de |
|---|---|
blockedBy |
Uma linha blocks em que o sujeito é o destino |
blocking |
Uma linha blocks em que o sujeito é a origem |
relatedTo |
Uma linha relates_to (simétrica) |
duplicateOf |
Uma linha duplicate_of em que o sujeito é a origem |
duplicatedBy |
Uma linha duplicate_of em que o sujeito é o destino |
subIssueOf |
tickets.parent_ticket_id no sujeito |
parentOf |
tickets.parent_ticket_id no outro ticket |
As ligações ascendente e de sub-ticket não são linhas de ticket_links — vivem em tickets.parent_ticket_id.
Eventos de domínio
Secção intitulada “Eventos de domínio”Estas oito classes vivem em packages/cc_domain/lib/core/domain/events/ticketing_events.dart. Não existe nenhum TicketStarted, TicketDelegated, TicketCollaboratorAdded nem ExternalTicketWebhookReceived.
| Evento | Disparado quando |
|---|---|
TicketCreated |
O ticket é criado |
TicketAssigned |
O ticket é atribuído a um principal ou a uma equipa |
TicketCompleted |
O trabalho termina com êxito |
TicketFailed |
O trabalho falha |
TicketCancelled |
O ticket é cancelado |
TicketStatusChanged |
Qualquer alteração de estado |
TicketReassigned |
O ticket é reatribuído |
TicketDetailsUpdated |
O título, a descrição, a prioridade ou as etiquetas mudam |
O TicketAssigned é um sinal de auditoria e de notificação e um disparador de pipeline. A atribuição a um agente não inicia nenhuma execução — não existe nenhum despachante de tickets a vigiar o assignedAgentId. A atribuição a uma equipa é a exceção: o TeamRoutingService despacha o líder da equipa. Adicionar um colaborador escreve uma linha e não publica nada. Um webhook de um fornecedor é aplicado através do TicketSyncEngine sem qualquer evento de domínio.
Ponto de passagem obrigatório das mutações
Secção intitulada “Ponto de passagem obrigatório das mutações”O TicketWorkflowService detém todas as mutações.
| Mecanismo | Detalhe |
|---|---|
| Ponto de passagem único | Todas as mutações passam por _mutate, que carrega a linha e chama _assertWorkspace antes de aplicar |
| Isolamento do espaço de trabalho | O _assertWorkspace lança WorkspaceMismatchException quando o workspaceId da linha carregada não corresponde ao de quem chama |
| Concorrência otimista | O _mutate escreve com expectedVersion definido para o version que acabou de ler e tenta novamente perante uma ConcurrencyConflictException, relendo o estado atualizado |
| Reclamação exclusiva | tryCheckout / releaseCheckout; um segundo agente que reclame um ticket já reservado recebe uma CheckoutConflictException |
| Barreira de transição | canTransitionTo, aplicado apenas quando force é false |