diff --git a/.github/workflows/artisan-run.yml b/.github/workflows/artisan-run.yml index ab0f9b6c..fc7fc867 100644 --- a/.github/workflows/artisan-run.yml +++ b/.github/workflows/artisan-run.yml @@ -45,7 +45,7 @@ jobs: echo "Requested: '$CMD_TRIM'" # Group 1 — read-only / dry-run / inspection: всегда разрешены - READ_ONLY_RE='^(migrate:status|route:list|schedule:list|queue:listen --help|about|env:show|config:show|cache:table|view:cache|optimize:status|snapshot:backfill( --date=20[2-9][0-9]-[0-1][0-9]-[0-3][0-9])?|scheduler:check-heartbeats|incidents:watch-failures( --threshold-spike=[0-9]+)?( --threshold-daily=[0-9]+)?( --persistent-hours=[0-9]+)?|supplier:rekey-orphans --dry-run|audit:verify-chains|partitions:list)( *)$' + READ_ONLY_RE='^(migrate:status|route:list|schedule:list|queue:listen --help|about|env:show|config:show|cache:table|view:cache|optimize:status|snapshot:backfill( --date=20[2-9][0-9]-[0-1][0-9]-[0-3][0-9])?|scheduler:check-heartbeats|incidents:watch-failures( --threshold-spike=[0-9]+)?( --threshold-daily=[0-9]+)?( --persistent-hours=[0-9]+)?|supplier:rekey-orphans --dry-run|audit:verify-chains)( *)$' # Group 2 — mutating: требуют confirm_apply=true MUTATING_RE='^(supplier:rekey-orphans|cache:clear|view:clear|config:clear|route:clear|optimize:clear|optimize|queue:restart|partitions:create-months( --months=[0-9]+)?|partitions:drop-old)( *)$' diff --git a/.github/workflows/stage5-daily-monitor.yml b/.github/workflows/stage5-daily-monitor.yml new file mode 100644 index 00000000..47c69baa --- /dev/null +++ b/.github/workflows/stage5-daily-monitor.yml @@ -0,0 +1,117 @@ +name: Stage 5 daily monitor (29.05→04.06) + +# Автоматический ежедневный мониторинг 3 ключевых сигналов прода +# во время 7-дневного окна перед переключением supplier_export_mode +# online→batch (Stage 5 Task 5.1). +# +# Запускается GitHub-cron'ом каждое утро 06:00 UTC (09:00 МСК) +# 29.05.2026 — 04.06.2026 (после 04.06 workflow можно отключить +# через UI Actions tab → Disable workflow, либо удалить файл). +# Также доступен ручной запуск через workflow_dispatch. +# +# Выводит результаты в job summary + сохраняет как artifact. +# +# План мониторинга: +# docs/superpowers/plans/2026-05-29-stage5-monitoring-checklist.md + +on: + schedule: + # 06:00 UTC = 09:00 МСК ежедневно + - cron: '0 6 * * *' + workflow_dispatch: + +jobs: + monitor: + runs-on: ubuntu-latest + timeout-minutes: 10 + + # Жёсткий стоп — workflow ничего не делает после 04.06.2026 даже + # если кто-то забудет отключить. CRON в GitHub Actions не имеет + # "until date" — реализуем через if-check на runner side. + if: github.event_name == 'workflow_dispatch' || github.event.schedule == '0 6 * * *' + + env: + LIDERRA_HOST: 111.88.246.137 + LIDERRA_USER: ubuntu + + steps: + - name: Check window not expired + id: window + run: | + TODAY=$(date -u +%Y-%m-%d) + DEADLINE='2026-06-05' # 04.06 + 1 день grace + if [[ "$TODAY" > "$DEADLINE" ]]; then + echo "::notice::Stage 5 monitoring window closed at $DEADLINE. Disable this workflow via Actions UI." + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + + - name: Setup SSH key + if: steps.window.outputs.skip != 'true' + run: | + mkdir -p ~/.ssh + echo "${{ secrets.LIDERRA_SSH_KEY }}" > ~/.ssh/liderra_deploy + chmod 600 ~/.ssh/liderra_deploy + ssh-keyscan -H ${{ env.LIDERRA_HOST }} >> ~/.ssh/known_hosts 2>/dev/null + + - name: Run 3 checks + if: steps.window.outputs.skip != 'true' + run: | + ssh -i ~/.ssh/liderra_deploy ${{ env.LIDERRA_USER }}@${{ env.LIDERRA_HOST }} 'bash -s' <<'REMOTE' | tee /tmp/monitor.log + set +e + cd /var/www/liderra/app + echo "=== Date: $(date -u) ===" + + echo + echo "=== 1. scheduler:check-heartbeats ===" + sudo -u www-data php artisan scheduler:check-heartbeats 2>&1 + echo "Exit: $?" + + echo + echo "=== 2. incidents:watch-failures ===" + sudo -u www-data php artisan incidents:watch-failures 2>&1 + echo "Exit: $?" + + echo + echo "=== 3. migrate:status ===" + sudo -u www-data php artisan migrate:status 2>&1 | tail -8 + echo "Exit: $?" + + echo + echo "=== Auxiliary signals from system tables ===" + echo "--- last 3 incidents_log entries ---" + sudo -u postgres psql -d liderra -tA -c "SELECT severity, created_at, root_cause FROM incidents_log ORDER BY created_at DESC LIMIT 3;" 2>&1 + echo "--- snapshot count last 3 days ---" + sudo -u postgres psql -d liderra -tA -c "SELECT snapshot_date, COUNT(*) FROM project_routing_snapshots GROUP BY 1 ORDER BY 1 DESC LIMIT 3;" 2>&1 + echo "--- failed_webhook_jobs last 24h count ---" + sudo -u postgres psql -d liderra -tA -c "SELECT COUNT(*) FROM failed_webhook_jobs WHERE failed_at > NOW() - INTERVAL '24 hours';" 2>&1 + echo "--- scheduler_heartbeats with failures ---" + sudo -u postgres psql -d liderra -tA -c "SELECT command_name, consecutive_failures, last_run_at FROM scheduler_heartbeats WHERE consecutive_failures > 0 ORDER BY consecutive_failures DESC;" 2>&1 + + echo + echo "=== DONE ===" + REMOTE + + - name: Print summary + if: always() && steps.window.outputs.skip != 'true' + run: | + { + echo "## Stage 5 daily monitor — $(date -u +%Y-%m-%d)" + echo + echo '```' + cat /tmp/monitor.log 2>/dev/null || echo "(no output)" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + - name: Upload as artifact + if: always() && steps.window.outputs.skip != 'true' + uses: actions/upload-artifact@v4 + with: + name: monitor-${{ github.run_id }} + path: /tmp/monitor.log + retention-days: 14 + + - name: Cleanup SSH key + if: always() + run: rm -f ~/.ssh/liderra_deploy diff --git a/docs/observer/STATUS.md b/docs/observer/STATUS.md index b7c2130f..28dd49a8 100644 --- a/docs/observer/STATUS.md +++ b/docs/observer/STATUS.md @@ -1,6 +1,6 @@ # Brain Status (auto-generated) -Last updated: 2026-05-29T02:13:22.220Z +Last updated: 2026-05-29T02:24:47.886Z | Контролёр | Состояние | Детали | |---|---|---| @@ -8,13 +8,13 @@ Last updated: 2026-05-29T02:13:22.220Z | C2 Cross-ref consistency | ✅ | [cross-ref-checker] OK — 0 drift in 4 files | | C3 Observer-of-observer | ✅ | [observer-of-observer] OK — last read 0 week(s) ago | | C4 Сигнальный статус | ✅ | This file (self-reference) | -| C5 Observer-coverage | ⚠️ | 656 episode(s) this month · Stop-hook + post-commit OK · 20 missed activation(s) — see /brain-retro | +| C5 Observer-coverage | ⚠️ | 660 episode(s) this month · Stop-hook + post-commit OK · 20 missed activation(s) — see /brain-retro | | C6 Chain map sync | ✅ | [chain-map-checker] OK — 16 chains in sync | ## Метрики (информационные, не алерты) -- Observer evidence: 656 episodes this month, 0 observer_error markers, 130 PII matches before filter -- Legacy v1 episodes (not in factor analysis): 517 +- Observer evidence: 660 episodes this month, 0 observer_error markers, 131 PII matches before filter +- Legacy v1 episodes (not in factor analysis): 521 - Last /brain-retro: 2 day(s) ago - Использование узлов: см. `/brain-retro` (раз в спринт). missed_activations: 20. **Неиспользованные узлы — не алерт, если профильной задачи не было** (Pravila §16.4 v1.36; capability-readiness; см. memory `feedback_brain_unused_tools_not_problem` — outside-repo memory store). @@ -31,9 +31,9 @@ Baseline дисциплины роутера (этап 2 router discipline overh | cleanup | 6 | 0.0% | 0.0% | | refactor | 1 | 0.0% | 0.0% | -Router step distribution: 1: 288, 2: 236, 3: 63, 5: 60 +Router step distribution: 1: 292, 2: 236, 3: 63, 5: 60 -Boundaries applied (ADR / границы): 75 of 647 эпизодов (11.6%). +Boundaries applied (ADR / границы): 75 of 651 эпизодов (11.5%). ## Активные многоэтапные проекты @@ -51,10 +51,10 @@ Boundaries applied (ADR / границы): 75 of 647 эпизодов (11.6%). | Компонент | Токены (in/out) | USD | |---|---|---| -| Classifier (Sonnet 4.6) | 3448/44775 | $0.68 | +| Classifier (Sonnet 4.6) | 3513/46753 | $0.71 | | Self-assessment (Sonnet 4.6) | 0/0 | $0.00 | | Reviewer (Opus 4.7 + fallback) | 0/0 | $0.00 | -| **Итого** | | **$0.68** | +| **Итого** | | **$0.71** | ## Аномалии классификатора @@ -67,7 +67,7 @@ Episodes since last run: 542 / threshold: 10 ## Reviewer: субагент vs fallback -0 эпизодов проверено из 656. +0 эпизодов проверено из 660. ## Reviewer findings @@ -110,7 +110,7 @@ Episodes since last run: 542 / threshold: 10 | Фраза | За всё время | За сегодня | |---|---|---| | `recovery` | 901 | 4 | -| `ремонт инфраструктуры` | 205 | 20 ⚠️ | +| `ремонт инфраструктуры` | 220 | 35 ⚠️ | | `без скилов` | 185 | 7 ⚠️ | | `срочно` | 93 | 0 | | `memory dump` | 17 | 0 | @@ -119,7 +119,13 @@ Episodes since last run: 542 / threshold: 10 ## System Health -Долго работающих процессов нет (порог CPU > 1ч). +Топ-3 процессов с CPU > 1ч: + +| PID | Имя | CPU-время | Возраст | +|---|---|---|---| +| 3464 | MsMpEng | 1.04ч | NaNч | + +⚠️ Проверь, не «осиротевшие» ли это процессы от завершённых Claude-сессий. ## Алерт-индикаторы diff --git a/docs/superpowers/plans/2026-05-29-stage5-monitoring-checklist.md b/docs/superpowers/plans/2026-05-29-stage5-monitoring-checklist.md new file mode 100644 index 00000000..8af1cf44 --- /dev/null +++ b/docs/superpowers/plans/2026-05-29-stage5-monitoring-checklist.md @@ -0,0 +1,121 @@ +# Stage 5 monitoring checklist — 7-дневное окно 29.05 → 04.06.2026 + +**Старт окна:** 28.05.2026 20:32 МСК (Stage 4 выкачен на боевой liderra.ru). +**Цель окна:** 7 дней без P0-регрессий → 04.06.2026 переключаем `supplier_export_mode` `online → batch` (Stage 5 Task 5.1). + +**Как проверять:** через `gh workflow run artisan-run.yml -f command="<команда>"` (см. `.github/workflows/artisan-run.yml`). Результат — в Actions summary / artifact. + +--- + +## Ежедневный чек-лист (5 минут) + +### 1. Cron'ы живут (scheduler_heartbeats) + +``` +gh workflow run artisan-run.yml -f command="scheduler:check-heartbeats" +``` + +**Expected:** `No missing heartbeats detected` или похожий короткий вывод. + +**Тревога:** в выводе строки `MISSING` или `WARN: command X consecutive_failures>=3` → расследовать (что ломается). Связано: `SchedulerHeartbeatMissingMail` на `kdv1@bk.ru`. + +### 2. Incident-watcher (failed jobs spike / persistent) + +``` +gh workflow run artisan-run.yml -f command="incidents:watch-failures" +``` + +**Expected:** `No failure spikes detected` или похожее. + +**Тревога:** `incidents_log` insert или email `[Лидерра инцидент]` на kdv1@bk.ru → проверить какая джоба льёт failures. + +### 3. Состояние миграций (что схема та же что после Stage 4 deploy) + +``` +gh workflow run artisan-run.yml -f command="migrate:status" +``` + +**Expected:** все строки `Ran`, последняя — `2026_05_27_120000_create_project_routing_snapshots_table` (или ещё свежее, если что-то долили). 0 строк `Pending`. + +**Тревога:** `Pending` миграции, которых не должно быть → значит кто-то залил изменения схемы вне процесса. + +### 4. Партиции на следующий месяц созданы (ОТЛОЖЕНО) + +**Проблема:** read-only artisan-команды `partitions:list` или `partitions:status` в проекте нет — есть только mutating `partitions:create-months` и `partitions:drop-expired`. Проверить наличие будущих партиций через GitHub Actions сейчас нельзя. + +**Workaround:** партиции `project_routing_snapshots` на май+июнь 2026 были созданы inline в миграции `2026_05_27_120000_create_project_routing_snapshots_table` при deploy этапа 4 — на 7-дневное окно мониторинга 29.05→04.06 этого достаточно. **К моменту перехода в июль (≈25.06) нужно либо:** +- (а) добавить read-only артизан-команду `partitions:status` в код приложения, либо +- (б) запустить `partitions:create-months --months=2` через `gh workflow run artisan-run.yml -f command="partitions:create-months --months=2" -f confirm_apply=true`. + +**На текущее окно — пункт пропускается.** + +### 5. Просмотр почты `kdv1@bk.ru` + +Ручная проверка: +- `[Лидерра ПАДЕНИЕ]` — портал упал >4 мин. +- `[Лидерра восстановлен]` — портал поднялся (после падения). +- `[Лидерра инцидент]` — auto-watcher нашёл подозрение. +- `liderra-queue` systemd alert — очередь упала ≥5 раз за 5 мин. +- `CsvDriftAlertMail` — рассинхрон CSV с поставщиком >5%. +- **`TenantBusinessDriftAlertMail` (новое — добавлено в Stage 4 R-05)** — shortfall в `project_routing_snapshots` per (snapshot_date, tenant_id) >20%. **Если приходит ежедневно** — Stage 5 переключение надо отодвинуть до выяснения. + +--- + +## Что особенно следим (R-checks из Stage 4) + +| R | Что проверить | Команда / источник | +|---|---|---| +| R-01 | Снимок снимается ежедневно в 18:02 МСК — есть строка за каждый день | `partitions:list` + ручной просмотр `project_routing_snapshots` через future SQL workflow | +| R-05 | Business-drift не зашкаливает | Почта `TenantBusinessDriftAlertMail` | +| R-13 | `paused_at` синхронизируется с заморозкой проекта | Sentry / `incidents_log` — нет ошибок при тогле проектов | +| R-17 | Новые SMS-проекты создаются с unified key (не дают orphan) | `supplier:rekey-orphans --dry-run` раз в 2-3 дня → должно оставаться `No orphan SMS supplier_projects found` | +| R-18 | `targetWeekdayForNow` не дёргается в полночь | Sentry: нет вспышек ошибок в `SyncSupplierProjectJob` около 00:00 МСК | +| R-19 | Preflight у шерящихся клиентов даёт корректные пропорции | Ручная проверка `Tenant::requiredLeadsForTomorrow` через будущий SQL workflow для топ-5 шерящихся клиентов | + +--- + +## Trigger «переключение преждевременно — отодвинуть» + +Если **хотя бы одно из перечисленного** случится в окне: + +- ≥1 P0-инцидент в `incidents_log` связанный с supplier-routing +- ≥3 ежедневных `TenantBusinessDriftAlertMail` в одну неделю +- 502/503 на портале >30 минут +- `failed_webhook_jobs` накопил >100 за день +- Любое падение `liderra-queue` сервиса по `OnFailure=` +- `consecutive_failures >= 3` у `SyncSupplierProjectsJob` или `CsvReconcileJob` +- 0 строк в `project_routing_snapshots` за день (сnapshot не снялся) +- Прямые жалобы клиентов на missing leads + +→ Stage 5 откладывается на +N дней (N = время диагноза + 3 дня чистого окна после фикса). + +--- + +## Trigger «всё чисто — двигаем 04.06» + +7 дней (29.05 → 04.06) подряд: +- 0 P0 +- 0 TenantBusinessDriftAlertMail +- scheduler_heartbeats: все green +- incidents:watch-failures: clean каждый день +- migrate:status: ровно тот же набор +- supplier:rekey-orphans --dry-run: clean каждый прогон +- Нет жалоб клиентов + +→ 04.06.2026 запускаем Stage 5 Task 5.1: переключение `supplier_export_mode='batch'`. После переключения — ещё 7 дней наблюдения по этому же чек-листу. Если 14 дней суммарно чисто — Stage 5 закрыт окончательно. + +--- + +## Расписание + +| День | Дата | Кто проверяет | Что особенно | +|---|---|---|---| +| День 1 | 29.05.2026 (ПТ) | Claude | Все 5 пунктов + R-17 dry-run | +| День 2 | 30.05.2026 (СБ) | Claude | Все 5 пунктов | +| День 3 | 31.05.2026 (ВС) | Claude | Все 5 пунктов | +| День 4 | 01.06.2026 (ПН) | Claude | Все 5 + повторный R-17 dry-run | +| День 5 | 02.06.2026 (ВТ) | Claude | Все 5 пунктов | +| День 6 | 03.06.2026 (СР) | Claude | Все 5 пунктов | +| День 7 | 04.06.2026 (ЧТ) | Claude+заказчик | Все 5 + решение GO/NO-GO Stage 5 | + +Можно ставить через `/loop` skill (раз в день) или `/schedule` cron-job — после согласования с заказчиком.