工單生命週期
工單狀態
標題為「工單狀態」的章節| 狀態 | 儲存字串 | 說明 |
|---|---|---|
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 以外的每一個狀態),包括重新開啟已進入終止狀態的工單。代理會以 fail_ticket 標記 failed。
狀態永遠可以轉換到自身。終止狀態沒有對外邊。
| 來源 | 允許的目標 |
|---|---|
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 |
由遠端擁有,在本機鏡像 |
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? |
被指派主體的 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> |
這張工單所連結的 PR 節點 id |
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 會派送團隊領導者。新增協作者只會寫入一筆資料列,不會發布任何事件。供應商 webhook 會透過 TicketSyncEngine 套用,不會產生領域事件。
變更關卡
標題為「變更關卡」的章節TicketWorkflowService 掌控每一項變更。
| 機制 | 細節 |
|---|---|
| 單一關卡 | 所有變更都經由 _mutate 處理,它會載入資料列,並在套用之前呼叫 _assertWorkspace |
| 工作區隔離 | 當載入資料列的 workspaceId 與呼叫端不符時,_assertWorkspace 會擲出 WorkspaceMismatchException |
| 樂觀併發 | _mutate 寫入時會把 expectedVersion 設為它剛讀到的 version,並在發生 ConcurrencyConflictException 時重新讀取最新狀態後重試 |
| 專屬認領 | tryCheckout / releaseCheckout;第二個代理若試圖認領已由他人認領的工單,會收到 CheckoutConflictException |
| 轉換防護 | canTransitionTo,僅在 force 為 false 時套用 |