ข้ามไปยังเนื้อหา

วงจรชีวิตตั๋วงาน

สถานะ สตริงที่จัดเก็บ คำอธิบาย
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 และตัวกระทบยอด การเปลี่ยนสถานะที่ผิดกฎตรงจุดนั้นจะถูกบันทึกและเพิกเฉย ทุกการเปลี่ยนสถานะที่ผู้ใช้สั่งใน UI จะส่ง 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 — (สถานะสิ้นสุด)

จัดเก็บเป็นจำนวนเต็มตามสเกล 0–4 ดั้งเดิมของ Linear ค่าที่ไม่รู้จักจะถูกแปลงเป็น 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 issue ที่ถูกดึงมาจึงถูกจัดเก็บด้วย 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 ต้นทางเป็นสำเนาซ้ำของปลายทาง

คำศัพท์ที่ UI และ 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 ของแถวที่โหลดมาไม่ตรงกับของผู้เรียก
การทำงานพร้อมกันแบบออปติมิสติก _mutate เขียนโดยตั้ง expectedVersion เป็น version ที่เพิ่งอ่านมา และลองใหม่เมื่อเกิด ConcurrencyConflictException โดยอ่านสถานะใหม่ล่าสุด
การจองแบบเอกสิทธิ์ tryCheckout / releaseCheckout; เอเจนต์ตัวที่สองที่จองตั๋วงานที่ถูกเช็คเอาต์อยู่จะได้รับ CheckoutConflictException
ตัวป้องกันการเปลี่ยนสถานะ canTransitionTo ใช้เฉพาะเมื่อ force เป็น false