Cycle de vie des tickets
Statuts des tickets
Section intitulée “Statuts des tickets”| Statut | Chaîne stockée | Description |
|---|---|---|
backlog |
backlog |
Saisi, pas encore trié dans la file active |
open |
open |
Trié et prêt à être travaillé, pas encore commencé |
inProgress |
inProgress |
En cours de traitement actif |
blocked |
blocked |
Commencé, mais bloqué par un élément externe |
inReview |
inReview |
Travail terminé, en attente de révision |
done |
done |
Terminé avec succès (état terminal) |
failed |
failed |
Terminé avec une erreur (état terminal) |
cancelled |
cancelled |
Abandonné (état 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 plutôt que d’être convertie.
tryParseLoose accepte les alias fournis par les agents, en ignorant la casse et 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 quand force est faux — les outils MCP et les réconciliateurs. Une transition illégale y est journalisée et ignorée. Tout changement de statut déclenché par l’utilisateur dans l’interface passe force: true et contourne entièrement le graphe ; une personne peut donc déplacer un ticket vers n’importe quel statut figurant dans le sélecteur (tous les statuts sauf failed), y compris rouvrir un état 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 |
— (état terminal) |
failed |
— (état terminal) |
cancelled |
— (état terminal) |
Priorités des tickets
Section intitulée “Priorités des tickets”Stockée sous forme d’entier sur l’échelle native 0–4 de Linear. Une valeur inconnue est interprétée comme none.
| Priorité | Stockée sous |
|---|---|
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 | État |
|---|---|
local |
Stocké entièrement dans la base de données de l’espace de travail ; 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’un fournisseur d’adaptateur de
synchronisation (vendorId: github) — ce n’est pas une valeur de TicketProvider, donc un ticket
GitHub récupéré est stocké avec originKind: externalSync et provider: local par défaut. La
correspondance des fournisseurs vit dans ticket_sync_links.
Provenance des tickets
Section intitulée “Provenance des tickets”createTicket écrit manual. Une récupération chez un fournisseur écrit externalSync. Les
enfants délégués restent manual.
| Valeur | Signification |
|---|---|
manual |
Valeur par défaut de createTicket (lignes humaines, d’agent et déléguées) |
externalSync |
Défini par TicketSyncEngine lors d’une récupération chez un fournisseur |
Champs des tickets
Section intitulée “Champs des tickets”L’agrégat couvre deux préoccupations sur une seule ligne. Le miroir (fournisseur, clés, titre, description, priorité, étiquettes, statuts, horodatages) est un cache pour un fournisseur distant et est réécrit au complet par une actualisation. La surcouche (attribution, délégation, espace, parent, projet, PR liées) n’existe que dans 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 (par défaut local) |
externalKey |
String? |
Clé propre au fournisseur (p. ex. LIN-123). Le createTicket local y écrit un UUID distinct, et non le id de la ligne |
url |
String? |
URL Web chez le fournisseur distant |
title |
String |
Court résumé ; ne doit pas être vide |
description |
String? |
Corps Markdown plus long |
priority |
TicketPriority |
Valeur par défaut none |
labels |
List<String> |
Étiquettes libres |
status |
TicketStatus |
Statut normalisé canonique |
rawStatus |
String? |
Nom d’état natif du fournisseur distant, conservé pour un affichage sans perte |
externalSyncedAt |
DateTime? |
Dernière synchronisation multifournisseur ; nul avant la première. La correspondance par fournisseur vit dans ticket_sync_links |
parentTicketId |
String? |
Parent dans l’arbre de délégation / de décomposition |
projectId |
String? |
Projet propriétaire ; jamais transmis à un système distant |
assignedAgentId |
String? |
Identifiant du principal attribué (métadonnées seulement — attribuer ne lance rien) |
assigneeType |
PrincipalType |
Type de principal que désigne assignedAgentId — user ou agent (par défaut agent) |
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 attribuée (métadonnées seulement) |
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é tiré, ou espace de l’agent délégant |
errorMessage |
String? |
Défini quand status vaut failed |
linkedPrIds |
List<String> |
Identifiants de nœud PR auxquels ce ticket est lié |
metadata |
Map<String, dynamic> |
Fourre-tout libre |
createdAt |
DateTime |
Horodatage de création |
startedAt |
DateTime? |
Moment où le travail a commencé |
blockedAt |
DateTime? |
Moment où le ticket a été bloqué |
cancelledAt |
DateTime? |
Moment où le ticket a été annulé |
completedAt |
DateTime? |
Moment où le ticket s’est terminé avec succès |
finishedAt |
DateTime? |
Moment où 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 |
Comment le ticket a été créé (par défaut manual) |
collaborators |
List<TicketCollaborator> |
Hydraté à la demande par le dépôt |
Dérivés : isTerminal, isRemote (provider != local), isAssignedToUser et displayKey (externalKey ?? id — un ticket local affiche donc l’UUID distinct généré à la création, et non le id de la ligne).
Relations entre tickets
Section intitulée “Relations entre tickets”Les arêtes stockées sont des lignes directionnelles 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 utilisé par l’interface et par ticket_relation est TicketRelationKind, dérivé d’une arête stockée et de l’extrémité où 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 de parent et de sous-ticket ne sont pas des lignes de ticket_links — ils vivent dans 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 aucun TicketStarted, TicketDelegated, TicketCollaboratorAdded ni ExternalTicketWebhookReceived.
| Événement | Déclenché quand |
|---|---|
TicketCreated |
Le ticket est créé |
TicketAssigned |
Le ticket est attribué à 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éattribué |
TicketDetailsUpdated |
Le titre, la description, la priorité ou les étiquettes changent |
TicketAssigned est un signal de vérification 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 responsable de l’équipe. L’ajout d’un collaborateur écrit une ligne et ne publie rien. Un webhook de fournisseur est appliqué par 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 quand le workspaceId de la ligne chargée ne correspond pas à celui de l’appelant |
| Concurrence optimiste | _mutate écrit avec expectedVersion fixé à la version qu’il vient de lire et réessaie sur ConcurrencyConflictException en relisant l’état à jour |
| Réservation exclusive | tryCheckout / releaseCheckout ; un deuxième agent qui réclame un ticket déjà réservé reçoit une CheckoutConflictException |
| Garde de transition | canTransitionTo, appliqué seulement quand force est faux |