ORCH-044 closes two blind spots that let a single de-authenticated agent
stall the shared queue for all projects:
P1 — preflight auth gate. `claude --version` answers even when logged out,
so version-only preflight was blind to auth. Adds a token-free, network-free
check of <AGENT_HOME>/.claude/.credentials.json: missing/unreadable/no-oauth
or an expired `claudeAiOauth.expiresAt` (epoch ms, vs now + skew) => preflight
FAIL; absent expiry => OK (no false positives). Result is cached on the same
preflight_cache_ttl. Post-factum safety net: launcher detects auth markers
("not logged in" / "/login" / "unauthorized" / 401) in the run log and resets
the preflight cache so the next tick re-evaluates auth. Auth failure is a gate,
not a transient — it does not spin the circuit breaker. Emergency toggle
ORCH_PREFLIGHT_CHECK_AUTH=false restores version-only behaviour.
P3 — empty log / no result-JSON => job failed. exit_code==0 with an empty or
JSON-less run log no longer counts as success: a separate result_ok flag gates
stage advance + usage comments, fires a Telegram alert, and routes the job
through the normal transient/permanent failure path (exit_code integrity in
agent_runs preserved).
Scope: P2 (--effort) is intentionally excluded and tracked in ORCH-50.
New settings: ORCH_PREFLIGHT_CHECK_AUTH, ORCH_CLAUDE_CREDENTIALS_PATH,
ORCH_AUTH_EXPIRY_SKEW_SECONDS. Docs updated (INFRA.md, internals.md, CHANGELOG).
Refs: ORCH-044
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
139 lines
13 KiB
Markdown
139 lines
13 KiB
Markdown
# INFRA.md — инфраструктура и эксплуатация оркестратора
|
||
|
||
> RUNBOOK. Топология, контейнеры, порты, переменные окружения, границы.
|
||
> **Секреты тут НЕ хранятся** — только дескрипторы. Реальные значения — в `.env` на хосте.
|
||
|
||
## Топология
|
||
|
||
```
|
||
host: mva154 (slin@82.22.50.71), network_mode: host
|
||
┌──────────────────────────────────────────────────────────────────────┐
|
||
│ orchestrator (PROD) :8500 env_file .env │
|
||
│ БД: ./data/orchestrator.db (обслуживает ВСЕ прод-проекты) │
|
||
│ │
|
||
│ orchestrator-staging (STAGING) :8501 env_file .env.staging │
|
||
│ БД: ./data/staging/orchestrator.db (изолирована, только sandbox) │
|
||
│ profile: staging — НЕ стартует обычным `docker compose up` │
|
||
└──────────────────────────────────────────────────────────────────────┘
|
||
│ webhooks │ git
|
||
▼ ▼
|
||
Plane (ag_proj) Gitea (localhost:3000)
|
||
/repos/<project> ← общий каталог репозиториев (host: /home/slin/repos)
|
||
```
|
||
|
||
## Контейнеры
|
||
|
||
| Контейнер | Роль | Порт | env_file | БД (хост) | Старт |
|
||
|-----------|------|------|----------|-----------|-------|
|
||
| `orchestrator` | прод | 8500 | `.env` | `./data/orchestrator.db` | `docker compose up -d` |
|
||
| `orchestrator-staging` | staging / песочница | 8501 | `.env.staging` | `./data/staging/orchestrator.db` | `docker compose --profile staging up -d orchestrator-staging` |
|
||
|
||
Оба: `network_mode: host`, `init: true` (tini как PID 1 — reaping зомби, B-2), `restart: unless-stopped`.
|
||
|
||
### Тома (volumes)
|
||
- `./data` → `/app/data` (БД; у staging — `./data/staging`)
|
||
- `/home/slin/repos` → `/repos` (рабочие репозитории проектов)
|
||
- `/var/run/docker.sock` (для docker-операций деплоя)
|
||
- claude-code, node, `~/.claude*` (CLI агентов, ro)
|
||
- `~/.orchestrator-ssh` → `/root/.ssh` (ro, деплой по ssh)
|
||
|
||
## Переменные окружения (карта; значения — в `.env`)
|
||
|
||
| Переменная | Назначение |
|
||
|-----------|-----------|
|
||
| `ORCH_PLANE_API_URL` / `_TOKEN` / `_WORKSPACE_SLUG` | доступ к Plane API |
|
||
| `ORCH_PLANE_WEB_URL` | внешний (браузерный) web-URL Plane для кликабельных ссылок на issue в уведомлениях (ORCH-017); пусто → фолбэк на `ORCH_PLANE_API_URL`, loopback-фолбэк → ссылка опускается |
|
||
| `ORCH_PLANE_WEBHOOK_SECRET` | HMAC-проверка вебхуков Plane |
|
||
| `ORCH_GITEA_URL` / `_TOKEN` / `_WEBHOOK_SECRET` | доступ к Gitea + HMAC |
|
||
| `ORCH_CLAUDE_BIN` | путь к claude CLI |
|
||
| `ORCH_REPOS_DIR` / `ORCH_HOST_REPOS_DIR` | каталог репозиториев (в контейнере / на хосте) |
|
||
| `ORCH_DB_PATH` | путь к SQLite БД |
|
||
| `ORCH_PROJECTS_JSON` | реестр проектов (Plane id → repo + prefix); пусто → дефолт из `src/projects.py` |
|
||
| `ORCH_AGENT_MODEL_DEFAULT` | LLM-модель агентов по умолчанию (ORCH-41); дефолт `claude-opus-4-8` |
|
||
| `ORCH_AGENT_MODEL_<AGENT>` | per-agent модель (ANALYST/ARCHITECT/DEVELOPER/REVIEWER/TESTER/DEPLOYER); пусто → default |
|
||
| `ORCH_AGENT_EFFORT_DEFAULT` | режим работы `--effort` по умолчанию (ORCH-41): low\|medium\|high\|xhigh\|max; дефолт `high` |
|
||
| `ORCH_AGENT_EFFORT_<AGENT>` | per-agent effort; дефолт: думающие → high, tester/deployer → medium |
|
||
| `ORCH_AGENT_FALLBACK_MODEL` | опц. фолбэк-модель при overloaded (`--fallback-model`); пусто → без флага |
|
||
| `ORCH_PREFLIGHT_CHECK_AUTH` | вкл/выкл token-free auth-проверку preflight (ORCH-044); дефолт `true`. Аварийный тумблер: `false` → preflight как до ORCH-044 (только `--version`) |
|
||
| `ORCH_CLAUDE_CREDENTIALS_PATH` | явный путь к `.credentials.json` (ORCH-044); пусто → `<AGENT_HOME>/.claude/.credentials.json`, где `AGENT_HOME=/home/slin` — HOME, под которым launcher реально спавнит claude (не HOME процесса орка) |
|
||
| `ORCH_AUTH_EXPIRY_SKEW_SECONDS` | запас на рассинхрон часов при сравнении `claudeAiOauth.expiresAt` (ORCH-044); дефолт `0` |
|
||
| `DEPLOY_SSH_USER` / `_HOST` / `DEPLOY_HOOK_SCRIPT` | параметры деплой-хука |
|
||
|
||
**Секреты — только в `.env` / `.env.staging` на хосте, в гит НЕ коммитятся.** Канон — `.env.example`, `.env.staging.example`.
|
||
|
||
## Реестр проектов (`src/projects.py`, ORCH-6)
|
||
Связывает Plane project id → gitea repo + work-item prefix. Источник: `ORCH_PROJECTS_JSON`, fallback — встроенный дефолт. Прод видит: `enduro-trails` (ET), `orchestrator` (ORCH). Staging видит ТОЛЬКО `orchestrator-sandbox` (SANDBOX) — изоляция.
|
||
|
||
## Модель и effort агентов (`src/config.py` + `src/agents/launcher.py`, ORCH-41)
|
||
Модель LLM и режим работы (`--effort`) каждого агента **конфигурируемы** — глобально per-agent (env) и per-project (через `ORCH_PROJECTS_JSON`).
|
||
|
||
**Приоритет резолвинга** (`resolve_agent_model` / `resolve_agent_effort`):
|
||
1. per-project override — `agent_models` / `agent_efforts` в записи `ORCH_PROJECTS_JSON`;
|
||
2. per-agent env — `ORCH_AGENT_MODEL_<AGENT>` / `ORCH_AGENT_EFFORT_<AGENT>` (если непусто);
|
||
3. глобальный дефолт — `ORCH_AGENT_MODEL_DEFAULT` (`claude-opus-4-8`) / `ORCH_AGENT_EFFORT_DEFAULT` (`high`);
|
||
4. пусто → флаг не передаётся, действует дефолт CLI.
|
||
|
||
**Значения effort:** `low` < `medium` < `high` < `xhigh` < `max` — рычаг «качество vs стоимость/время». Дефолтная раскладка: думающие агенты (analyst/architect/developer/reviewer) → `high`, механические (tester/deployer) → `medium`. Невалидное значение → лог-warning, флаг опускается.
|
||
|
||
**Per-project override в `ORCH_PROJECTS_JSON`** (поля `agent_models` / `agent_efforts` опциональны, старые записи работают):
|
||
```json
|
||
{"plane_project_id":"...","repo":"orchestrator","work_item_prefix":"ORCH",
|
||
"agent_models":{"developer":"claude-opus-4-8","reviewer":"claude-sonnet-4-6"},
|
||
"agent_efforts":{"developer":"xhigh","tester":"low"}}
|
||
```
|
||
|
||
> ⚠️ Бюджет (ORCH-38): `claude-opus-4-8` дефолт в коде; реальное переключение прод-env делается отдельно после согласования.
|
||
|
||
## Preflight auth-гейт (`src/preflight.py`, ORCH-044)
|
||
`claude --version` отвечает успешно **даже когда claude разлогинен** (версия — локальная инфа), поэтому до ORCH-044 preflight был слеп к авторизации: разлогиненный инстанс клеймил job и тихо умирал с пустым логом, блокируя общую очередь всех проектов.
|
||
|
||
ORCH-044 добавляет **token-free** проверку (без сети, без prompt-ping — BR-1):
|
||
1. **Проактивно (основной гейт):** после успешного `--version` читается `<AGENT_HOME>/.claude/.credentials.json` (путь — `ORCH_CLAUDE_CREDENTIALS_PATH` или дефолт от `AGENT_HOME=/home/slin`, **не** HOME процесса орка). Нет файла / битый JSON / нет `claudeAiOauth.accessToken` ⇒ `check()=(False, …)`. `claudeAiOauth.expiresAt` (epoch ms) `<= now + ORCH_AUTH_EXPIRY_SKEW_SECONDS` ⇒ протух ⇒ FAIL. Нет `expiresAt` ⇒ OK (не плодим ложные срабатывания). Результат кешируется тем же `ORCH_PREFLIGHT_CACHE_TTL`, что и `--version`.
|
||
2. **Постфактум (защитная сетка):** если агент всё же стартовал при протухшей сессии, launcher детектит маркер (`not logged in` / `please run /login` / `unauthorized` / `401`) в run-логе и сбрасывает preflight-кеш, чтобы следующий тик переоценил auth. Auth-провал **не** считается transient и **не** крутит circuit breaker — гейт здесь preflight.
|
||
|
||
При `auth=fail` job **не клеймится** (`_drain_once` уже корректен при `ok=False`), reason виден в `/queue` (`preflight_reason`). Аварийный тумблер `ORCH_PREFLIGHT_CHECK_AUTH=false` возвращает version-only поведение.
|
||
|
||
> ⚠️ Риск ложноположительного auth-fail (R-1): неверный путь к credentials заблокирует клейм **всех** проектов (общая очередь). Митигация: единый источник `AGENT_HOME`, тумблер, обязательная проверка на staging (8501) перед прод-деплоем. ADR — `docs/work-items/ORCH-044/06-adr/ADR-001-preflight-auth-and-empty-result-failure.md`.
|
||
|
||
> ℹ️ `--effort` (P2) в ORCH-044 **не трогается** — вынесен в ORCH-50.
|
||
|
||
## ⚠️ Self-hosting — оркестратор дорабатывает САМ СЕБЯ
|
||
|
||
**Факт:** прод-инстанс `orchestrator` (8500) — ОДИН на ВСЕ прод-проекты (enduro-trails + orchestrator), с ОБЩЕЙ БД `./data/orchestrator.db` и общей очередью задач (ORCH-1).
|
||
|
||
**Следствие — групповой риск:** когда орк выполняет задачу из проекта ORCH (дорабатывает себя), он бежит в том же инстансе, что обслуживает enduro-trails.
|
||
- Рестарт / падение прод-контейнера орк-задачей → конвейер ВСЕХ проектов встаёт.
|
||
- Кривой self-деплой (ORCH-36, Вариант B) → лежат все проекты сразу.
|
||
- Общая очередь → орк-задача занимает concurrency-слоты других проектов.
|
||
|
||
**Что изолировано (безопасно):**
|
||
- Staging (8501) — отдельная БД (`./data/staging`), отдельный реестр (`ORCH_PROJECTS_JSON` = только sandbox). Прод-проекты не видит.
|
||
- Репозитории разделены, изоляция веток через git worktree (ORCH-2).
|
||
|
||
**Страховки:**
|
||
- Стадия `deploy-staging` (порт 8501) — обязательный гейт перед прод-деплоем орка. Прод-деплой недостижим, пока staging-гейт не зелёный (см. `STAGING.md`, ORCH-35). Гейт условный: реален только для self-hosting (repo=orchestrator), для остальных проектов — no-op.
|
||
|
||
**Правила для агентов при задачах ORCH:**
|
||
1. НЕ перезапускать / не ронять прод-контейнер `orchestrator` в рамках задачи.
|
||
2. Все проверки деплоя — на staging (8501), боевой 8500 не трогать.
|
||
3. Деплой self — только через хук с health-check + авто-rollback (`DEPLOY_HOOK.md`).
|
||
|
||
## Эксплуатация (быстрые команды)
|
||
```bash
|
||
# статус
|
||
docker ps --filter name=orchestrator
|
||
curl -s http://localhost:8500/health
|
||
curl -s http://localhost:8500/status # активные задачи
|
||
curl -s http://localhost:8500/queue # очередь
|
||
|
||
# поднять staging-песочницу
|
||
docker compose --profile staging up -d orchestrator-staging
|
||
curl -s http://localhost:8501/health
|
||
|
||
# логи
|
||
docker logs --tail 100 orchestrator
|
||
```
|
||
|
||
---
|
||
*RUNBOOK 2026-06-05. Обновлять при изменении топологии/портов/переменных. См. CONTRIBUTING.md §8.*
|