Ciclo de vida de tickets
Status dos tickets
Seção intitulada “Status dos tickets”| Status | String de armazenamento | Descrição |
|---|---|---|
backlog |
backlog |
Capturado, ainda não triado para a fila ativa |
open |
open |
Triado e pronto para ser trabalhado, não iniciado |
inProgress |
inProgress |
Sendo trabalhado ativamente |
blocked |
blocked |
Iniciado, mas bloqueado por algo externo |
inReview |
inReview |
Trabalho concluído, aguardando revisão |
done |
done |
Concluído com sucesso (terminal) |
failed |
failed |
Concluído com erro (terminal) |
cancelled |
cancelled |
Abandonado (terminal) |
Os valores de status 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 pelo agente, ignorando maiúsculas e minúsculas e - / _ / espaço:
| Status | Aliases aceitos |
|---|---|
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 status
Seção intitulada “Transições de status”backlog ──→ open ──→ inProgress ──→ inReview ──→ done │ │ │ │ └──→ failed ┘ └──→ blocked ──→ inProgress
Any non-terminal status ──→ cancelled; inReview ──→ failedEste grafo vincula apenas agentes e automação. O TicketWorkflowService.transitionStatus o verifica quando force é false — as ferramentas MCP e os reconciliadores. Uma transição ilegal ali é registrada em log e ignorada. Toda mudança de status feita por um usuário na interface passa force: true e ignora o grafo por completo, então uma pessoa pode mover um ticket para qualquer status que o seletor liste (todos os status exceto failed), inclusive reabrir um estado terminal. Os agentes marcam failed com fail_ticket.
Um status sempre pode fazer transição para si mesmo. 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
Seção intitulada “Prioridades de ticket”Armazenada como um inteiro na escala nativa de 0 a 4 do Linear. Um valor desconhecido é interpretado como none.
| Prioridade | Armazenada como |
|---|---|
none |
0 |
urgent |
1 |
high |
2 |
medium |
3 |
low |
4 |
Provedores de ticket
Seção intitulada “Provedores de ticket”Um valor armazenado desconhecido ou nulo é interpretado como local.
| Provedor | Status |
|---|---|
local |
Armazenado inteiramente no banco de dados do espaço de trabalho; o padrão |
linear |
De propriedade remota, espelhado localmente |
Os adaptadores de sincronização ficam 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) — ele não é um valor de TicketProvider, então um issue do GitHub importado é armazenado com originKind: externalSync e provider: local padrão. O mapeamento de fornecedores fica em ticket_sync_links.
Origem do ticket
Seção intitulada “Origem do ticket”O createTicket grava manual. Uma importação de fornecedor grava externalSync. Filhos delegados permanecem manual.
| Valor | Significado |
|---|---|
manual |
Padrão no createTicket (linhas humanas, de agente e delegadas) |
externalSync |
Definido pelo TicketSyncEngine em uma importação de fornecedor |
Campos do ticket
Seção intitulada “Campos do ticket”O agregado abrange duas preocupações em uma única linha. O espelho (provedor, chaves, título, descrição, prioridade, rótulos, status, carimbos de data e hora) é um cache para um provedor remoto e é reescrito por inteiro por uma atualização. A sobreposição (atribuição, delegação, espaço, pai, projeto, PRs vinculadas) existe só no Control Center e uma atualização nunca a toca.
| Campo | Tipo | Descrição |
|---|---|---|
id |
String |
UUID v4 |
workspaceId |
String |
Espaço de trabalho proprietário |
provider |
TicketProvider |
Backend que detém os dados canônicos (padrão local) |
externalKey |
String? |
Chave nativa do provedor (por exemplo, LIN-123). O createTicket local grava um UUID separado aqui, não o id da linha |
url |
String? |
URL da web no provedor remoto |
title |
String |
Resumo curto; não pode estar vazio |
description |
String? |
Corpo em Markdown mais longo |
priority |
TicketPriority |
Padrão none |
labels |
List<String> |
Rótulos de formato livre |
status |
TicketStatus |
Status canônico normalizado |
rawStatus |
String? |
O nome do estado nativo do provedor remoto, mantido para exibição sem perdas |
externalSyncedAt |
DateTime? |
Última sincronização com vários fornecedores; nulo até a primeira. O mapeamento por fornecedor fica em ticket_sync_links |
parentTicketId |
String? |
Pai na árvore de delegação / decomposição |
projectId |
String? |
Projeto proprietário; nunca é enviado a 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 (padrão agent) |
createdByType |
PrincipalType? |
Tipo de principal que criou o ticket (user ou agent); nulo para linhas legadas/do sistema |
createdById |
String? |
Id do principal que criou o ticket |
assignedTeamId |
String? |
Equipe 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 foi derivado, ou o espaço do agente delegador |
errorMessage |
String? |
Definido quando o status é failed |
linkedPrIds |
List<String> |
Ids de nó de PR aos quais este ticket está vinculado |
metadata |
Map<String, dynamic> |
Contêiner de formato livre |
createdAt |
DateTime |
Momento 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 sucesso |
finishedAt |
DateTime? |
Quando o ticket alcançou qualquer estado terminal |
updatedAt |
DateTime |
Última mutação (atualização do espelho ou mudança na sobreposição) |
version |
int |
Contador de concorrência otimista, incrementado a cada mutação |
originKind |
TicketOriginKind |
Como o ticket foi criado (padrão manual) |
collaborators |
List<TicketCollaborator> |
Hidratados pelo repositório sob demanda |
Derivados: isTerminal, isRemote (provider != local), isAssignedToUser e displayKey (externalKey ?? id — portanto, tickets locais exibem o UUID separado criado no momento da criação, não o id da linha).
Relações de ticket
Seção intitulada “Relações de ticket”As arestas armazenadas são linhas direcionais em ticket_links:
| Tipo | String de armazenamento | Significado |
|---|---|---|
blocks |
blocks |
A origem bloqueia o destino |
relatesTo |
relates_to |
Relação simétrica |
duplicateOf |
duplicate_of |
A origem é uma duplicata do destino |
O vocabulário que a interface e o ticket_relation falam é TicketRelationKind, derivado de uma aresta armazenada mais em qual ponta o sujeito se encontra:
| Tipo | Derivado de |
|---|---|
blockedBy |
Uma linha de blocks em que o sujeito é o destino |
blocking |
Uma linha de blocks em que o sujeito é a origem |
relatedTo |
Uma linha de relates_to (simétrica) |
duplicateOf |
Uma linha de duplicate_of em que o sujeito é a origem |
duplicatedBy |
Uma linha de duplicate_of em que o sujeito é o destino |
subIssueOf |
tickets.parent_ticket_id no sujeito |
parentOf |
tickets.parent_ticket_id no outro ticket |
Vínculos de pai e de subitem não são linhas de ticket_links — eles ficam em tickets.parent_ticket_id.
Eventos de domínio
Seção intitulada “Eventos de domínio”Estas oito classes ficam em packages/cc_domain/lib/core/domain/events/ticketing_events.dart. Não existe TicketStarted, TicketDelegated, TicketCollaboratorAdded nem ExternalTicketWebhookReceived.
| Evento | Disparado quando |
|---|---|
TicketCreated |
O ticket é criado |
TicketAssigned |
O ticket é atribuído a um principal ou a uma equipe |
TicketCompleted |
O trabalho é concluído com sucesso |
TicketFailed |
O trabalho falha |
TicketCancelled |
O ticket é cancelado |
TicketStatusChanged |
Qualquer mudança de status |
TicketReassigned |
O ticket é reatribuído |
TicketDetailsUpdated |
Título, descrição, prioridade ou rótulos mudam |
O TicketAssigned é um sinal de auditoria e de notificação e um gatilho de pipeline. A atribuição a um agente não inicia nenhuma execução — não existe um despachante de tickets observando assignedAgentId. A atribuição a uma equipe é a exceção: o TeamRoutingService despacha o líder da equipe. Adicionar um colaborador grava uma linha e não publica nada. Um webhook de fornecedor é aplicado por meio do TicketSyncEngine sem um evento de domínio.
Ponto único de passagem das mutações
Seção intitulada “Ponto único de passagem das mutações”O TicketWorkflowService concentra todas as mutações.
| Mecanismo | Detalhe |
|---|---|
| Ponto único de passagem | Todas as mutações passam por _mutate, que carrega a linha e chama _assertWorkspace antes de aplicar |
| Isolamento de 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 grava com expectedVersion definido como o version que acabou de ler e tenta de novo em caso de ConcurrencyConflictException, relendo o estado atualizado |
| Reserva exclusiva | tryCheckout / releaseCheckout; um segundo agente que reivindica um ticket já reservado recebe uma CheckoutConflictException |
| Barreira de transição | canTransitionTo, aplicada apenas quando force é false |