コンテンツにスキップ

チケットのライフサイクル

ステータス 保存文字列 説明
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 リモートが所有し、ローカルにミラーリングされる

同期アダプターは 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 が設定

この集約は 1 つの行に 2 つの関心事をまたがって持ちます。ミラー(プロバイダー、キー、タイトル、説明、優先度、ラベル、ステータス、タイムスタンプ)はリモートプロバイダーのキャッシュであり、リフレッシュによって丸ごと書き換えられます。オーバーレイ(割り当て、委任、スペース、親、プロジェクト、リンクされた PR)は Control Center だけのものであり、リフレッシュが触れることはありません。

フィールド 型 説明
id String UUID v4
workspaceId String 所有するワークスペース
provider TicketProvider 正規データを所有するバックエンド(既定は local)
externalKey String? プロバイダー固有のキー(例: LIN-123)。ローカルの createTicket はここに別の UUID を書き込み、行の id は書き込みません
url String? リモートプロバイダー上の Web 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 — そのためローカルチケットは、行の id ではなく、作成時に付与された別の UUID を表示します)。

保存されるエッジは 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 にあります。

これら 8 つのクラスは 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 を経由し、_mutate は行をロードして適用前に _assertWorkspace を呼び出す
ワークスペース分離 ロードした行の workspaceId が呼び出し元のものと一致しない場合、_assertWorkspace は WorkspaceMismatchException をスローする
楽観的同時実行 _mutate は、読み取ったばかりの version を expectedVersion に設定して書き込み、ConcurrencyConflictException では最新の状態を再読み取りして再試行する
排他的なチェックアウト tryCheckout / releaseCheckout。チェックアウト済みのチケットを別のエージェントが要求すると CheckoutConflictException を受け取る
遷移ガード canTransitionTo。force が false のときにのみ適用される