Cycle de vie des tickets
Statuts des tickets
Section intitulée « Statuts des tickets »| Statut | Chaîne stockée | Description |
|---|---|---|
backlog |
backlog |
Enregistré, pas encore trié dans la file active |
open |
open |
Trié et prêt à être traité, pas encore commencé |
inProgress |
inProgress |
En cours de traitement |
blocked |
blocked |
Commencé mais bloqué par un élément externe |
inReview |
inReview |
Travail terminé, en attente de revue |
done |
done |
Terminé avec succès (terminal) |
failed |
failed |
Terminé sur une erreur (terminal) |
cancelled |
cancelled |
Abandonné (terminal) |
Les valeurs de statut sont stockées en camelCase (inProgress, inReview), jamais in_progress. Une valeur stockée nulle est interprétée comme open ; une valeur inconnue lève une exception au lieu d’être convertie.
tryParseLoose accepte les alias fournis par les agents, en ignorant la casse ainsi que les caractères - / _ / espace :
| Statut | Alias acceptés |
|---|---|
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 |
Transitions de statut
Section intitulée « Transitions de statut »backlog ──→ open ──→ inProgress ──→ inReview ──→ done │ │ │ │ └──→ failed ┘ └──→ blocked ──→ inProgress
Any non-terminal status ──→ cancelled; inReview ──→ failedCe graphe ne lie que les agents et l’automatisation. TicketWorkflowService.transitionStatus le vérifie lorsque force vaut false — les outils MCP et les réconciliateurs. Une transition illégale y est journalisée et ignorée. Chaque changement de statut déclenché par un utilisateur dans l’interface passe force: true et contourne entièrement le graphe, si bien qu’un humain peut déplacer un ticket vers n’importe quel statut proposé par le sélecteur (tous les statuts sauf failed), y compris rouvrir un statut terminal. Les agents marquent failed avec fail_ticket.
Un statut peut toujours effectuer une transition vers lui-même. Les états terminaux n’ont aucune arête sortante.
| De | Cibles autorisées |
|---|---|
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) |
Priorités des tickets
Section intitulée « Priorités des tickets »Stockées sous forme d’entier sur l’échelle native 0–4 de Linear. Une valeur inconnue est interprétée comme none.
| Priorité | Valeur stockée |
|---|---|
none |
0 |
urgent |
1 |
high |
2 |
medium |
3 |
low |
4 |
Fournisseurs de tickets
Section intitulée « Fournisseurs de tickets »Une valeur stockée inconnue ou nulle est interprétée comme local.
| Fournisseur | Statut |
|---|---|
local |
Stocké entièrement dans la base de données de l’espace de travail ; la valeur par défaut |
linear |
Détenu à distance, mis en miroir localement |
Les adaptateurs de synchronisation vivent sous packages/cc_infra/lib/src/tickets/sync/ pour Linear,
GitHub Issues, Jira Cloud et ClickUp. GitHub Issues n’est qu’une source d’adaptateur de
synchronisation (vendorId: github) — ce n’est pas une valeur de TicketProvider, donc
une issue GitHub récupérée est stockée avec originKind: externalSync et provider: local
par défaut. La correspondance des sources vit sur ticket_sync_links.
Origine des tickets
Section intitulée « Origine des tickets »createTicket écrit manual. Une récupération depuis une source écrit externalSync. Les
enfants délégués restent manual.
| Valeur | Signification |
|---|---|
manual |
Valeur par défaut de createTicket (lignes humaines, agent et déléguées) |
externalSync |
Défini par TicketSyncEngine lors d’une récupération depuis une source |
Champs des tickets
Section intitulée « Champs des tickets »L’agrégat couvre deux préoccupations sur une même ligne. Le miroir (fournisseur, clés, titre, description, priorité, étiquettes, statuts, horodatages) est un cache pour un fournisseur distant et est réécrit intégralement par une actualisation. La surcouche (attribution, délégation, espace, parent, projet, PR liées) est propre à Control Center et une actualisation n’y touche jamais.
| Champ | Type | Description |
|---|---|---|
id |
String |
UUID v4 |
workspaceId |
String |
Espace de travail propriétaire |
provider |
TicketProvider |
Backend propriétaire des données canoniques (local par défaut) |
externalKey |
String? |
Clé native du fournisseur (par ex. LIN-123). Le createTicket local écrit ici un UUID distinct, et non l’id de la ligne |
url |
String? |
URL web chez le fournisseur distant |
title |
String |
Résumé court ; ne doit pas être vide |
description |
String? |
Corps Markdown plus long |
priority |
TicketPriority |
none par défaut |
labels |
List<String> |
Étiquettes libres |
status |
TicketStatus |
Statut normalisé canonique |
rawStatus |
String? |
Le nom d’état natif du fournisseur distant, conservé pour un affichage sans perte |
externalSyncedAt |
DateTime? |
Dernière synchronisation multi-source ; nulle jusqu’à la première. La correspondance par source vit dans ticket_sync_links |
parentTicketId |
String? |
Parent dans l’arborescence de délégation / décomposition |
projectId |
String? |
Projet propriétaire ; jamais poussé vers un distant |
assignedAgentId |
String? |
Identifiant du principal assigné (métadonnées uniquement — assigner ne lance rien) |
assigneeType |
PrincipalType |
Type de principal que désigne assignedAgentId — user ou agent (agent par défaut) |
createdByType |
PrincipalType? |
Type de principal ayant créé le ticket (user ou agent) ; nul pour les lignes héritées ou système |
createdById |
String? |
Identifiant du principal créateur |
assignedTeamId |
String? |
Équipe assignée (métadonnées uniquement) |
delegatedByAgentId |
String? |
Agent qui a délégué ce ticket |
delegationDepth |
int |
Profondeur dans la chaîne de délégation ; la racine vaut 0, comparée au plafond de profondeur maximale |
delegationRootTicketId |
String? |
Racine de la chaîne de délégation ; nul pour un ticket racine |
spaceId |
String? |
Conversation dont le ticket a été extrait, ou espace de l’agent délégant |
errorMessage |
String? |
Défini lorsque status vaut failed |
linkedPrIds |
List<String> |
Identifiants des nœuds de PR auxquels ce ticket est lié |
metadata |
Map<String, dynamic> |
Ensemble libre |
createdAt |
DateTime |
Heure de création |
startedAt |
DateTime? |
Heure de début du travail |
blockedAt |
DateTime? |
Heure à laquelle le ticket a été bloqué |
cancelledAt |
DateTime? |
Heure à laquelle le ticket a été annulé |
completedAt |
DateTime? |
Heure à laquelle le ticket s’est achevé avec succès |
finishedAt |
DateTime? |
Heure à laquelle le ticket a atteint un état terminal |
updatedAt |
DateTime |
Dernière mutation (actualisation du miroir ou modification de la surcouche) |
version |
int |
Compteur de concurrence optimiste, incrémenté à chaque mutation |
originKind |
TicketOriginKind |
Mode de création du ticket (manual par défaut) |
collaborators |
List<TicketCollaborator> |
Hydraté par le dépôt à la demande |
Dérivés : isTerminal, isRemote (provider != local), isAssignedToUser et displayKey (externalKey ?? id — les tickets locaux affichent donc l’UUID distinct créé au moment de la création, et non l’id de la ligne).
Relations entre tickets
Section intitulée « Relations entre tickets »Les arêtes stockées sont des lignes orientées dans ticket_links :
| Type | Chaîne stockée | Signification |
|---|---|---|
blocks |
blocks |
La source bloque la cible |
relatesTo |
relates_to |
Relation symétrique |
duplicateOf |
duplicate_of |
La source est un doublon de la cible |
Le vocabulaire que parlent l’interface et ticket_relation est TicketRelationKind, dérivé d’une arête stockée et de l’extrémité sur laquelle se trouve le sujet :
| Type | Dérivé de |
|---|---|
blockedBy |
Une ligne blocks où le sujet est la cible |
blocking |
Une ligne blocks où le sujet est la source |
relatedTo |
Une ligne relates_to (symétrique) |
duplicateOf |
Une ligne duplicate_of où le sujet est la source |
duplicatedBy |
Une ligne duplicate_of où le sujet est la cible |
subIssueOf |
tickets.parent_ticket_id sur le sujet |
parentOf |
tickets.parent_ticket_id sur l’autre ticket |
Les liens parent et sous-ticket ne sont pas des lignes ticket_links — ils vivent sur tickets.parent_ticket_id.
Événements de domaine
Section intitulée « Événements de domaine »Ces huit classes vivent dans packages/cc_domain/lib/core/domain/events/ticketing_events.dart. Il n’existe pas de TicketStarted, TicketDelegated, TicketCollaboratorAdded ni ExternalTicketWebhookReceived.
| Événement | Déclenché quand |
|---|---|
TicketCreated |
Le ticket est créé |
TicketAssigned |
Le ticket est assigné à un principal ou à une équipe |
TicketCompleted |
Le travail se termine avec succès |
TicketFailed |
Le travail échoue |
TicketCancelled |
Le ticket est annulé |
TicketStatusChanged |
Tout changement de statut |
TicketReassigned |
Le ticket est réassigné |
TicketDetailsUpdated |
Le titre, la description, la priorité ou les étiquettes changent |
TicketAssigned est un signal d’audit et de notification, ainsi qu’un déclencheur de pipeline. L’attribution à un agent ne démarre aucune exécution — aucun répartiteur de tickets ne surveille assignedAgentId. L’attribution à une équipe est l’exception : TeamRoutingService lance le chef d’équipe. Ajouter un collaborateur écrit une ligne et ne publie rien. Un webhook de source est appliqué via TicketSyncEngine sans événement de domaine.
Point de passage des mutations
Section intitulée « Point de passage des mutations »TicketWorkflowService détient chaque mutation.
| Mécanisme | Détail |
|---|---|
| Point de passage unique | Toutes les mutations passent par _mutate, qui charge la ligne et appelle _assertWorkspace avant d’appliquer |
| Isolation des espaces de travail | _assertWorkspace lève WorkspaceMismatchException lorsque le workspaceId de la ligne chargée ne correspond pas à celui de l’appelant |
| Concurrence optimiste | _mutate écrit avec expectedVersion défini sur la version qu’il vient de lire et réessaie sur ConcurrencyConflictException en relisant l’état frais |
| Réservation exclusive | tryCheckout / releaseCheckout ; un second agent qui réserve un ticket déjà réservé reçoit une CheckoutConflictException |
| Garde de transition | canTransitionTo, appliqué uniquement lorsque force vaut false |