Ir al contenido

Ciclo de vida de tickets

Estado Cadena almacenada Descripción
backlog backlog Registrado, aún sin clasificar en la cola activa
open open Clasificado y listo para trabajarse, sin comenzar
inProgress inProgress En trabajo activo
blocked blocked Comenzado pero bloqueado por algo externo
inReview inReview Trabajo terminado, en 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 coercerse.

tryParseLoose acepta alias proporcionados por el agente, ignorando mayúsculas y minúsculas y los caracteres - / _ / 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 únicamente a los agentes y la automatización. TicketWorkflowService.transitionStatus lo comprueba cuando force es false: las herramientas MCP y los reconciliadores. Una transición ilegal allí 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 excepto 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 almacenan 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 Situación
local Se almacena por completo en la base de datos del espacio de trabajo; es el valor predeterminado
linear Propiedad del sistema remoto, replicado localmente

Los adaptadores de sincronización se encuentran en packages/cc_infra/lib/src/tickets/sync/ para Linear, GitHub Issues, Jira Cloud y ClickUp. GitHub Issues solo es un proveedor de adaptador 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 vive en ticket_sync_links.

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

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

El agregado abarca dos conceptos 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 vinculadas) 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 separado, no el id de la fila
url String? URL web en el proveedor remoto
title String Resumen breve; no debe estar vacío
description String? Cuerpo más largo en Markdown
priority TicketPriority none de forma predeterminada
labels List<String> Etiquetas de formato libre
status TicketStatus Estado canónico normalizado
rawStatus String? El nombre del estado nativo del proveedor remoto, conservado para mostrarlo sin pérdidas
externalSyncedAt DateTime? Última sincronización multivendor; nulo hasta la primera. La asignación por proveedor vive 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 sistema remoto
assignedAgentId String? Id del principal asignado (solo metadatos: asignar no despacha nada)
assigneeType PrincipalType Qué tipo de principal designa assignedAgentId: user o agent (agent de forma predeterminada)
createdByType PrincipalType? Tipo de principal que creó el ticket (user o agent); nulo en 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, verificada contra el límite de profundidad máxima
delegationRootTicketId String? Raíz de la cadena de delegación; nulo para 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 PR a los que está vinculado este ticket
metadata Map<String, dynamic> Bolsa de formato libre
createdAt DateTime Momento de creación
startedAt DateTime? Cuándo comenzó el trabajo
blockedAt DateTime? Cuándo se bloqueó el ticket
cancelledAt DateTime? Cuándo se canceló el ticket
completedAt DateTime? Cuándo se completó correctamente el ticket
finishedAt DateTime? Cuándo alcanzó el ticket cualquier estado terminal
updatedAt DateTime Última mutación (actualización del espejo o cambio de 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 — los tickets locales muestran por lo tanto el UUID separado de la 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 más el extremo en el que se encuentra el sujeto:

Tipo Derivado de
blockedBy Una fila blocks donde el sujeto es el destino
blocking Una fila blocks donde el sujeto es el origen
relatedTo Una fila relates_to (simétrica)
duplicateOf Una fila duplicate_of donde el sujeto es el origen
duplicatedBy Una fila duplicate_of donde 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 existen 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. La asignación 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. Agregar un colaborador escribe una fila y no publica nada. Un webhook de proveedor se aplica mediante TicketSyncEngine sin un evento de dominio.

TicketWorkflowService es dueño de 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 aplicar
Aislamiento de espacio 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 en la version que acaba de leer y reintenta ante ConcurrencyConflictException releyendo el estado más reciente
Reclamo exclusivo tryCheckout / releaseCheckout; un segundo agente que reclama un ticket ya reclamado recibe una CheckoutConflictException
Barandilla de transición canTransitionTo, aplicada solo cuando force es false