تخطَّ إلى المحتوى

دورة حياة التذكرة

الحالة سلسلة التخزين الوصف
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