d7271f836d
Co-authored-by: Cursor <cursoragent@cursor.com>
359 lines
21 KiB
Markdown
359 lines
21 KiB
Markdown
# EventHub — правила работы
|
||
|
||
Живой документ процесса разработки. Дополняется по мере появления новых договорённостей.
|
||
|
||
**Репозитории:** `EventHubBack`, `EventHubFrontAdmin`, `EventHubSpec`
|
||
**Трекер:** https://git.sabilin.com/EventHub
|
||
**Cursor skill для задач:** `eventhub-gitea`
|
||
|
||
---
|
||
|
||
## 1. Задачи (Gitea)
|
||
|
||
### Язык
|
||
Все задачи, комментарии и описания — **на русском**.
|
||
Исключение: имена файлов, пути API, идентификаторы в backticks.
|
||
|
||
### Жизненный цикл
|
||
|
||
| Этап | Действие |
|
||
|------|----------|
|
||
| Старт | Прочитать issue → назначить на себя → комментарий «Беру в работу» |
|
||
| План | **Предложить 1–3 варианта решения с trade-offs → ждать утверждения** |
|
||
| Работа | После утверждения — код в репозитории-владельце; коммиты с `Refs EventHub/<repo>#N` |
|
||
| Финиш | Тесты зелёные → комментарий с итогом → закрыть issue |
|
||
|
||
### Метки (единый набор во всех репо)
|
||
|
||
| Метка | Назначение |
|
||
|-------|------------|
|
||
| Bug | Дефект |
|
||
| Task | Техническая задача |
|
||
| Story | Пользовательская история |
|
||
| Epic | Крупная инициатива |
|
||
| Future | Отложено |
|
||
|
||
Синхронизация меток: `~/.cursor/skills/eventhub-gitea/scripts/sync-labels.ps1`
|
||
|
||
### Приоритеты
|
||
`P0` → `P1` → `P2` (указывается в заголовке задачи).
|
||
|
||
---
|
||
|
||
## 2. Тестирование (обязательный gate)
|
||
|
||
**Нельзя** закрывать задачу и **нельзя** обновлять спеку, пока:
|
||
|
||
- пройдены релевантные тесты репозитория;
|
||
- выполнена ручная проверка, если автотестов нет;
|
||
- нет известных регрессий в затронутой области.
|
||
|
||
| Репозиторий | Минимум |
|
||
|-------------|---------|
|
||
| EventHubBack | `make test` / `make eunit` / целевые API-тесты (см. §2.1 WSL) |
|
||
| EventHubFrontAdmin | `npm run lint`, `npm run build`, **`npm run test:e2e`** (Playwright mock-suite) |
|
||
| EventHubSpec | ревью текста, ссылки на код |
|
||
|
||
IFT smoke Admin UI (живой API после деплоя): `npm run test:e2e:ift` / workflow `e2e-ift.yml`. Учётки — из env стенда (`SMOKE_ADMIN_*` / `ADMIN_SUPER_*` в `EventHubDevOps/{ift,stage}/.env`). Контракт селекторов: `EventHubFrontAdmin/e2e/TESTIDS.md`.
|
||
|
||
---
|
||
|
||
## 2.1. Локальная разработка (WSL)
|
||
|
||
Окружение: **WSL + asdf** (Erlang/OTP 28, rebar3). PowerShell/Windows — только IDE; сборка и тесты — в WSL.
|
||
|
||
```bash
|
||
cd /mnt/c/Users/alexc/IdeaProjects/eventHub/EventHubBack
|
||
bash scripts/wsl-ext4-dirs.sh # один раз: logs/data на ext4
|
||
bash scripts/fix-rebar-unit-symlink.sh # перед CT, если rebar сломал unit/api
|
||
rebar3 as test eunit
|
||
rebar3 ct --suite=api_admins_SUITE
|
||
```
|
||
|
||
### Git-коммиты из WSL (Agent и разработчик)
|
||
|
||
Коммиты из **Agent Shell / PowerShell** ломаются: Cursor подставляет `--trailer "Co-authored-by: Cursor <cursoragent@cursor.com>"`, а PowerShell падает на `<`.
|
||
|
||
**Решение:** Agent коммитит через WSL-скрипт в `~/.cursor/eventhub/` (в команде нет строки `git commit`):
|
||
|
||
```bash
|
||
EH=/mnt/c/Users/alexc/IdeaProjects/eventHub
|
||
GIT=/mnt/c/Users/alexc/.cursor/eventhub
|
||
|
||
bash $GIT/git-commit.sh $EH/EventHubBack \
|
||
"сообщение. Refs EventHub/EventHubBack#N" \
|
||
path/to/file ...
|
||
```
|
||
|
||
Ручной коммит — тот же вызов в WSL-терминале.
|
||
|
||
### Пропуск CI (Gitea Actions)
|
||
|
||
В **последнем** коммите пуша (или в title PR) можно отключить запуск workflow:
|
||
|
||
| Маркер | Пример |
|
||
|--------|--------|
|
||
| `[skip ci]` | предпочтительно |
|
||
| `[ci skip]` / `[no ci]` / `[skip actions]` / `[actions skip]` | синонимы |
|
||
|
||
Gitea не ставит run в очередь → не сработают deploy по `workflow_run`.
|
||
Через скрипт:
|
||
|
||
```bash
|
||
SKIP_CI=1 bash $GIT/git-commit.sh $EH/EventHubBack \
|
||
"docs only. Refs EventHub/EventHubBack#N" \
|
||
path/to/file
|
||
|
||
# или
|
||
bash $GIT/git-commit.sh $EH/EventHubBack "docs" file.md --skip-ci
|
||
```
|
||
|
||
Не злоупотреблять: без CI нет образов/IFT. Ручной `workflow_dispatch` по-прежнему доступен.
|
||
|
||
### Push на remote
|
||
|
||
**Agent делает `git push` только после явного подтверждения пользователя** («пуш», «отправь», «push»):
|
||
|
||
```bash
|
||
bash $GIT/git-push.sh $EH/EventHubBack
|
||
```
|
||
|
||
До подтверждения: коммиты остаются локально; в конце задачи Agent напоминает, что можно пушить.
|
||
|
||
Attribution в Cursor (Settings → Agent → Attribution) можно выключить дополнительно.
|
||
|
||
---
|
||
|
||
## 3. Спецификация (после успешных тестов)
|
||
|
||
Если изменение затрагивает **поведение, API, модель данных или роли**:
|
||
|
||
1. Обновить соответствующий файл в `EventHubSpec/`:
|
||
- backend → `EventHubBackSpec.md`
|
||
- admin → `EventHubFrontAdminSpec.md`
|
||
2. В комментарии к issue указать: «Спека обновлена: …»
|
||
3. Спека описывает **фактическую** реализацию, не планы.
|
||
|
||
Что синхронизировать:
|
||
- endpoint-ы (путь, метод, auth);
|
||
- форматы запросов/ответов;
|
||
- поля records/таблиц;
|
||
- роли и права;
|
||
- ограничения и допущения.
|
||
|
||
Чистый рефакторинг без смены контракта — обновление спеки не обязательно (написать в issue).
|
||
|
||
---
|
||
|
||
## 5. Старт нового чата (передача контекста)
|
||
|
||
Новую задачу лучше начинать в **отдельном чате** с коротким брифом — не копировать всю переписку.
|
||
|
||
### Что приложить в Cursor
|
||
|
||
- `@EventHubSpec/WORKFLOW.md` — этот документ
|
||
- Ссылка на issue: `https://git.sabilin.com/EventHub/<Repo>/issues/N`
|
||
- При необходимости: 1–3 ключевых файла кода (`@src/...`)
|
||
|
||
### Шаблон первого сообщения
|
||
|
||
```markdown
|
||
Проект: EventHub
|
||
- EventHubBack: .../EventHubBack
|
||
- EventHubFrontAdmin: .../EventHubFrontAdmin
|
||
- EventHubSpec: .../EventHubSpec
|
||
|
||
Gitea: https://git.sabilin.com/EventHub
|
||
Следуй WORKFLOW.md и skills eventhub-gitea / eventhub-workflow.
|
||
|
||
Окружение: WSL + asdf (OTP 28). Git: ~/.cursor/eventhub/git-commit.sh, push — только после «пуш».
|
||
Перед CT: scripts/fix-rebar-unit-symlink.sh; logs/data: scripts/wsl-ext4-dirs.sh
|
||
|
||
Задача: EventHubBack#24 — <краткая цель>
|
||
Issue: https://git.sabilin.com/EventHub/EventHubBack/issues/24
|
||
|
||
Контекст (что уже сделано):
|
||
- #23 закрыт: auth_session, admin refresh JWT (фаза 1)
|
||
|
||
Правила: 1–3 варианта → утверждение → код; тесты перед закрытием; спека после контракта.
|
||
```
|
||
|
||
### Минимальный вариант (одной строкой)
|
||
|
||
> Берём EventHubBack#24. WORKFLOW.md. WSL, git через ~/.cursor/eventhub/. Варианты — потом утверждение. Пуш — только когда скажу.
|
||
|
||
### Что не передавать
|
||
|
||
- Полные логи, diff, историю старого чата
|
||
- Токены (`GITEA_TOKEN` — в `~/.cursor/secrets/eventhub-gitea.env`)
|
||
|
||
---
|
||
|
||
## 6. Версии и деплой (IFT / stage)
|
||
|
||
### Product version (`EventHubSpec/VERSION`)
|
||
|
||
Единый источник правды: **`EventHubSpec/VERSION`** — формат **`MAJOR.MINOR`** (сейчас `0.1`).
|
||
|
||
- Бампается **вручную и редко** (смена продуктовой линейки).
|
||
- В `EventHubBack` / `EventHubFrontAdmin` (и будущем client UI) файл `VERSION` — только **fallback** для локальных сборок без доступа к Spec.
|
||
- CI на push/PR **читает Spec** (`scripts/resolve-product-version.sh`), не локальный файл (если Spec доступен).
|
||
|
||
### Build number
|
||
|
||
| Поле | Откуда | Одинаково между репо? |
|
||
|------|--------|------------------------|
|
||
| `version` (`MAJOR.MINOR`) | `EventHubSpec/VERSION` | **да** |
|
||
| `build` | `GITHUB_RUN_NUMBER` workflow CI | **нет** (у каждого репо свой счётчик) |
|
||
| `git_sha` | `GITHUB_SHA` (12 символов) | нет |
|
||
| `built_at` | время CI UTC | нет |
|
||
|
||
### Идентичность сборки (health / UI)
|
||
|
||
**API** (`GET /health`, `GET /admin/health`, `GET /v1/admin/health`):
|
||
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"service": "eventhub",
|
||
"version": "0.1",
|
||
"build": 338,
|
||
"git_sha": "2b65a804d354",
|
||
"built_at": "2026-07-17T18:00:00Z"
|
||
}
|
||
```
|
||
|
||
- **Back:** bake `EVENTHUB_VERSION`, `EVENTHUB_BUILD`, `EVENTHUB_GIT_SHA`, `EVENTHUB_BUILT_AT`.
|
||
- **Admin UI:** bake `VITE_APP_VERSION`, `VITE_APP_BUILD`, `VITE_GIT_SHA`, `VITE_BUILT_AT`.
|
||
- **Client UI (будущее):** те же `VITE_APP_*`, лейбл компонента — `UI`.
|
||
|
||
Отображение в Admin UI (меню профиля):
|
||
|
||
- `Admin UI 0.1.341 (eb37476)`
|
||
- `API 0.1.338 (2b65a804d354)`
|
||
|
||
Третье число в лейбле — **build**, не semver-patch релиза.
|
||
|
||
### Registry / окружения
|
||
|
||
| Окружение | Тег образа | Когда |
|
||
|-----------|------------|--------|
|
||
| IFT / push CI | `sha-<12>` + `MAJOR.MINOR.BUILD` + floating `:ift` / `:dev` | после зелёного CI |
|
||
| Stage | тот же `sha-<12>` + floating `:stage` | auto после успешного CI на `master` |
|
||
|
||
IFT и stage крутят **одну и ту же** сборку `sha-*`. Product-метка `0.1.{build}` — для registry retention/отображения; на выбор тега деплоя не влияет (деплой по `sha-*`).
|
||
|
||
### E2E Admin UI (Playwright)
|
||
|
||
- **CI / PR:** `npm run test:e2e` — Chromium, API-моки (`e2e/mocks`), project `mock`.
|
||
- **IFT smoke:** после успешного Deploy IFT — workflow `e2e-ift.yml` → `npm run test:e2e:ift` (без моков). Учётки с хоста IFT: `/opt/eventhub-ift/.env` (`SMOKE_ADMIN_*` → `ADMIN_SUPER_*`).
|
||
- Селекторы: `data-testid` + a11y; контракт в `EventHubFrontAdmin/e2e/TESTIDS.md`.
|
||
|
||
---
|
||
|
||
## 7. Нагрузочное тестирование IFT (Tsung)
|
||
|
||
Стенд IFT (HOME-PC): Docker Swarm **внутри WSL2**. Часть BOUNDARY_HIT под aggressive stress — артефакт гипервизора/`vmmem`, не только приложения.
|
||
|
||
### Baseline (июль 2026, IFT / WSL2, `sha-6d8f02d`)
|
||
|
||
| Метрика | Значение |
|
||
|---------|----------|
|
||
| Зелёный hold (×2 / ×3, maxusers=100) | 0 errors; mean ~40–65 ms; late ≤ ~210 ms |
|
||
| ×1, maxusers=100 | **BOUNDARY** по latency (mean ~613 ms) — одной ноды мало |
|
||
| Hot ladder ×3 (0.1→0.05→0.035, maxusers=100) | **UNDER** (mean ~47 ms) |
|
||
| Soft OVER concurrent | maxusers=**150** на ×3 — 0 errors, но late ~1.5 s → BOUNDARY |
|
||
| Prod-ориентир с IFT | минимум **2** реплики EventHub; абсолютные VU/CPU с WSL **не** копировать 1:1 |
|
||
|
||
Подробности: `EventHubBack/test/tsung/README.md`. Абсолютный потолок на bare Linux — `EventHubDevOps#4` (Future).
|
||
|
||
### Правила (обязательно)
|
||
|
||
- **Default stress-профиль:** `eventhub_ift_stress_hold.xml` / `stress-hold` (maxusers=**100**).
|
||
- `maxusers` ≤ **150** на IFT без согласования; **200+** / uncapped / `stress-bump` / beams — только с `CONFIRM_DANGEROUS=1`.
|
||
- `thinktime` ≥ **1** s; плановый interarrival ≥ **0.1** s для green hold.
|
||
- Prepare/restore: `EventHubDevOps/scripts/ift-loadtest-*.sh`; при hang WSL SSH — Windows `restore-ift.ps1` / `wsl --shutdown`.
|
||
- **Потолок vs репликация:** сначала `EVENTHUB_REPLICAS=1`, затем 2, 3 — отдельные прогоны с gate.
|
||
- IFT Traefik: штатно **без Coraza/WAF** (память); loadtest-оверлей — `dynamic_conf.loadtest.yml` / `LOADTEST_TRAEFIK`.
|
||
|
||
### После каждого stress-прогона (обязательно)
|
||
|
||
Прогон **не считается завершённым**, пока не сделаны сбор, разбор и **очистка дампов на IFT**. Restore без архива — только если стенд уже мёртв и сбор невозможен; тогда collect сразу после `wsl --shutdown`.
|
||
|
||
1. **Tsung summary (2A):** `python3 test/tsung/summarize-tsung-stress.py ~/.tsung/log/<stamp>/tsung.log` → `verdict`, error_rate, mean, коды HTTP, transport errors.
|
||
2. **Сбор логов стенда** на exchange-share (`\\…\eventhub-cursor\` / `C:\eventhub-cursor-exchange\ift-to-dev\loadtest-<stamp>\`):
|
||
- SSH жив: `collect-ift-loadtest-logs.ps1` (после download сам чистит remote dump этого stamp)
|
||
- SSH banner hang: `restart-wsl-and-collect-logs.ps1` **или** вручную `wsl --shutdown` на HOME-PC → затем `collect-ift-loadtest-logs.ps1`
|
||
- На консоли IFT без SSH: `collect-ift-loadtest-logs-on-ift.sh` (после copy на шару чистит локальные дампы)
|
||
3. **Анализ (минимум):**
|
||
- сравнить с предыдущим прогоном того же профиля/replicas;
|
||
- в архиве: `eventhub-service.log` / `traefik-service.log` — 429, Mnesia `dump_log`, archive/peer, OOM, crash;
|
||
- `ift-docker-stats-*.log` / `docker-stats.txt` — CPU/MEM пики;
|
||
- **метрики EventHub (обязательно смотреть):**
|
||
- scrape `GET https://api.ift.eventhub.local/metrics` (Prometheus text, без auth; eventhub остаётся up в prepare) — до/во время/после; оркестратор пишет сэмплы в `~/.tsung/ift-eh-metrics-*.log`;
|
||
- история узлов: `GET /v1/admin/nodes/metrics?from=&to=` (admin JWT) — переживает restart, если volume Mnesia цел;
|
||
- стек Grafana/Prometheus (compose) при `KEEP_OBSERVABILITY=1` — опционально; если prepare гасит их, mid-run там пусто;
|
||
- зафиксировать: образ `sha-*`, replicas, verdict, узкое место.
|
||
4. **Очистка дампов на IFT** (если collect не очистил / остались старые):
|
||
`cleanup-ift-loadtest-dumps.ps1` / `EventHubDevOps/scripts/cleanup-ift-loadtest-dumps.sh`
|
||
Удаляет `~/loadtest-logs-*`, `/tmp/ift-loadtest-logs-*.tgz`, `/tmp/ift-collect-*.sh`. **Копии на шаре и Tsung на дев не трогает.**
|
||
5. **Restore** полного стека (`ift-loadtest-prepare` уже гасит observability; restore поднимает) + smoke health.
|
||
6. **Отчёт:** комментарий в issue (`EventHubBack#30`) и/или строка в `EventHubBack/test/tsung/README.md`.
|
||
|
||
Prepare: по умолчанию compose prometheus/grafana выключены. Для скрейпа **приложения** это не нужно — `/metrics` на eventhub доступен через Traefik всегда, пока up нода. `KEEP_OBSERVABILITY=1` — только если нужен отдельный Prometheus/Grafana стек.
|
||
|
||
### Критерий верхней границы (2A)
|
||
|
||
error rate > 1% **или** mean request > 500 ms (hold ≥2 мин в фазе) → `BOUNDARY_HIT`.
|
||
|
||
Сравнение hold на bare Linux (без WSL2) — отложено: `EventHubDevOps#4` (Future).
|
||
|
||
---
|
||
|
||
## 4. Расширение правил
|
||
|
||
При появлении новой договорённости в работе:
|
||
|
||
1. Добавить пункт в этот файл (раздел «Правила» ниже).
|
||
2. При необходимости — задача `Task` в `EventHubSpec` на согласование.
|
||
3. Для Cursor — обновить skill `eventhub-workflow`.
|
||
|
||
---
|
||
|
||
## Правила (накопительный список)
|
||
|
||
- Задачи в Gitea вести через skill `eventhub-gitea`; тексты на русском.
|
||
- **Перед реализацией** — предложить оптимальные варианты решения; кодить только после утверждения варианта пользователем.
|
||
- Перед закрытием issue — успешные тесты изменённого кода.
|
||
- После успешных тестов — обновить спеку, если менялся внешний контракт.
|
||
- Одна задача — один логический объём работы.
|
||
- Код фичи — в репозитории-владельце; `EventHubSpec` — для документации и процесса.
|
||
- Метки во всех репозиториях держать одинаковыми (`sync-labels.ps1`).
|
||
- Секреты и токены не коммитить; Gitea token — только в env / `~/.cursor/secrets/`.
|
||
- **Git commit/push — через WSL** (`~/.cursor/eventhub/git-commit.sh`, `git-push.sh`); **push — только после подтверждения пользователя** (см. §2.1).
|
||
- **Пропуск CI:** `[skip ci]` (и синонимы) в сообщении последнего коммита пуша / title PR; `SKIP_CI=1` или `--skip-ci` в `git-commit.sh` (см. §2.1).
|
||
- Новая задача — **отдельный чат** с брифом по §5.
|
||
- **Версии/деплой:** product = `VERSION` (`MAJOR.MINOR`); IFT+stage = `sha-*` (+ floating `:ift`/`:stage`); без ручных stage-тегов и без patch++ на каждый push (см. §6).
|
||
- **FrontAdmin E2E:** mock-suite обязателен в CI; IFT smoke — после Deploy IFT (`e2e-ift.yml`, учётки из стендового `.env`).
|
||
- **IFT loadtest:** hold default maxusers=100; зелень на ×2/×3; ×1 — latency BOUNDARY; absolute capacity — bare Linux (`EventHubDevOps#4`); uncapped/beams без `CONFIRM_DANGEROUS=1` запрещены; после stress — collect+анализ+cleanup+отчёт+restore (см. §7).
|
||
|
||
---
|
||
|
||
## История
|
||
|
||
| Дата | Изменение |
|
||
|------|-----------|
|
||
| 2026-07-15 | §7: анализ через EventHub `/metrics` + admin `nodes/metrics`; compose Prom опционален |
|
||
| 2026-07-15 | §7: cleanup дампов на IFT + мониторинг в анализе; KEEP_OBSERVABILITY |
|
||
| 2026-07-15 | §7: сбор и анализ логов — обязательный gate после stress |
|
||
| 2026-07-15 | §7: capacity baseline IFT + правила Tsung stress (после EventHubBack#30) |
|
||
| 2026-07-15 | §2.1: пропуск CI — `[skip ci]` / `SKIP_CI=1` в git-commit.sh |
|
||
| 2026-07-14 | §6 + gate: Playwright E2E (mock CI + IFT smoke secrets) |
|
||
| 2026-07-14 | §6: VERSION MAJOR.MINOR + auto stage по sha после CI |
|
||
| 2026-07-07 | Git-скрипты Agent перенесены в `~/.cursor/eventhub/` (не в репозиториях) |
|
||
| 2026-07-07 | §5: шаблон передачи контекста в новый чат |
|
||
| 2026-07-07 | Push на remote — только после подтверждения пользователя |
|
||
| 2026-07-07 | §2.1: WSL, git-коммиты через `scripts/git-commit.sh` |
|
||
| 2026-07-07 | Правило: варианты решения и утверждение перед реализацией |
|
||
| 2026-07-07 | Первая версия: Gitea workflow, gate тестирования, sync спеки |
|