| 1 | # FORGE-ADR-0005: CI webhook bridge — не свой GitLab CI |
| 2 | |
| 3 | | | | |
| 4 | |---|---| |
| 5 | | **Status** | Accepted | |
| 6 | | **Date** | 2026-06-09 | |
| 7 | | **Relates to** | [FORGE-ADR-0001](FORGE-ADR-0001-agent-first-not-gitlab-clone.md), [FORGE-ADR-0004](FORGE-ADR-0004-git-transport-ssh-http.md) | |
| 8 | | **Target** | v0.2–v0.3 | |
| 9 | |
| 10 | ## Context |
| 11 | |
| 12 | Команды привыкли: MR → pipeline. GitLab CI встроен в монолит. Forge **не** строит runner zoo в ядре ([FORGE-ADR-0001](FORGE-ADR-0001-agent-first-not-gitlab-clone.md) — CI в **MEF plugin** / bridge). |
| 13 | |
| 14 | Нужен **минимальный контракт**: forge знает, что pipeline запущен/зелёный/красный — для thin web approve и MCP status. Запуск CI — **существующая** инфра (GitLab runner, Jenkins, custom docker) через **webhook**. |
| 15 | |
| 16 | ## Decision |
| 17 | |
| 18 | ### 1. Принцип |
| 19 | |
| 20 | - **Forge не исполняет** build/test в ядре v0.2. |
| 21 | - Forge **эмитит** события и **принимает** callbacks. |
| 22 | - Plugin `IForgeCiBridge` (MEF) — адаптер под конкретную CI; default — **generic webhook**. |
| 23 | |
| 24 | ### 2. События forge → CI (outbound) |
| 25 | |
| 26 | | Событие | Когда | Payload (JSON) | |
| 27 | |---------|-------|----------------| |
| 28 | | `repo.created` | POST `/api/v1/repos` | `repo`, `cloneUrl` | |
| 29 | | `mr.opened` | POST merge-request | `repo`, `mr`, `sourceBranch`, `targetBranch` | |
| 30 | | `push` | post-receive hook (ADR-0004) | `repo`, `ref`, `commit`, `pusher` | |
| 31 | |
| 32 | `POST {FORGE_CI_WEBHOOK_URL}` с HMAC подписью `FORGE_CI_WEBHOOK_SECRET`. |
| 33 | |
| 34 | ### 3. Callback CI → forge (inbound) |
| 35 | |
| 36 | ``` |
| 37 | POST /api/v1/ci/callback |
| 38 | Authorization: Bearer {FORGE_CI_CALLBACK_TOKEN} |
| 39 | ``` |
| 40 | |
| 41 | | Поле | Смысл | |
| 42 | |------|--------| |
| 43 | | `repo` | slug | |
| 44 | | `mr` | number (optional) | |
| 45 | | `commit` | sha | |
| 46 | | `status` | `pending` \| `success` \| `failure` \| `cancelled` | |
| 47 | | `url` | ссылка на лог pipeline (view-only) | |
| 48 | |
| 49 | Хранится в WitDatabase (`ci_runs` table, v0.3). MR approve в thin web может показывать **last status** (ADR-0002). |
| 50 | |
| 51 | ### 4. MCP / chat |
| 52 | |
| 53 | - `get_merge_request` расширить полем `ciStatus`, `ciUrl` (read-only). |
| 54 | - Агент **не** триггерит CI напрямую в v0.2, если не настроен plugin — только читает callback. |
| 55 | |
| 56 | ### 5. Non-goals |
| 57 | |
| 58 | - `.gitlab-ci.yml` parser в ядре. |
| 59 | - Собственный runner fleet. |
| 60 | - Required status checks как hard gate day-one (advisory v0.3, enforce v0.4+). |
| 61 | |
| 62 | ## Consequences |
| 63 | |
| 64 | ### Positive |
| 65 | |
| 66 | - Пилот CAD: оставляют **существующий** runner, forge — metadata + webhook. |
| 67 | - Зоопарк CI — в плагинах, не в Core. |
| 68 | |
| 69 | ### Negative / trade-offs |
| 70 | |
| 71 | - Две системы (forge + CI) — нужен runbook для секретов и URL. |
| 72 | - Без callback MR status устаревает — документировать. |
| 73 | |
| 74 | ## Alternatives considered |
| 75 | |
| 76 | | Alternative | Why not | |
| 77 | |-------------|---------| |
| 78 | | Встроенный docker runner | Ops + не MCP-first. | |
| 79 | | Только GitLab CI | Monolith dependency. | |
| 80 | | Нет CI вообще | Reviewer хочет зелёную галочку. | |
| 81 | |
| 82 | ## Implementation notes |
| 83 | |
| 84 | - Env: `FORGE_CI_WEBHOOK_URL`, `FORGE_CI_WEBHOOK_SECRET`, `FORGE_CI_CALLBACK_TOKEN`. |
| 85 | - v0.2: outbound webhook only; v0.3: callback + DB + thin web badge. |
| 86 | - Plugin: `ForgeCiBridge.GitLab`, `ForgeCiBridge.GenericHttp` — позже. |
| 87 | |
| 88 | ## Links |
| 89 | |
| 90 | - [FORGE-ADR-0004](FORGE-ADR-0004-git-transport-ssh-http.md) — post-receive |
| 91 | - [FORGE-ADR-0002](FORGE-ADR-0002-mcp-first-web-view-only.md) — MR approve UI |
| 92 | - [FORGE-ADR-0016](FORGE-ADR-0016-visual-ci-plugin-and-runner-fleet.md) — Visual CI product (Plugin.Ci + runner fleet; extends this bridge) |
| 93 | |