Життєвий цикл тікета
Статуси тікета
Розділ із назвою «Статуси тікета»| Статус | Рядок у сховищі | Опис |
|---|---|---|
backlog |
backlog |
Зафіксовано, ще не розібрано в активну чергу |
open |
open |
Розібрано й готове до роботи, ще не розпочате |
inProgress |
inProgress |
Активно виконується |
blocked |
blocked |
Розпочато, але заблоковано чимось зовнішнім |
inReview |
inReview |
Роботу виконано, очікує перевірки |
done |
done |
Завершено успішно (термінальний) |
failed |
failed |
Завершено з помилкою (термінальний) |
cancelled |
cancelled |
Покинуто (термінальний) |
Значення статусів зберігаються в camelCase (inProgress, inReview), ніколи не in_progress. Null-значення у сховищі розбирається як open; невідоме значення кидає виняток, а не зводиться до чогось іншого.
tryParseLoose приймає псевдоніми, надані агентом, ігноруючи регістр і - / _ / пробіл:
| Статус | Прийняті псевдоніми |
|---|---|
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Цей граф застосовується лише до агентів і автоматизації. TicketWorkflowService.transitionStatus перевіряє його, коли force дорівнює false, — це інструменти MCP та узгоджувачі. Неприпустимий перехід там записується в журнал і ігнорується. Кожна зміна статусу з боку користувача в інтерфейсі передає force: true і повністю обминає граф, тож людина може перевести тікет у будь-який статус зі списку селектора (кожен статус, крім failed), зокрема відкрити термінальний заново. Агенти позначають failed через fail_ticket.
Статус завжди може перейти сам у себе. Термінальні стани не мають вихідних ребер.
| Зі статусу | Дозволені цілі |
|---|---|
backlog |
open, cancelled |
open |
inProgress, blocked, cancelled |
inProgress |
blocked, inReview, done, failed, cancelled |
blocked |
inProgress, cancelled |
inReview |
inProgress, done, failed, cancelled |
done |
— (термінальний) |
failed |
— (термінальний) |
cancelled |
— (термінальний) |
Пріоритети тікета
Розділ із назвою «Пріоритети тікета»Зберігається як ціле число у власній шкалі Linear 0–4. Невідоме значення розбирається як none.
| Пріоритет | Зберігається як |
|---|---|
none |
0 |
urgent |
1 |
high |
2 |
medium |
3 |
low |
4 |
Постачальники тікетів
Розділ із назвою «Постачальники тікетів»Невідоме або null-значення у сховищі розбирається як local.
| Постачальник | Стан |
|---|---|
local |
Зберігається цілком у базі даних робочого простору; типово |
linear |
Належить віддаленій системі, дзеркалиться локально |
Адаптери синхронізації живуть у packages/cc_infra/lib/src/tickets/sync/ для Linear,
GitHub Issues, Jira Cloud і ClickUp. GitHub Issues — це лише постачальник
адаптера синхронізації (vendorId: github), а не значення TicketProvider,
тож витягнутий issue з GitHub зберігається з originKind: externalSync і
типовим provider: local. Зіставлення постачальників живе в ticket_sync_links.
Походження тікета
Розділ із назвою «Походження тікета»createTicket записує manual. Витяг від постачальника записує externalSync.
Делеговані дочірні тікети залишаються manual.
| Значення | Опис |
|---|---|
manual |
Типове для createTicket (рядки від людини, агента й делеговані) |
externalSync |
Встановлює TicketSyncEngine під час витягу від постачальника |
Поля тікета
Розділ із назвою «Поля тікета»Агрегат поєднує дві площини в одному рядку. Дзеркало (постачальник, ключі, заголовок, опис, пріоритет, мітки, статуси, часові мітки) — це кеш для віддаленого постачальника, який оновлення перезаписує повністю. Оверлей (призначення, делегування, простір, батьківський тікет, проєкт, пов’язані PR) існує лише в Control Center, і оновлення ніколи його не торкається.
| Поле | Тип | Опис |
|---|---|---|
id |
String |
UUID v4 |
workspaceId |
String |
Робочий простір-власник |
provider |
TicketProvider |
Бекенд, який володіє канонічними даними (типово local) |
externalKey |
String? |
Ключ, рідний для постачальника (наприклад, LIN-123). Локальний createTicket записує сюди окремий UUID, а не id рядка |
url |
String? |
Веб-URL у віддаленого постачальника |
title |
String |
Короткий підсумок; не може бути порожнім |
description |
String? |
Довше тіло у форматі Markdown |
priority |
TicketPriority |
Типово none |
labels |
List<String> |
Мітки довільної форми |
status |
TicketStatus |
Канонічний нормалізований статус |
rawStatus |
String? |
Рідна назва стану у віддаленого постачальника, збережена для показу без втрат |
externalSyncedAt |
DateTime? |
Остання синхронізація між кількома постачальниками; null до першої. Зіставлення для кожного постачальника живе в ticket_sync_links |
parentTicketId |
String? |
Батько в дереві делегування / розбиття |
projectId |
String? |
Проєкт-власник; ніколи не надсилається на віддалену систему |
assignedAgentId |
String? |
id призначеного суб’єкта (лише метадані — призначення нічого не запускає) |
assigneeType |
PrincipalType |
Який різновид суб’єкта називає assignedAgentId — user чи agent (типово agent) |
createdByType |
PrincipalType? |
Різновид суб’єкта, який створив тікет (user чи agent); null для застарілих/системних рядків |
createdById |
String? |
id суб’єкта, який створив тікет |
assignedTeamId |
String? |
Призначена команда (лише метадані) |
delegatedByAgentId |
String? |
Агент, який делегував цей тікет |
delegationDepth |
int |
Глибина в ланцюгу делегування; корінь — 0, перевіряється проти обмеження максимальної глибини |
delegationRootTicketId |
String? |
Корінь ланцюга делегування; null для кореневого тікета |
spaceId |
String? |
Розмова, з якої виокремився тікет, або простір агента, що делегує |
errorMessage |
String? |
Встановлюється, коли status дорівнює failed |
linkedPrIds |
List<String> |
id вузлів PR, з якими пов’язано цей тікет |
metadata |
Map<String, dynamic> |
Контейнер довільної форми |
createdAt |
DateTime |
Час створення |
startedAt |
DateTime? |
Коли робота почалася |
blockedAt |
DateTime? |
Коли тікет було заблоковано |
cancelledAt |
DateTime? |
Коли тікет було скасовано |
completedAt |
DateTime? |
Коли тікет успішно завершився |
finishedAt |
DateTime? |
Коли тікет досяг будь-якого термінального стану |
updatedAt |
DateTime |
Остання зміна (оновлення дзеркала або зміна оверлею) |
version |
int |
Лічильник оптимістичної конкурентності, який зростає за кожної зміни |
originKind |
TicketOriginKind |
Як тікет було створено (типово manual) |
collaborators |
List<TicketCollaborator> |
Наповнюються репозиторієм на вимогу |
Похідні: isTerminal, isRemote (provider != local), isAssignedToUser і displayKey (externalKey ?? id — тож локальні тікети показують окремий UUID, створений під час створення, а не id рядка).
Зв’язки тікета
Розділ із назвою «Зв’язки тікета»Збережені ребра — це напрямлені рядки в ticket_links:
| Тип | Рядок у сховищі | Значення |
|---|---|---|
blocks |
blocks |
Джерело блокує ціль |
relatesTo |
relates_to |
Симетричний зв’язок |
duplicateOf |
duplicate_of |
Джерело є дублікатом цілі |
Словник, яким послуговуються інтерфейс і ticket_relation, — це TicketRelationKind, виведений зі збереженого ребра плюс те, на якому кінці стоїть суб’єкт:
| Вид | Виведено з |
|---|---|
blockedBy |
Рядок blocks, де суб’єкт — ціль |
blocking |
Рядок blocks, де суб’єкт — джерело |
relatedTo |
Рядок relates_to (симетричний) |
duplicateOf |
Рядок duplicate_of, де суб’єкт — джерело |
duplicatedBy |
Рядок duplicate_of, де суб’єкт — ціль |
subIssueOf |
tickets.parent_ticket_id на суб’єкті |
parentOf |
tickets.parent_ticket_id на іншому тікеті |
Зв’язки батьківського тікета й підзадачі — не рядки ticket_links; вони живуть у tickets.parent_ticket_id.
Доменні події
Розділ із назвою «Доменні події»Ці вісім класів живуть у packages/cc_domain/lib/core/domain/events/ticketing_events.dart. Немає TicketStarted, TicketDelegated, TicketCollaboratorAdded чи ExternalTicketWebhookReceived.
| Подія | Коли спрацьовує |
|---|---|
TicketCreated |
Тікет створено |
TicketAssigned |
Тікет призначено суб’єкту або команді |
TicketCompleted |
Робота успішно завершується |
TicketFailed |
Робота зазнає невдачі |
TicketCancelled |
Тікет скасовано |
TicketStatusChanged |
Будь-яка зміна статусу |
TicketReassigned |
Тікет перепризначено |
TicketDetailsUpdated |
Змінюються заголовок, опис, пріоритет або мітки |
TicketAssigned — це сигнал аудиту й сповіщень та тригер конвеєра. Призначення агенту не запускає жодного запуску — немає диспетчера тікетів, який стежив би за assignedAgentId. Призначення команді — виняток: TeamRoutingService запускає лідера команди. Додавання учасника записує рядок і нічого не публікує. Вебхук від постачальника застосовується через TicketSyncEngine без доменної події.
Контрольна точка змін
Розділ із назвою «Контрольна точка змін»TicketWorkflowService володіє кожною зміною.
| Механізм | Деталі |
|---|---|
| Єдина контрольна точка | Усі зміни проходять через _mutate, який завантажує рядок і перед застосуванням викликає _assertWorkspace |
| Ізоляція робочого простору | _assertWorkspace кидає WorkspaceMismatchException, коли workspaceId завантаженого рядка не збігається з workspaceId того, хто викликає |
| Оптимістична конкурентність | _mutate записує з expectedVersion, встановленим у щойно прочитаний version, і повторює спробу на ConcurrencyConflictException, перечитуючи свіжий стан |
| Виключне резервування | tryCheckout / releaseCheckout; другий агент, який претендує на вже зарезервований тікет, отримує CheckoutConflictException |
| Запобіжник переходів | canTransitionTo, застосовується лише коли force дорівнює false |