Aller au contenu

Cycle de vie 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
backlog ──→ open ──→ inProgress ──→ inReview ──→ done
│ │ │
│ └──→ failed ┘
└──→ blocked ──→ inProgress
Any non-terminal status ──→ cancelled; inReview ──→ failed

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

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

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.

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

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

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.

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.

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