Pular para o conteúdo

Ciclo de vida de 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
backlog ──→ open ──→ inProgress ──→ inReview ──→ done
│ │ │
│ └──→ failed ┘
└──→ blocked ──→ inProgress
Any non-terminal status ──→ cancelled; inReview ──→ failed

Este 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)

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

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.

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

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).

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.

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.

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