Ir al contenido

Ciclo de vida de tickets

Estado Cadena almacenada Descripción
backlog backlog Registrado, aún sin triar en la cola activa
open open Triado y listo para trabajarlo, sin empezar
inProgress inProgress En curso
blocked blocked Empezado pero bloqueado por algo externo
inReview inReview Trabajo terminado, a la espera de revisión
done done Terminado correctamente (terminal)
failed failed Terminado con un error (terminal)
cancelled cancelled Abandonado (terminal)

Los valores de estado se almacenan en camelCase (inProgress, inReview), nunca in_progress. Un valor almacenado nulo se interpreta como open; un valor desconocido lanza una excepción en lugar de convertirse.

tryParseLoose acepta los alias que proporciona el agente, ignorando las mayúsculas y minúsculas y - / _ / espacio:

Estado Alias aceptados
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 solo a los agentes y a la automatización. TicketWorkflowService.transitionStatus lo comprueba cuando force es false —las herramientas MCP y los reconciliadores. Una transición ilegal ahí se registra y se ignora. Todos los cambios de estado que hace un usuario desde la interfaz pasan force: true y omiten el grafo por completo, así que una persona puede mover un ticket a cualquier estado que liste el selector (todos los estados salvo failed), incluido reabrir uno terminal. Los agentes marcan failed con fail_ticket.

Un estado siempre puede transicionar a sí mismo. Los estados terminales no tienen aristas de salida.

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

Se almacena como un entero en la escala nativa 0–4 de Linear. Un valor desconocido se interpreta como none.

Prioridad Se almacena como
none 0
urgent 1
high 2
medium 3
low 4

Un valor almacenado desconocido o nulo se interpreta como local.

Proveedor Estado
local Se almacena por completo en la base de datos del espacio de trabajo; es el valor predeterminado
linear Propiedad del sistema remoto, con un espejo local

Los adaptadores de sincronización están en packages/cc_infra/lib/src/tickets/sync/ para Linear, GitHub Issues, Jira Cloud y ClickUp. GitHub Issues es solo un proveedor de adaptadores de sincronización (vendorId: github): no es un valor de TicketProvider, así que una incidencia de GitHub descargada se almacena con originKind: externalSync y provider: local de forma predeterminada. La asignación del proveedor está en ticket_sync_links.

createTicket escribe manual. Una descarga de un proveedor escribe externalSync. Los hijos delegados se quedan en manual.

Valor Significado
manual Valor predeterminado en createTicket (filas humanas, de agente y delegadas)
externalSync Lo establece TicketSyncEngine en una descarga de un proveedor

El agregado abarca dos aspectos en una sola fila. El espejo (proveedor, claves, título, descripción, prioridad, etiquetas, estados, marcas de tiempo) es una caché para un proveedor remoto y una actualización lo reescribe por completo. La superposición (asignación, delegación, espacio, padre, proyecto, PR enlazadas) es exclusiva de Control Center y una actualización nunca la toca.

Campo Tipo Descripción
id String UUID v4
workspaceId String Espacio de trabajo propietario
provider TicketProvider Backend propietario de los datos canónicos (local de forma predeterminada)
externalKey String? Clave nativa del proveedor (p. ej., LIN-123). El createTicket local escribe aquí un UUID aparte, no el id de la fila
url String? URL web en el proveedor remoto
title String Resumen corto; no puede estar vacío
description String? Cuerpo Markdown más largo
priority TicketPriority none de forma predeterminada
labels List<String> Etiquetas de formato libre
status TicketStatus Estado normalizado canónico
rawStatus String? El nombre de estado nativo del proveedor remoto, conservado para mostrarlo sin pérdida
externalSyncedAt DateTime? Última sincronización multiproveedor; nula hasta la primera. La asignación por proveedor está en ticket_sync_links
parentTicketId String? Padre en el árbol de delegación o desglose
projectId String? Proyecto propietario; nunca se envía a un remoto
assignedAgentId String? Id del principal asignado (solo metadatos: asignar no despacha nada)
assigneeType PrincipalType Qué tipo de principal nombra assignedAgentId: user o agent (agent de forma predeterminada)
createdByType PrincipalType? Tipo de principal que creó el ticket (user o agent); nulo en las filas heredadas o del sistema
createdById String? Id del principal que lo creó
assignedTeamId String? Equipo asignado (solo metadatos)
delegatedByAgentId String? Agente que delegó este ticket
delegationDepth int Profundidad en la cadena de delegación; la raíz es 0 y se comprueba contra el límite de profundidad máxima
delegationRootTicketId String? Raíz de la cadena de delegación; nula en un ticket raíz
spaceId String? Conversación de la que surgió el ticket, o el espacio del agente que delega
errorMessage String? Se establece cuando status es failed
linkedPrIds List<String> Ids de nodo de las PR a las que está enlazado este ticket
metadata Map<String, dynamic> Bolsa de formato libre
createdAt DateTime Momento de creación
startedAt DateTime? Cuándo empezó el trabajo
blockedAt DateTime? Cuándo se bloqueó el ticket
cancelledAt DateTime? Cuándo se canceló el ticket
completedAt DateTime? Cuándo se completó el ticket correctamente
finishedAt DateTime? Cuándo alcanzó el ticket cualquier estado terminal
updatedAt DateTime Última mutación (actualización del espejo o cambio en la superposición)
version int Contador de concurrencia optimista, incrementado en cada mutación
originKind TicketOriginKind Cómo se creó el ticket (manual de forma predeterminada)
collaborators List<TicketCollaborator> El repositorio los hidrata a petición

Derivados: isTerminal, isRemote (provider != local), isAssignedToUser y displayKey (externalKey ?? id; por eso los tickets locales muestran el UUID aparte del momento de creación, no el id de la fila).

Las aristas almacenadas son filas direccionales en ticket_links:

Tipo Cadena almacenada Significado
blocks blocks El origen bloquea al destino
relatesTo relates_to Relación simétrica
duplicateOf duplicate_of El origen es un duplicado del destino

El vocabulario que hablan la interfaz y ticket_relation es TicketRelationKind, derivado de una arista almacenada y de en qué extremo se sitúa el sujeto:

Tipo Derivado de
blockedBy Una fila blocks en la que el sujeto es el destino
blocking Una fila blocks en la que el sujeto es el origen
relatedTo Una fila relates_to (simétrica)
duplicateOf Una fila duplicate_of en la que el sujeto es el origen
duplicatedBy Una fila duplicate_of en la que el sujeto es el destino
subIssueOf tickets.parent_ticket_id en el sujeto
parentOf tickets.parent_ticket_id en el otro ticket

Los enlaces de padre y subincidencia no son filas de ticket_links: viven en tickets.parent_ticket_id.

Estas ocho clases viven en packages/cc_domain/lib/core/domain/events/ticketing_events.dart. No hay TicketStarted, TicketDelegated, TicketCollaboratorAdded ni ExternalTicketWebhookReceived.

Evento Se dispara cuando
TicketCreated Se crea el ticket
TicketAssigned Se asigna el ticket a un principal o a un equipo
TicketCompleted El trabajo termina correctamente
TicketFailed El trabajo falla
TicketCancelled Se cancela el ticket
TicketStatusChanged Cualquier cambio de estado
TicketReassigned Se reasigna el ticket
TicketDetailsUpdated Cambian el título, la descripción, la prioridad o las etiquetas

TicketAssigned es una señal de auditoría y notificación y un disparador de pipeline. Asignar un ticket a un agente no inicia ninguna ejecución: no hay ningún despachador de tickets que vigile assignedAgentId. La asignación a un equipo es la excepción: TeamRoutingService despacha al líder del equipo. Añadir un colaborador escribe una fila y no publica nada. Un webhook de proveedor se aplica a través de TicketSyncEngine sin ningún evento de dominio.

TicketWorkflowService posee todas las mutaciones.

Mecanismo Detalle
Punto de paso único Todas las mutaciones pasan por _mutate, que carga la fila y llama a _assertWorkspace antes de aplicarla
Aislamiento de espacios de trabajo _assertWorkspace lanza WorkspaceMismatchException cuando el workspaceId de la fila cargada no coincide con el de quien llama
Concurrencia optimista _mutate escribe con expectedVersion establecido al version que acaba de leer y reintenta ante una ConcurrencyConflictException releyendo el estado actualizado
Reclamación exclusiva tryCheckout / releaseCheckout; un segundo agente que reclame un ticket ya reclamado recibe una CheckoutConflictException
Guarda de transición canTransitionTo, aplicada solo cuando force es false