دورة حياة التذكرة
حالات التذكرة
قسم بعنوان “حالات التذكرة”| الحالة | سلسلة التخزين | الوصف |
|---|---|---|
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، فتُخزَّن مشكلة GitHub المسحوبة بقيمة originKind: externalSync والافتراضي provider: local. تعيين المورّد يعيش على ticket_sync_links.
منشأ التذكرة
قسم بعنوان “منشأ التذكرة”createTicket يكتب manual. السحب من المورّد يكتب externalSync. الأبناء المفوَّضون يبقون manual.
| القيمة | المعنى |
|---|---|
manual |
الافتراضي عند createTicket (صفوف الإنسان والوكيل والمفوَّضين) |
externalSync |
يضبطها TicketSyncEngine عند السحب من المورّد |
حقول التذكرة
قسم بعنوان “حقول التذكرة”يجمع الكيان جهتين على صف واحد. المرآة (المزوّد والمفاتيح والعنوان والوصف والأولوية والتسميات والحالات والطوابع الزمنية) ذاكرة مؤقتة لمزوّد بعيد، وإعادة الجلب تعيد كتابتها كلها. التراكب (الإسناد والتفويض والمساحة والأصل والمشروع وطلبات السحب المرتبطة) خاص بـ 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 |
أي نوع من Principal يسمّيه assignedAgentId، 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> |
معرّفات عقد طلبات السحب المرتبطة بهذه التذكرة |
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 الاستثناء WorkspaceMismatchException حين لا يطابق workspaceId الصف المحمَّل مساحة عمل المستدعي |
| التزامن المتفائل | يكتب _mutate بقيمة expectedVersion تساوي version الذي قرأه للتو، ويعيد المحاولة عند ConcurrencyConflictException بإعادة قراءة الحالة الجديدة |
| مطالبة حصرية | tryCheckout / releaseCheckout؛ والوكيل الثاني الذي يطالب بتذكرة محجوزة يتلقى CheckoutConflictException |
| حارس الانتقال | canTransitionTo، ويُطبَّق فقط حين تكون force قيمة false |