# Solicitações de alteração

Como o trabalho da sessão chega à branch padrão por meio da revisão.

Canonical page: https://beeuni.ai/docs/pt/work/change-requests

Uma solicitação de alteração (CR) mescla um branch git a outro. O Beeuni cria uma CR a partir do branch de uma sessão (`head_ref`) para o branch padrão do projeto (`base_ref`, geralmente `main`). A linha da CR contém metadados; a mesclagem, o diff e as verificações de conflitos são executados como operações git reais no repositório do projeto. Uma CR é a única maneira de o trabalho de uma sessão chegar ao branch padrão.

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

## Por que o trabalho passa por uma CR

Uma sessão é executada em um sandbox no próprio branch, nomeado de acordo com o ID da sessão. O sandbox não dura para sempre, mas o branch dura: o git é o único registro durável do trabalho de uma sessão. Toda nova sessão começa no branch padrão. Até que uma CR seja mesclada, o trabalho permanece no próprio branch, sem revisão e invisível para todos os outros agentes, gatilhos e colaboradores. Isso se aplica a toda alteração: código, agentes, skills e o manifesto (`beeuni.yaml`) — sem exceções.

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

## O requisito para o agente

Um agente deve abrir uma CR para incorporar qualquer alteração ao branch padrão do projeto. O agente não mescla a própria CR — a mesclagem é uma decisão do usuário. Siga este contrato:

1. Faça commit no branch da sessão (`$BEEUNI_BRANCH_NAME`). Faça commits pequenos e funcionais. Não reescreva o histórico nem faça force-push.
2. Envie o branch: `git push origin HEAD`.
3. Abra a CR: `beeuni cr open --title "..." --description "..."`. Dentro de um sandbox de sessão, `--head` e `--session` têm como padrão `$BEEUNI_BRANCH_NAME` e `$BEEUNI_SESSION_ID`. `--base` tem como padrão o branch padrão do projeto.
4. Informe ao usuário o número da CR para que ele possa revisá-la.
5. Pare. Não mescle a CR você mesmo.

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

### Antipadrões

- **Fazer force-push no branch padrão.** Isso viola o contrato de revisão, mesmo quando o backend permite.
- **"Está no meu branch, baixe você mesmo."** O branch da sessão desaparece quando o sandbox é encerrado, a menos que uma CR o tenha mesclado antes.
- **Enviar a alteração como arquivo, texto colado ou arquivo compactado.** O sistema de CR já resolve esse problema.

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

## Modelo de dados

As CRs ficam na tabela `change_requests`.

| Coluna | Tipo | Observações |
| --- | --- | --- |
| `cr_id` | uuid | Chave primária. O identificador da API REST. |
| `project_id` | uuid | O projeto ao qual a CR pertence. |
| `number` | integer | Número de exibição por projeto (`#1`, `#2`…). Único por projeto. Nunca é reutilizado. |
| `title` | text | Obrigatório. |
| `description` | text | O padrão é uma string vazia. |
| `base_ref` | text | O branch no qual a mesclagem é feita. Geralmente `main`. |
| `head_ref` | text | O branch do qual a mesclagem é feita. Em uma sessão, é o ID da sessão (um UUID). |
| `status` | enum | `open`, `merged` ou `closed`. |
| `head_commit_sha` | text | Atualizado em relação ao tip ativo de `head_ref` a cada leitura, para CRs abertas. Capturado no momento da mesclagem para CRs mescladas. |
| `base_commit_sha` | text | Mesma regra, para `base_ref`. |
| `origin_session_id` | text | A sessão que abriu a CR. Definido como null se essa sessão for excluída. |
| `created_by` | uuid | O usuário que criou a CR. |
| `merged_at` / `merged_by` | timestamp / uuid | Quando e por quem a CR foi mesclada. |
| `merge_commit_sha` | text | O commit de mesclagem. Igual a `head_commit_sha` em um fast-forward. |
| `closed_at` / `closed_by` | timestamp / uuid | Quando e por quem a CR foi fechada sem mesclagem. |
| `metadata` | jsonb | Contém `requested_changes`, uma lista de entradas `{text, by, at}` adicionadas por `POST /:crId/request-changes`. As CRs não têm uma tabela de comentários separada. |
| `created_at` / `updated_at` | timestamp | Definidos na criação. Atualizados a cada alteração de status ou atualização de SHA. |

Um índice exclusivo em `(project_id, number)` permite referenciar uma CR pelo número curto em vez do UUID.

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

## Ciclo de vida

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

- `open` é o status inicial.
- `closed` é reversível. `POST /:crId/reopen` o define novamente como `open`.
- `merged` é terminal. Não é possível reabrir nem fechar uma CR mesclada. Abra uma nova CR com base no estado mesclado.

O Beeuni se recusa a criar uma CR cujo branch não tenha commits à frente do branch padrão. Isso geralmente significa que o agente fez commit localmente, mas nunca enviou o branch. Envie os commits e crie a CR novamente.

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

## Atualização de SHA

Para uma CR aberta, o Beeuni atualiza `head_commit_sha` e `base_commit_sha` em relação aos branches ativos a cada `GET`. Se o repositório estiver inacessível ou um branch estiver ausente, o Beeuni ignora a atualização e disponibiliza os últimos metadados conhecidos da CR. Uma CR mesclada mantém os SHAs capturados no momento da mesclagem — o Beeuni nunca os atualiza novamente.

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

## Mecânica de mesclagem

`POST /v1/projects/:projectId/change-requests/:crId/merge` executa estas etapas.

1. O Beeuni lê o manifesto (`beeuni.yaml`) de `head_ref` e o valida em relação ao esquema do manifesto. Um branch sem manifesto é aceito. Um manifesto inválido retorna `422` com `code: "MANIFEST_INVALID"` e interrompe a mesclagem.
2. O Beeuni faz fast-forward de `base_ref` se `head_ref` estiver estritamente à frente dele.
3. Caso contrário, o Beeuni cria um commit de mesclagem. A mensagem padrão é `Merge CR #<n>: <title>`, e você pode substituí-la usando `message` no corpo da solicitação. O autor do commit é `Beeuni <noreply@beeuni.com>`.
4. Um conflito retorna `409` com a lista de conflitos. Verifique a mesma lista com `GET /:crId/merge-preview` antes de mesclar.
5. Em caso de sucesso, o Beeuni define `status` como `merged`, registra `merged_at`, `merged_by` e `merge_commit_sha` e invalida o cache git do projeto.

A mesclagem de uma CR que não esteja `open` retorna `409`.

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

### Prévia da mesclagem

`GET /:crId/merge-preview` retorna:

| Campo | Tipo | Significado |
| --- | --- | --- |
| `base_sha` | string | Tip atual de `base_ref`. |
| `head_sha` | string | Tip atual de `head_ref`. |
| `merge_base` | string \| null | Ancestral comum. Null se os históricos não tiverem relação. |
| `is_up_to_date` | boolean | `head_ref` está totalmente mesclado em `base_ref`. |
| `can_merge` | boolean | Sem conflitos. |
| `can_fast_forward` | boolean | `head_ref` está estritamente à frente de `base_ref`. |
| `conflicts` | string[] | Caminhos de arquivos que entrariam em conflito. |

<a id="rest-api"></a>

## API REST

Todas as rotas ficam sob `/v1/projects/:projectId/change-requests`.

| Método | Caminho | Observações |
| --- | --- | --- |
| GET | `/` | `?status=open\|merged\|closed\|all`. Sem filtro, retorna todos os status. |
| POST | `/` | Corpo: `{title, description?, head_ref, base_ref?, session_id?}`. Retorna `201`. |
| GET | `/:crId` | Retorna a CR. Atualiza os SHAs como efeito colateral. |
| PATCH | `/:crId` | Edita `title` ou `description`. `409` se não estiver `open`. |
| GET | `/:crId/diff` | Patch unificado: lista de arquivos, adições e exclusões. |
| GET | `/:crId/merge-preview` | Consulte Prévia da mesclagem acima. |
| POST | `/:crId/merge` | Corpo: `{message?}`. `422` quando o manifesto é inválido. `409` em caso de conflito ou se não estiver `open`. |
| POST | `/:crId/close` | `409` se já estiver `merged`. |
| POST | `/:crId/reopen` | `409` se não estiver `closed`. |
| POST | `/:crId/request-changes` | Corpo: `{feedback}`. Anexa a `metadata.requested_changes` e desperta o agente da sessão de origem. `409` se não estiver `open`. |

`POST /` rejeita uma `head_ref` sem commits à frente de `base_ref`: `422 code: "CR_HEAD_NOT_AHEAD"`. Esse é o erro que um agente vê se abrir uma CR antes de enviar o branch.

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

### Autorização

As rotas de leitura precisam de acesso de leitura ao projeto. As rotas de escrita precisam de acesso de escrita. Cada ação de escrita também precisa de uma capacidade, mostrada abaixo para um token completo e para o token com escopo de uma sessão.

| Ação | Capacidade |
| --- | --- |
| Abrir uma CR | `project.gitops.push` |
| Solicitar alterações | `project.review.act` |
| Mesclar | `project.gitops.merge` |

Uma capacidade por ação, tanto para um token completo quanto para o token com escopo de uma sessão. Abrir e mesclar são folhas separadas, portanto um token pode abrir solicitações de alteração sem ter poder para mesclá-las — esse é o mecanismo por trás do requisito para o agente acima.

`project.cr.open` e `project.cr.merge` eram os nomes anteriores à transição para `project.gitops.push` e `project.gitops.merge` — a mesma capacidade com um segundo nome. Um `beeuni.yaml` que ainda liste um deles continua funcionando (a concessão é reescrita para a grafia vigente quando é resolvida), mas use o nome `gitops` em tudo que for novo.

Uma sessão nunca pode mesclar uma solicitação de alteração que ela mesma abriu, independentemente das concessões que tenha recebido. Essa regra é estrutural, não uma capacidade.
