変更リクエスト
セッションでの作業がレビューを経てデフォルトブランチに反映される仕組み。
変更リクエスト(CR)は、ある git ブランチを別のブランチにマージします。Beeuni は、セッションのブランチ(head_ref)からプロジェクトのデフォルトブランチ(base_ref、通常は main)への CR を作成します。CR の行はメタデータであり、マージ、差分、競合チェックはプロジェクトのリポジトリに対する実際の git 操作として実行されます。セッションの作業をデフォルトブランチに到達させる唯一の方法が CR です。
作業を CR 経由にする理由
セッションは、セッション ID にちなんで命名された独自のブランチ上のサンドボックスで実行されます。サンドボックスは永続しませんが、ブランチは永続します。セッションの作業を永続的に記録する唯一の場所が git だからです。新しいセッションはすべてデフォルトブランチから開始されます。CR がマージされるまでは、作業は独自のブランチに残り、レビューされず、他のエージェント、トリガー、コラボレーターから見えません。これは、コード、エージェント、スキル、マニフェスト(beeuni.yaml)を含むすべての変更に適用されます。例外はありません。
エージェントの義務
エージェントは、プロジェクトのデフォルトブランチに変更を取り込むために CR を開く必要があります。エージェント自身が CR をマージしてはいけません。マージはユーザーの判断です。次の契約に従ってください。
- セッションブランチ(
$BEEUNI_BRANCH_NAME)でコミットする。小さく、動作するコミットを作成する。履歴を書き換えたり、強制プッシュしたりしない。 - ブランチをプッシュする:
git push origin HEAD。 - CR を開く:
beeuni cr open --title "..." --description "..."。セッションサンドボックス内では、--headと--sessionのデフォルトは$BEEUNI_BRANCH_NAMEと$BEEUNI_SESSION_IDです。--baseのデフォルトはプロジェクトのデフォルトブランチです。 - ユーザーがレビューできるよう、CR 番号を伝える。
- 停止する。自分で CR をマージしない。
アンチパターン
- デフォルトブランチへの強制プッシュ。 バックエンドが許可している場合でも、レビューの契約に違反します。
- 「自分のブランチにあるので、自分で取得してください。」 CR が先にマージされていない限り、サンドボックスが停止するとセッションブランチは失われます。
- 変更をファイル、貼り付け、アーカイブとして送る。 CR システムがすでにこの問題を解決しています。
データモデル
CR は change_requests テーブルに保存されます。
| 列 | 型 | 注記 |
|---|---|---|
cr_id |
uuid | 主キー。REST API の識別子。 |
project_id |
uuid | CR が属するプロジェクト。 |
number |
integer | プロジェクトごとの表示番号(#1、#2…)。プロジェクトごとに一意。再利用されない。 |
title |
text | 必須。 |
description |
text | 空文字列がデフォルト。 |
base_ref |
text | マージ先のブランチ。通常は main。 |
head_ref |
text | マージ元のブランチ。セッションではセッション ID(UUID)。 |
status |
enum | open、merged、または closed。 |
head_commit_sha |
text | オープン中の CR では、読み取りのたびにライブの head_ref の先端に対して更新される。マージ済み CR ではマージ時に取得される。 |
base_commit_sha |
text | base_ref に対しても同じルール。 |
origin_session_id |
text | CR を開いたセッション。セッションが削除されると null に設定される。 |
created_by |
uuid | CR を作成したユーザー。 |
merged_at / merged_by |
timestamp / uuid | CR をマージした日時とユーザー。 |
merge_commit_sha |
text | マージコミット。fast-forward の場合は head_commit_sha と等しい。 |
closed_at / closed_by |
timestamp / uuid | マージせずに CR をクローズした日時とユーザー。 |
metadata |
jsonb | requested_changes を保持する。これは POST /:crId/request-changes が追加した {text, by, at} エントリのリスト。CR に独立したコメントテーブルはない。 |
created_at / updated_at |
timestamp | 作成時に設定される。ステータス変更または SHA 更新のたびに更新される。 |
(project_id, number) に一意なインデックスがあるため、UUID の代わりに短い番号で CR を参照できます。
ライフサイクル
open ──(merge)──▶ merged (terminal)
open ──(close)──▶ closed ──(reopen)──▶ open
openは開始時のステータスです。closedは元に戻せます。POST /:crId/reopenによりopenに戻ります。mergedは終端状態です。マージ済みの CR を再度開いたりクローズしたりすることはできません。マージ後の状態に対して新しい CR を開いてください。
Beeuni は、デフォルトブランチより先行するコミットがブランチにない CR の作成を拒否します。通常、これはエージェントがローカルでコミットしたものの、プッシュしていないことを意味します。コミットをプッシュしてから、もう一度 CR を作成してください。
SHA の更新
オープン中の CR では、Beeuni はすべての GET で、ライブブランチに対して head_commit_sha と base_commit_sha を更新します。リポジトリに到達できない場合、またはブランチが存在しない場合、Beeuni は更新をスキップし、CR の最後に確認されたメタデータを提供します。マージ済みの CR では、マージ時に取得された SHA が保持され、Beeuni が再度更新することはありません。
マージの仕組み
POST /v1/projects/:projectId/change-requests/:crId/merge は次の手順を実行します。
- Beeuni は
head_refからマニフェスト(beeuni.yaml)を読み取り、マニフェストスキーマに対して検証します。マニフェストがないブランチは合格します。無効なマニフェストの場合、code: "MANIFEST_INVALID"を伴う422を返してマージを停止します。 head_refがbase_refより厳密に先行している場合、Beeuni は そのブランチ を fast-forward します。- それ以外の場合、Beeuni はマージコミットを作成します。デフォルトメッセージは
Merge CR #<n>: <title>で、リクエスト本文のmessageで上書きできます。コミット作成者はBeeuni <noreply@beeuni.com>です。 - 競合がある場合、競合リストを伴う
409を返します。マージ前にGET /:crId/merge-previewで同じリストを確認できます。 - 成功すると、Beeuni は
statusをmergedに設定し、merged_at、merged_by、merge_commit_shaを記録して、プロジェクトの git キャッシュを無効化します。
open ではない CR をマージすると、409 が返されます。
マージプレビュー
GET /:crId/merge-preview は次を返します。
| フィールド | 型 | 意味 |
|---|---|---|
base_sha |
string | base_ref の現在の先端。 |
head_sha |
string | head_ref の現在の先端。 |
merge_base |
string | null | 共通の祖先。履歴が無関係の場合は null。 |
is_up_to_date |
boolean | head_ref が base_ref に完全にマージされているかどうか。 |
can_merge |
boolean | 競合がない。 |
can_fast_forward |
boolean | head_ref が base_ref より厳密に先行しているかどうか。 |
conflicts |
string[] | 競合するファイルパス。 |
REST API
すべてのルートは /v1/projects/:projectId/change-requests 配下にあります。
| メソッド | パス | 注記 |
|---|---|---|
| GET | / |
?status=open|merged|closed|all。フィルターを指定しない場合はすべてのステータスを返す。 |
| POST | / |
本文: {title, description?, head_ref, base_ref?, session_id?}。201 を返す。 |
| GET | /:crId |
CR を返す。副作用として SHA を更新する。 |
| PATCH | /:crId |
title または description を編集する。open でない場合は 409。 |
| GET | /:crId/diff |
統合パッチ: ファイル一覧、追加、削除。 |
| GET | /:crId/merge-preview |
上記のマージプレビューを参照。 |
| POST | /:crId/merge |
本文: {message?}。無効なマニフェストの場合は 422。競合時または open でない場合は 409。 |
| POST | /:crId/close |
すでに merged の場合は 409。 |
| POST | /:crId/reopen |
closed でない場合は 409。 |
| POST | /:crId/request-changes |
本文: {feedback}。metadata.requested_changes に追加し、元のセッションのエージェントを起動する。open でない場合は 409。 |
POST / は、base_ref より先行するコミットがない head_ref を拒否します: 422 code: "CR_HEAD_NOT_AHEAD"。これは、ブランチをプッシュする前に CR を開いた場合にエージェントが受け取るエラーです。
認証
読み取りルートにはプロジェクトへの読み取りアクセスが必要です。書き込みルートには書き込みアクセスが必要です。各書き込み操作にはさらに権限が必要で、完全なトークンの場合とセッションのスコープ付きトークンの場合を以下に示します。
| 操作 | 権限 |
|---|---|
| CR を開く | project.gitops.push |
| 変更を要求 | project.review.act |
| マージ | project.gitops.merge |
各アクションに1つの機能を割り当てます。これはフルトークンでも、セッションにスコープされたトークンでも同じです。オープンとマージは別々のリーフであるため、トークンには変更リクエストをオープンする権限だけを与え、マージする権限は与えないこともできます。これが、上記のエージェントへの権限付与の仕組みです。
project.cr.openとproject.cr.mergeは、カットオーバー前のproject.gitops.pushとproject.gitops.mergeの名称で、同じ機能の別名でした。これらのいずれかがまだ記載されているbeeuni.yamlも引き続き機能します(権限付与は、解決時に現在の名称へ書き換えられます)。ただし、新しく作成するものにはgitopsの名称を使用してください。
セッションには、どのような権限が付与されていても、自分でオープンした変更リクエストをマージすることはできません。このルールは構造上のものであり、機能ではありません。