رفتن به محتوا

چرخهٔ عمر تیکت

وضعیت رشتهٔ ذخیره‌سازی توضیح
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 ذخیره می‌شود. یک مقدار ناشناخته به none تفسیر می‌شود.

اولویت ذخیره‌شده به‌صورت
none 0
urgent 1
high 2
medium 3
low 4

یک مقدار ذخیره‌شدهٔ ناشناخته یا null به local تفسیر می‌شود.

ارائه‌دهنده وضعیت
local تماماً در پایگاه‌دادهٔ فضای کاری ذخیره می‌شود؛ پیش‌فرض
linear در مالکیت راه دور، به‌صورت محلی آینه می‌شود

آداپتورهای همگام‌سازی برای Linear، GitHub Issues، Jira Cloud و ClickUp زیر packages/cc_infra/lib/src/tickets/sync/ زندگی می‌کنند. GitHub Issues فقط یک فروشندهٔ آداپتور همگام‌سازی است (vendorId: github) — یک مقدار TicketProvider نیست، بنابراین یک ایشوی واکشی‌شده از 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? شناسهٔ principal تخصیص‌یافته (فقط فراداده — تخصیص دادن چیزی را اعزام نمی‌کند)
assigneeType PrincipalType اینکه assignedAgentId نام کدام نوع principal را می‌برد — user یا agent (پیش‌فرض agent)
createdByType PrincipalType? نوع principalی که تیکت را ساخت (user یا agent)؛ برای ردیف‌های قدیمی/سیستمی null است
createdById String? شناسهٔ principal سازنده
assignedTeamId String? تیم تخصیص‌یافته (فقط فراداده)
delegatedByAgentId String? عاملی که این تیکت را واگذار کرد
delegationDepth int عمق در زنجیرهٔ واگذاری؛ ریشه 0 است و با سقف بیشینهٔ عمق بررسی می‌شود
delegationRootTicketId String? ریشهٔ زنجیرهٔ واگذاری؛ برای یک تیکت ریشه null است
spaceId String? گفت‌وگویی که تیکت از آن بیرون کشیده شد، یا فضای عامل واگذارکننده
errorMessage String? وقتی status برابر failed است تنظیم می‌شود
linkedPrIds List<String> شناسه‌های گره 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 تیکت به یک principal یا تیم تخصیص می‌یابد
TicketCompleted کار با موفقیت به پایان می‌رسد
TicketFailed کار شکست می‌خورد
TicketCancelled تیکت لغو می‌شود
TicketStatusChanged هر تغییر وضعیت
TicketReassigned تیکت دوباره تخصیص می‌یابد
TicketDetailsUpdated عنوان، شرح، اولویت یا برچسب‌ها تغییر می‌کند

TicketAssigned یک سیگنال حسابرسی و اعلان و یک تریگر پایپ‌لاین است. تخصیص به یک عامل هیچ اجرایی را شروع نمی‌کند — هیچ اعزام‌کنندهٔ تیکتی وجود ندارد که assignedAgentId را بپاید. تخصیص به یک تیم استثناست: TeamRoutingService سرگروه تیم را اعزام می‌کند. افزودن یک همکار یک ردیف می‌نویسد و هیچ چیزی منتشر نمی‌کند. یک وبهوک فروشنده بدون هیچ رویداد دامنه‌ای از راه TicketSyncEngine اعمال می‌شود.

TicketWorkflowService مالک هر جهش است.

سازوکار جزئیات
گلوگاه واحد همهٔ جهش‌ها از _mutate عبور می‌کنند، که ردیف را بارگذاری می‌کند و پیش از اعمال، _assertWorkspace را صدا می‌زند
ایزوله‌سازی فضای کاری _assertWorkspace وقتی workspaceId ردیف بارگذاری‌شده با فضای کاری فراخوان نمی‌خواند، WorkspaceMismatchException می‌اندازد
همزمانی خوش‌بینانه _mutate با expectedVersion برابر با versionی که تازه خوانده می‌نویسد و در صورت ConcurrencyConflictException با بازخوانی وضعیت تازه دوباره می‌کوشد
ادعای انحصاری tryCheckout / releaseCheckout؛ عامل دومی که تیکتی را که پیش‌تر checkout شده است ادعا کند، CheckoutConflictException می‌گیرد
گارد گذار canTransitionTo، فقط وقتی force برابر false است اعمال می‌شود