From 608365aeac7204df0b38bf58bc4a9136050490ea Mon Sep 17 00:00:00 2001 From: Stream Date: Thu, 4 Jun 2026 23:40:01 +0300 Subject: [PATCH] auto-sync: 2026-06-04 23:40:01 --- memory/2026-06-04.md | 13 ++++ .../DEV_TASK_FIX_WEBHOOK_SECRET_ISOLATION.md | 60 +++++++++++++++++++ .../DEV_TASK_ORCH31_STAGING_INFRA.md | 57 ++++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 tasks/orchestrator/DEV_TASK_FIX_WEBHOOK_SECRET_ISOLATION.md create mode 100644 tasks/orchestrator/DEV_TASK_ORCH31_STAGING_INFRA.md diff --git a/memory/2026-06-04.md b/memory/2026-06-04.md index d9ba829..357dad4 100644 --- a/memory/2026-06-04.md +++ b/memory/2026-06-04.md @@ -9,3 +9,16 @@ - **Записала в ORCH-30** (id `85da2b10-6dc4-4693-b611-e96c6117f565`, seq 30, Backlog): анализ + 3 варианта (A эфемерный CI-стенд / B постоянный staging / C гибрид — рекомендую) + правки методики (стадия deploy-staging ПЕРЕД deploy-prod, тестер на staging-эндпоинт, build-once-promote, seed-фикстуры) + 5 открытых вопросов Славе. Связь: блокирует ORCH-7, пересекается с ORCH-21. - **Грабли Plane API (запомнить):** env `ORCH_PLANE_API_URL=http://localhost:8091` БЕЗ `/api/v1` — суффикс добавляет код (`plane_sync.py:15 PLANE_BASE=f"{url}/api/v1"`). Если дёргать API вручную — добавлять `/api/v1` самой, иначе Plane вернёт HTML (200) вместо JSON. Заголовок `X-API-Key`. Скрипт лить в контейнер orchestrator через base64-stdin+docker cp (scp на воркстейшене НЕТ, прямого доступа к localhost:8091 из контейнера ассистента НЕТ). - enduro-deploy-hook УЖЕ умеет rollback (PREV_IMG) — пригодится для ORCH-21/staging. + +### 🏗️ Staging-среда orchestrator — дизайн согласован, 5 задач заведены (04.06) +- Решения Славы: (1) постоянный staging-контейнер, (2) полная изоляция БД, (3) вариант b (песочница: тестовый Plane-проект + Gitea-репо), (4) промоут — на усмотрение Стрим («автономно и надёжно»), (5) не только smoke — реальные прогоны/статусы/комменты Plane + проверка доступов + интеграционное тестирование. (6) агенты = гибрид C (заглушки по дефолту + full-real по флагу). (7) approve — ручной на старте. +- **Ключевой принцип (решение Стрим):** деплоем орка рулит ВНЕШНИЙ хост-хук, НЕ сам контейнер (иначе убивает себя на середине self-деплоя). Рубильник снаружи. +- **Дизайн:** `tasks/orchestrator/DESIGN_STAGING_ENV.md` +- **Задачи (все в Backlog, НЕ запускать — пайплайн стартует только при In Progress):** + - ORCH-31 (`5a68a13c-3f3b-4d9e-91f7-24b0794bed06`) — staging-инфра (контейнер 8501, изолир. БД) + - ORCH-32 (`3dadcf0d-f1b6-4302-8075-8d428ace01f6`) — песочница Plane+Gitea + - ORCH-33 (`a11a7c0f-a78a-4309-9aa5-1c43d4355d0f`) — тест-сьют (smoke+доступы+e2e) + - ORCH-34 (`47fe9e75-d7ff-4ecb-bb99-67af9b23ab67`) — хост-деплой-хук + promote + auto-rollback (ядро, пересекается с ORCH-21) + - ORCH-35 (`4ead9be7-e1bf-4a28-8c76-88bda1c1fc2c`) — встройка в методику (deploy-staging перед deploy-prod) +- **Backlog state id ORCH:** `2d5d42ff-e94d-4209-a664-8020c28c2a95` +- Слава: «делай все этапы, минимум вопросов, installer skill если нужно, следи чтобы задачи случайно не запустились». diff --git a/tasks/orchestrator/DEV_TASK_FIX_WEBHOOK_SECRET_ISOLATION.md b/tasks/orchestrator/DEV_TASK_FIX_WEBHOOK_SECRET_ISOLATION.md new file mode 100644 index 0000000..75749d7 --- /dev/null +++ b/tasks/orchestrator/DEV_TASK_FIX_WEBHOOK_SECRET_ISOLATION.md @@ -0,0 +1,60 @@ +# DEV TASK — изолировать webhook-секрет между тестами (CI зелёный на полном прогоне) + +**Проект:** orchestrator | **Сервер:** slin@82.22.50.71 (pw motoZ@yaz2010) | **Репо:** /home/slin/repos/orchestrator | **Контейнер:** orchestrator +**Ветка УЖЕ существует:** `fix/isolate-webhook-tests-from-plane` (коммиты 7bbab9c, e856e09). РАБОТАТЬ В НЕЙ, добавить новый коммит сверху. `git fetch origin && git checkout fix/isolate-webhook-tests-from-plane && git pull --ff-only`. + +## ПРОБЛЕМА (диагностирована, причина железная) +- `test_webhooks.py` поодиночке = 16 passed. Но в ПОЛНОМ прогоне `pytest tests/` (как делает CI) 10 webhook-тестов падают с `assert 401 == 200`. +- Причина: `settings` (Pydantic, синглтон в src/config.py) читает webhook-секрет один раз. Разные тест-файлы по-разному управляют секретом: + - `test_webhooks.py:10-11` ставит `os.environ["ORCH_PLANE_WEBHOOK_SECRET"]=""` ЖЁСТКО на import-time. + - Остальные файлы (test_m6_sequence, test_plane_webhook и т.д.) — через `os.environ.setdefault(...,"")` (только если не задан). + - `test_webhook_dedup.py:268,278` временно ставит реальный секрет `"s3cr3t"` прямо в `settings` через `monkeypatch.setattr`. +- В зависимости от ПОРЯДКА сбора тестов pytest в `settings` протекает ненулевой секрет → HMAC-проверка включается → webhook-тесты получают 401 вместо 200. + +## РЕШЕНИЕ — узкая autouse-фикстура в conftest.py (НЕ трогать HMAC-механизм!) +Добавить в `tests/conftest.py` autouse-фикстуру, которая ПЕРЕД КАЖДЫМ тестом сбрасывает webhook-секрет на module-level `settings` в `""` — РОВНО тем же проверенным паттерном, что УЖЕ применён в `test_webhook_dedup.py:80-81`: +```python +@pytest.fixture(autouse=True) +def _reset_webhook_secrets(monkeypatch): + """Изоляция: webhook-секрет протекает между тест-файлами через синглтон settings + (settings читается один раз). Сбрасываем его в "" перед каждым тестом, чтобы HMAC + по умолчанию выключен. Тесты, которым нужен реальный секрет (test_webhook_dedup), + переопределяют его СВОИМ monkeypatch ПОСЛЕ этой фикстуры — она им не мешает.""" + import src.webhooks.gitea as gitea_mod + import src.webhooks.plane as plane_mod + monkeypatch.setattr(gitea_mod.settings, "gitea_webhook_secret", "", raising=False) + monkeypatch.setattr(plane_mod.settings, "plane_webhook_secret", "", raising=False) +``` +(Проверь точные имена модулей/атрибутов settings по факту — в test_webhook_dedup.py образец: `gitea_mod.settings.gitea_webhook_secret`, `plane_mod.settings.plane_webhook_secret`. Импорты settings в webhooks могут быть `from ..config import settings` — фикстура должна патчить ТОТ объект settings, что реально используется в проверке подписи. Если в gitea.py/plane.py секрет читается не через `settings.X`, а иначе — СТОП, отчёт, не угадывай.) + +## ⛔ OFF-LIMITS (НЕ ТРОГАТЬ — иначе СТОП и отчёт) +- НЕ менять сам HMAC-механизм / проверку подписи в src/webhooks/*.py. +- НЕ менять 9 HMAC/401 тестов (те, что проверяют ОТКЛОНЕНИЕ при неверной подписи). Фикстура сбрасывает секрет в "", но тесты, которые СПЕЦИАЛЬНО ставят секрет (test_webhook_dedup.py:268,278) и проверяют 401 — должны продолжать работать, т.к. они переопределяют секрет своим monkeypatch ПОСЛЕ autouse-фикстуры (порядок: autouse применяется первой, тестовый monkeypatch — внутри теста, перекрывает). ПРОВЕРЬ это явно прогоном. +- НЕ трогать src/config.py, логику settings. +- Если autouse-фикстура ломает хоть один из 401/HMAC/dedup тестов → СТОП, отчёт. Значит подход надо скорректировать — НЕ продавливать. + +## ПРОВЕРКА (обязательный пруф в отчёт) +1. ПОЛНЫЙ прогон как в CI (чистое окружение, read-only, без .env): + ``` + docker run --rm -v /home/slin/repos/orchestrator:/code:ro -w /code -e PYTHONPATH=/code \ + --entrypoint python3 $(docker inspect orchestrator --format '{{.Config.Image}}') \ + -m pytest tests/ -q + ``` + — ДОЛЖНО быть **0 failed** (было 10 failed / 284 passed). Приложи итоговую строку. +2. Отдельно прогнать dedup/HMAC-тесты, убедиться что 401-проверки ЖИВЫ (не сломаны фикстурой): + ``` + ... -m pytest tests/test_webhook_dedup.py -q + ``` + — все passed. Приложи. +3. test_webhooks.py отдельно — по-прежнему passed. +4. После push: workflow прогнан, job test → **success** (был failure). `/api/v1/repos/admin/orchestrator/actions/tasks`, токен `docker exec orchestrator printenv ORCH_GITEA_TOKEN`. ⚠️ ЭТО ГЛАВНЫЙ КРИТЕРИЙ — CI наконец зелёный. +5. `git log --oneline origin/main..origin/fix/isolate-webhook-tests-from-plane` — три коммита (7bbab9c, e856e09, твой новый). + +## ПРАВИЛА +- Менять ТОЛЬКО `tests/conftest.py` (добавить одну фикстуру). Если для работы фикстуры строго необходимо что-то ещё в tests/ — обоснуй в отчёте, но src/ НЕ трогать. +- ⚠️ НЕ регистрировать раннеров, НЕ nohup. Раннер mva154-runner-orch уже есть. +- PR push в main ЗАПРЕЩЁН. НЕ мержить — ревьюер (Стрим). +- Conventional Commits. Если факт разошёлся с ТЗ — СТОП, отчёт с вопросом. + +## РЕЗУЛЬТАТ +Новый коммит в ветке: `tests/conftest.py` с autouse-фикстурой сброса webhook-секрета. Полный `pytest tests/` = 0 failed в чистом окружении, HMAC/dedup-тесты живы, CI job test = success. PR #27 зелёный. Отчёт → `tasks/orchestrator/reports/dev-2026-06-04-fix-webhook-secret-isolation.md`. Коммит: `test: reset webhook secret per-test to fix cross-file isolation (CI green)`. diff --git a/tasks/orchestrator/DEV_TASK_ORCH31_STAGING_INFRA.md b/tasks/orchestrator/DEV_TASK_ORCH31_STAGING_INFRA.md new file mode 100644 index 0000000..b3142cb --- /dev/null +++ b/tasks/orchestrator/DEV_TASK_ORCH31_STAGING_INFRA.md @@ -0,0 +1,57 @@ +# DEV TASK — ORCH-31: Staging-инфраструктура orchestrator (контейнер 8501, изолированная БД) + +**Проект:** orchestrator | **Сервер:** slin@82.22.50.71 (pw motoZ@yaz2010) | **Репо:** /home/slin/repos/orchestrator | **Контейнер прод:** orchestrator (8500) +**Дизайн:** tasks/orchestrator/DESIGN_STAGING_ENV.md (§1, §2) | **Plane:** ORCH-31 (id 5a68a13c-3f3b-4d9e-91f7-24b0794bed06) +**Ветка:** `feature/ORCH-31-staging-infra` из свежего origin/main. + +## ЦЕЛЬ (Этап 1 из 5) +Добавить в репо возможность поднять ПОСТОЯННЫЙ staging-инстанс орка на порту 8501, с БД, ПОЛНОСТЬЮ изолированной от прод-очереди задач. На этом этапе — ТОЛЬКО инфра-обвязка (compose + конфиг + доки). Песочница Plane/Gitea, тест-сьют, деплой-хук — это ORCH-32/33/34, НЕ здесь. + +## ФАКТЫ О ТЕКУЩЕМ УСТРОЙСТВЕ (проверено на проде, не угадывай) +- `docker-compose.yml`: один service `orchestrator`, `build: .`, `container_name: orchestrator`, `network_mode: host`, `restart: unless-stopped`, `init: true`, `env_file: .env`, `group_add: ["999"]`. +- Запуск (Cmd образа): `uvicorn src.main:app --host 0.0.0.0 --port 8500`. +- Volumes прод: `./data:/app/data`, `/home/slin/repos:/repos`, `/var/run/docker.sock`, claude-code (ro), node (ro), `/home/slin/.claude` (rw), `/home/slin/.claude.json` (ro), `/home/slin/.orchestrator-ssh:/root/.ssh` (ro). +- БД: `src/config.py:43` → `db_path: str = "/app/data/orchestrator.db"`, переопределяется env `ORCH_DB_PATH`. Подключение `src/db.py:6` `sqlite3.connect(settings.db_path)`. **ORCH_DB_PATH уже параметризован** — изоляция БД через него. +- `network_mode: host` → порт задаётся в команде uvicorn, мапить порты не нужно. Два контейнера на host-сети сосуществуют, пока порты разные (8500 vs 8501). + +## ЧТО СДЕЛАТЬ + +### 1. Новый compose-service `orchestrator-staging` +Добавить в `docker-compose.yml` второй service `orchestrator-staging` ПОД ПРОФИЛЕМ `staging` (чтобы обычный `docker compose up -d` НЕ поднимал его случайно — только `docker compose --profile staging up -d orchestrator-staging`): +- `profiles: ["staging"]` +- `build: .` (тот же Dockerfile) ИЛИ `image: orchestrator:candidate` — см. примечание ниже. +- `container_name: orchestrator-staging` +- `network_mode: host`, `init: true`, `restart: unless-stopped`, `group_add: ["999"]` — как у прода. +- **Команда/порт:** переопределить запуск на порт **8501**. Если в Dockerfile порт зашит в CMD — переопредели через `command:` в compose: `command: ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8501"]`. +- `env_file: .env.staging` (НЕ .env прода). Файл .env.staging НА ЭТОМ ЭТАПЕ НЕ КОММИТИТЬ (секреты); вместо него закоммитить `\.env.staging.example` с именами ключей и комментариями (включая ORCH_DB_PATH). Реальный .env.staging создаётся при выкате (отдельный шаг через installer, не в репо). +- **Volumes (изоляция БД!):** `./data/staging:/app/data` — отдельный подкаталог, чтобы staging-БД (`/app/data/orchestrator.db` внутри → физически `./data/staging/orchestrator.db` на хосте) НЕ делила файл с продом (`./data`). Остальные mount'ы как у прода (repos, docker.sock, claude-code, node, .claude, ssh) — staging должен уметь то же, что прод. +- `environment`: те же ORCH_REPOS_DIR=/repos, ORCH_HOST_REPOS_DIR, DEPLOY_* как у прода. ДОБАВИТЬ явно `ORCH_DB_PATH=/app/data/orchestrator.db` (внутри контейнера; на хосте это ./data/staging/ — изолировано через volume) — чтобы было очевидно и не зависело от дефолта. + +> ⚠️ Примечание про образ: на Этапе 1 допустимо `build: .` (staging собирается из текущего кода). Тег `orchestrator:candidate` и promote — это ORCH-34. Здесь не усложняй. + +### 2. `.env.staging.example` +Создать файл `\.env.staging.example` (в корне репо, коммитится) — копия структуры .env с именами всех ключей (ORCH_PLANE_*, ORCH_GITEA_*, ORCH_TELEGRAM_*, ORCH_DB_PATH, ORCH_CLAUDE_BIN, ORCH_REPOS_DIR и т.д.), значения — плейсхолдеры/комментарии. Пометить комментарием вверху: «STAGING env. Plane/Gitea токены и sandbox-проект — ORCH-32. На Этапе 1 можно скопировать с прод .env, поменяв только то, что относится к изоляции.» + +### 3. `.gitignore` +Убедиться, что `.env.staging` (реальный) в `.gitignore` (как .env). Добавить если нет. `data/staging/` тоже в gitignore (как data/). + +### 4. Документация +Создать `docs/STAGING.md` (или дополнить существующую доку): как поднять staging (`docker compose --profile staging up -d orchestrator-staging`), порт 8501, где БД (./data/staging/), что .env.staging создаётся отдельно. Кратко — на этапе 1 это инфра, песочница/тесты/деплой будут в следующих задачах. + +## ПРОВЕРКА (приложить пруф в отчёт) +1. `docker compose config` (на хосте, в репо) — валиден, виден service orchestrator-staging под профилем staging. +2. ОБЫЧНЫЙ `docker compose config --profiles ""` или `docker compose up -d --dry-run` (без --profile staging) НЕ включает orchestrator-staging (доказать, что случайно не поднимется). +3. НЕ поднимать staging на этом этапе на проде (нет .env.staging — это шаг выката, делает Стрим). Достаточно валидации конфига. +4. `git log origin/main..origin/feature/ORCH-31-staging-infra` показывает коммит. + +## ЗАПРЕТЫ / ПРАВИЛА +- ⚠️ НЕ трогать прод-service `orchestrator` в compose (только ДОБАВИТЬ новый). НЕ менять порт прода, volumes прода, .env прода. +- ⚠️ НЕ поднимать staging-контейнер на проде (нет секретов, это отдельный шаг). Только код+валидация конфига. +- ⚠️ НЕ регистрировать раннеров, НЕ nohup. +- ⚠️ НЕ коммитить реальные секреты/.env.staging — только .example. +- PR в Gitea, push в main ЗАПРЕЩЁН (pre-receive hook). НЕ мержить — мержит ревьюер (Стрим). +- docker-compose.yml — критический конфиг: правка через PR, выкат согласует Стрим. +- Conventional Commits. Если факт ТЗ разошёлся с реальностью — СТОП, отчёт с вопросом. + +## РЕЗУЛЬТАТ +PR с изменениями: docker-compose.yml (+service staging под профилем), `.env.staging.example`, `.gitignore`, `docs/STAGING.md`. Конфиг валиден, staging НЕ поднимается без явного профиля, прод не затронут. Отчёт → `tasks/orchestrator/reports/dev-2026-06-04-orch31-staging-infra.md`. Коммит: `feat(staging): add isolated orchestrator-staging service (port 8501, separate DB)`.