Lewati ke konten

Siklus hidup tiket

Status String penyimpanan Deskripsi
backlog backlog Tercatat, belum ditriase ke antrean aktif
open open Sudah ditriase dan siap dikerjakan, belum dimulai
inProgress inProgress Sedang dikerjakan secara aktif
blocked blocked Sudah dimulai tetapi tertahan oleh hal eksternal
inReview inReview Pekerjaan selesai, menunggu tinjauan
done done Selesai dengan sukses (terminal)
failed failed Selesai dengan galat (terminal)
cancelled cancelled Ditinggalkan (terminal)

Nilai status disimpan dalam camelCase (inProgress, inReview), tidak pernah in_progress. Nilai tersimpan yang null diurai sebagai open; nilai yang tidak dikenal melempar galat alih-alih dikonversi paksa.

tryParseLoose menerima alias yang diberikan agen, dengan mengabaikan huruf besar-kecil serta - / _ / spasi:

Status Alias yang diterima
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

Graf ini hanya mengikat agen dan otomatisasi. TicketWorkflowService.transitionStatus memeriksanya saat force bernilai false — alat MCP dan reconciler. Transisi ilegal di sana dicatat ke log dan diabaikan. Setiap perubahan status yang dipicu pengguna di UI meneruskan force: true dan melewati graf sepenuhnya, sehingga manusia boleh memindahkan tiket ke status mana pun yang tercantum di pemilih (semua status kecuali failed), termasuk membuka kembali tiket berstatus terminal. Agen menandai failed dengan fail_ticket.

Sebuah status selalu boleh bertransisi ke dirinya sendiri. Status terminal tidak memiliki sisi keluar.

Dari Target yang diizinkan
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)

Disimpan sebagai bilangan bulat pada skala asli Linear 0–4. Nilai yang tidak dikenal diurai menjadi none.

Prioritas Disimpan sebagai
none 0
urgent 1
high 2
medium 3
low 4

Nilai tersimpan yang tidak dikenal atau null diurai menjadi local.

Penyedia Status
local Disimpan sepenuhnya di basis data ruang kerja; default
linear Dimiliki jarak jauh, dicerminkan secara lokal

Adaptor sinkronisasi berada di bawah packages/cc_infra/lib/src/tickets/sync/ untuk Linear, GitHub Issues, Jira Cloud, dan ClickUp. GitHub Issues hanyalah vendor adaptor sinkronisasi (vendorId: github) — bukan nilai TicketProvider, sehingga issue GitHub yang ditarik disimpan dengan originKind: externalSync dan provider: local default. Pemetaan vendor berada di ticket_sync_links.

createTicket menulis manual. Penarikan dari vendor menulis externalSync. Anak yang didelegasikan tetap manual.

Nilai Arti
manual Default pada createTicket (baris manusia, agen, dan delegasi)
externalSync Ditetapkan oleh TicketSyncEngine saat penarikan dari vendor

Agregat ini mencakup dua urusan pada satu baris. Cermin (penyedia, kunci, judul, deskripsi, prioritas, label, status, stempel waktu) adalah cache untuk penyedia jarak jauh dan ditulis ulang sepenuhnya oleh penyegaran. Lapisan tambahan (penugasan, delegasi, space, induk, proyek, PR tertaut) hanya milik Control Center dan penyegaran tidak pernah menyentuhnya.

Kolom Tipe Deskripsi
id String UUID v4
workspaceId String Ruang kerja yang memiliki
provider TicketProvider Backend yang memiliki data kanonis (default local)
externalKey String? Kunci asli penyedia (mis. LIN-123). createTicket lokal menulis UUID terpisah di sini, bukan id baris
url String? URL web pada penyedia jarak jauh
title String Ringkasan singkat; tidak boleh kosong
description String? Isi Markdown yang lebih panjang
priority TicketPriority Default none
labels List<String> Label bentuk bebas
status TicketStatus Status kanonis yang dinormalisasi
rawStatus String? Nama state asli penyedia jarak jauh, disimpan demi tampilan tanpa kehilangan informasi
externalSyncedAt DateTime? Sinkronisasi multi-vendor terakhir; null hingga yang pertama. Pemetaan per vendor berada di ticket_sync_links
parentTicketId String? Induk dalam pohon delegasi / perincian
projectId String? Proyek yang memiliki; tidak pernah didorong ke jarak jauh
assignedAgentId String? Id prinsipal penerima tugas (hanya metadata — menugaskan tidak mengirim apa pun)
assigneeType PrincipalType Jenis prinsipal yang ditunjuk assignedAgentId — user atau agent (default agent)
createdByType PrincipalType? Jenis prinsipal yang membuat tiket (user atau agent); null untuk baris lama/sistem
createdById String? Id prinsipal pembuat
assignedTeamId String? Tim penerima tugas (hanya metadata)
delegatedByAgentId String? Agen yang mendelegasikan tiket ini
delegationDepth int Kedalaman dalam rantai delegasi; akar bernilai 0, diperiksa terhadap batas kedalaman maksimum
delegationRootTicketId String? Akar rantai delegasi; null untuk tiket akar
spaceId String? Percakapan asal tiket dibentuk, atau space agen yang mendelegasikan
errorMessage String? Ditetapkan saat status bernilai failed
linkedPrIds List<String> Id node PR yang ditautkan ke tiket ini
metadata Map<String, dynamic> Kantong bentuk bebas
createdAt DateTime Waktu pembuatan
startedAt DateTime? Saat pekerjaan dimulai
blockedAt DateTime? Saat tiket tertahan
cancelledAt DateTime? Saat tiket dibatalkan
completedAt DateTime? Saat tiket selesai dengan sukses
finishedAt DateTime? Saat tiket mencapai status terminal apa pun
updatedAt DateTime Mutasi terakhir (penyegaran cermin atau perubahan lapisan tambahan)
version int Penghitung konkurensi optimistis, ditambah pada setiap mutasi
originKind TicketOriginKind Cara tiket dibuat (default manual)
collaborators List<TicketCollaborator> Dihidrasi oleh repositori sesuai permintaan

Turunan: isTerminal, isRemote (provider != local), isAssignedToUser, dan displayKey (externalKey ?? id — karena itu tiket lokal menampilkan UUID terpisah saat pembuatan, bukan id baris).

Sisi tersimpan adalah baris berarah di ticket_links:

Jenis String penyimpanan Arti
blocks blocks Sumber memblokir target
relatesTo relates_to Relasi simetris
duplicateOf duplicate_of Sumber adalah duplikat dari target

Kosakata yang dipakai UI dan ticket_relation adalah TicketRelationKind, yang diturunkan dari sisi tersimpan plus ujung mana yang menjadi subjek:

Jenis Diturunkan dari
blockedBy Baris blocks dengan subjek sebagai target
blocking Baris blocks dengan subjek sebagai sumber
relatedTo Baris relates_to (simetris)
duplicateOf Baris duplicate_of dengan subjek sebagai sumber
duplicatedBy Baris duplicate_of dengan subjek sebagai target
subIssueOf tickets.parent_ticket_id pada subjek
parentOf tickets.parent_ticket_id pada tiket lainnya

Tautan induk dan sub-isu bukan baris ticket_links — tautan itu berada di tickets.parent_ticket_id.

Delapan kelas ini berada di packages/cc_domain/lib/core/domain/events/ticketing_events.dart. Tidak ada TicketStarted, TicketDelegated, TicketCollaboratorAdded, atau ExternalTicketWebhookReceived.

Peristiwa Dipicu saat
TicketCreated Tiket dibuat
TicketAssigned Tiket ditugaskan ke prinsipal atau tim
TicketCompleted Pekerjaan selesai dengan sukses
TicketFailed Pekerjaan gagal
TicketCancelled Tiket dibatalkan
TicketStatusChanged Setiap perubahan status
TicketReassigned Tiket ditugaskan ulang
TicketDetailsUpdated Judul, deskripsi, prioritas, atau label berubah

TicketAssigned adalah sinyal audit dan notifikasi sekaligus pemicu pipeline. Penugasan kepada agen tidak memulai run apa pun — tidak ada pengirim tiket yang memantau assignedAgentId. Penugasan kepada tim adalah pengecualiannya: TeamRoutingService mengirimkan pemimpin tim. Menambahkan kolaborator menulis satu baris dan tidak menerbitkan apa pun. Webhook vendor diterapkan melalui TicketSyncEngine tanpa peristiwa domain.

TicketWorkflowService memegang setiap mutasi.

Mekanisme Detail
Titik penyempitan tunggal Semua mutasi dialirkan melalui _mutate, yang memuat barisnya dan memanggil _assertWorkspace sebelum menerapkan
Isolasi ruang kerja _assertWorkspace melempar WorkspaceMismatchException saat workspaceId baris yang dimuat tidak cocok dengan milik pemanggil
Konkurensi optimistis _mutate menulis dengan expectedVersion diatur ke version yang baru dibacanya dan mencoba ulang saat ConcurrencyConflictException dengan membaca ulang state yang baru
Klaim eksklusif tryCheckout / releaseCheckout; agen kedua yang mengklaim tiket yang sudah di-checkout mendapat CheckoutConflictException
Pagar transisi canTransitionTo, hanya diterapkan saat force bernilai false