Ciclo de vida de tickets
Estados de los tickets
Sección titulada «Estados de los 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 |
Transiciones de estado
Sección titulada «Transiciones de estado»backlog ──→ open ──→ inProgress ──→ inReview ──→ done │ │ │ │ └──→ failed ┘ └──→ blocked ──→ inProgress
Any non-terminal status ──→ cancelled; inReview ──→ failedEste 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) |
Prioridades de los tickets
Sección titulada «Prioridades de los tickets»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 |
Proveedores de tickets
Sección titulada «Proveedores de tickets»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.
Origen del ticket
Sección titulada «Origen del ticket»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 |
Campos del ticket
Sección titulada «Campos del ticket»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).
Relaciones entre tickets
Sección titulada «Relaciones entre tickets»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.
Eventos de dominio
Sección titulada «Eventos de dominio»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.
Punto de paso de mutaciones
Sección titulada «Punto de paso de mutaciones»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 |