---
title: "変更リクエスト"
description: "セッションでの作業がレビューを経てデフォルトブランチに反映される仕組み。"
---

変更リクエスト（CR）は、ある git ブランチを別のブランチにマージします。Beeuni は、セッションのブランチ（`head_ref`）からプロジェクトのデフォルトブランチ（`base_ref`、通常は `main`）への CR を作成します。CR の行はメタデータであり、マージ、差分、競合チェックはプロジェクトのリポジトリに対する実際の git 操作として実行されます。セッションの作業をデフォルトブランチに到達させる唯一の方法が CR です。

<a id="why-work-goes-through-a-cr"></a>

## 作業を CR 経由にする理由

セッションは、セッション ID にちなんで命名された独自のブランチ上のサンドボックスで実行されます。サンドボックスは永続しませんが、ブランチは永続します。セッションの作業を永続的に記録する唯一の場所が git だからです。新しいセッションはすべてデフォルトブランチから開始されます。CR がマージされるまでは、作業は独自のブランチに残り、レビューされず、他のエージェント、トリガー、コラボレーターから見えません。これは、コード、エージェント、スキル、マニフェスト（`beeuni.yaml`）を含むすべての変更に適用されます。例外はありません。

<a id="the-agent-mandate"></a>

## エージェントの義務

エージェントは、プロジェクトのデフォルトブランチに変更を取り込むために CR を開く必要があります。エージェント自身が CR をマージしてはいけません。マージはユーザーの判断です。次の契約に従ってください。

1. セッションブランチ（`$BEEUNI_BRANCH_NAME`）でコミットする。小さく、動作するコミットを作成する。履歴を書き換えたり、強制プッシュしたりしない。
2. ブランチをプッシュする: `git push origin HEAD`。
3. CR を開く: `beeuni cr open --title "..." --description "..."`。セッションサンドボックス内では、`--head` と `--session` のデフォルトは `$BEEUNI_BRANCH_NAME` と `$BEEUNI_SESSION_ID` です。`--base` のデフォルトはプロジェクトのデフォルトブランチです。
4. ユーザーがレビューできるよう、CR 番号を伝える。
5. 停止する。自分で CR をマージしない。

<a id="anti-patterns"></a>

### アンチパターン

- **デフォルトブランチへの強制プッシュ。** バックエンドが許可している場合でも、レビューの契約に違反します。
- **「自分のブランチにあるので、自分で取得してください。」** CR が先にマージされていない限り、サンドボックスが停止するとセッションブランチは失われます。
- **変更をファイル、貼り付け、アーカイブとして送る。** CR システムがすでにこの問題を解決しています。

<a id="data-model"></a>

## データモデル

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 を参照できます。

<a id="lifecycle"></a>

## ライフサイクル

```
open ──(merge)──▶ merged      (terminal)
open ──(close)──▶ closed ──(reopen)──▶ open
```

- `open` は開始時のステータスです。
- `closed` は元に戻せます。`POST /:crId/reopen` により `open` に戻ります。
- `merged` は終端状態です。マージ済みの CR を再度開いたりクローズしたりすることはできません。マージ後の状態に対して新しい CR を開いてください。

Beeuni は、デフォルトブランチより先行するコミットがブランチにない CR の作成を拒否します。通常、これはエージェントがローカルでコミットしたものの、プッシュしていないことを意味します。コミットをプッシュしてから、もう一度 CR を作成してください。

<a id="sha-refresh"></a>

## SHA の更新

オープン中の CR では、Beeuni はすべての `GET` で、ライブブランチに対して `head_commit_sha` と `base_commit_sha` を更新します。リポジトリに到達できない場合、またはブランチが存在しない場合、Beeuni は更新をスキップし、CR の最後に確認されたメタデータを提供します。マージ済みの CR では、マージ時に取得された SHA が保持され、Beeuni が再度更新することはありません。

<a id="merge-mechanics"></a>

## マージの仕組み

`POST /v1/projects/:projectId/change-requests/:crId/merge` は次の手順を実行します。

1. Beeuni は `head_ref` からマニフェスト（`beeuni.yaml`）を読み取り、マニフェストスキーマに対して検証します。マニフェストがないブランチは合格します。無効なマニフェストの場合、`code: "MANIFEST_INVALID"` を伴う `422` を返してマージを停止します。
2. `head_ref` が `base_ref` より厳密に先行している場合、Beeuni は そのブランチ を fast-forward します。
3. それ以外の場合、Beeuni はマージコミットを作成します。デフォルトメッセージは `Merge CR #<n>: <title>` で、リクエスト本文の `message` で上書きできます。コミット作成者は `Beeuni <noreply@beeuni.com>` です。
4. 競合がある場合、競合リストを伴う `409` を返します。マージ前に `GET /:crId/merge-preview` で同じリストを確認できます。
5. 成功すると、Beeuni は `status` を `merged` に設定し、`merged_at`、`merged_by`、`merge_commit_sha` を記録して、プロジェクトの git キャッシュを無効化します。

`open` ではない CR をマージすると、`409` が返されます。

<a id="merge-preview"></a>

### マージプレビュー

`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 を開いた場合にエージェントが受け取るエラーです。

<a id="authorization"></a>

### 認証

読み取りルートにはプロジェクトへの読み取りアクセスが必要です。書き込みルートには書き込みアクセスが必要です。各書き込み操作にはさらに権限が必要で、完全なトークンの場合とセッションのスコープ付きトークンの場合を以下に示します。

| 操作 | 権限 |
| --- | --- |
| 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`の名称を使用してください。

セッションには、どのような権限が付与されていても、自分でオープンした変更リクエストをマージすることはできません。このルールは構造上のものであり、機能ではありません。
