跳到內容

工單生命週期

狀態 儲存字串 說明
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 時套用