TTL-арифметика 2 cron-тиков (implied write 02:01:09 + 03:01:08 = journalctl
DONE) доказала: cron обновляет сессию каждый час. Ранний вывод «zombie lock»
был ошибкой замера. Root cause 3-дневного простоя — worker бежал со stale
--timeout=60 (timeout.conf=300 от 22.05 не подхвачен без рестарта); recycle
00:18 UTC подхватил правильный конфиг → самовосстановление.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Снимок-заметка: feature-фаза, на боевой liderra.ru ничего не выкачено.
Ветка feat/billing-v2-spec-c HEAD d8955f57 (9 ahead of main).
Phase 1 preflight баланса: заморозка cut-off 18:00 MSK + 409 при
перегрузке + 4 письма + initial-sweep. Schema v8.36. Pest 13/13.
Осталось: Task 1.10 frontend + Phase 2-5 (VTB безнал).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Корень: Redis ключ liderra-database-liderra-cache-supplier:session был пуст
во всех DB. SupplierPortalClient не мог авторизоваться → CSV recovery=0,
sync 7 дней пустой, поставщик не активировал выдачу.
Hot-fix: dispatchSync через cat-pipe tinker stdin (quirk #109 workaround).
TTL 5ч59м. CsvReconcileJob drift=0.425 — false-positive (мусор в project field).
scheduler+worker+cron конфигурация правильная (timeout=300, hourly entry,
liderra-queue active, /etc/cron.d/liderra-scheduler каждую минуту).
journalctl показал DONE каждый час, но Redis пуст — вероятно zombie lock
после 22.05 worker-крахов. Точный root cause не установлен; auto-verify
в 02:00 UTC.
Все 275 lead_charges Компании 1 = prepaid (старая схема); ни одного
charge_source=rub за всю историю прода. Биллинг v2 Phase A ждёт первого
нового лида чтобы впервые применить tier-lookup живьём.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
v2.0 → v2.1 — 3 группы изменений (16 пунктов суммарно):
Группа 1 — решения принятые после v2.0, не внесённые:
- 1.1 Памятка classifier (4 паттерна: brainstorming / discovery-interview /
writing-plans / systematic-debugging). +flag prompt-enrichment-mode.
- 1.2 Reviewer как полноценный Claude Code subagent (tools=[Read,Grep,Glob,
Skill], model=opus). Новый файл .claude/agents/reviewer-agent.md.
+стоимость $240-1200/мес vs $40-80 direct API. Crash fallback на direct
API. Context bloat cap 10 соседних эпизодов.
- 1.3 Inheritance + 3 группы коротких prompt'ов (continuation/acknowledgment/
cancellation) + 30-минутный таймаут. +flag inheritance-mode. Новые поля
в schema v4.1: inherited_from_task_id, inheritance_age_minutes,
previous_direction_rejected, previous_task_id_rejected.
Группа 2 — edge cases:
- 2.1 Reviewer model явно opus в agent file.
- 2.2 Reviewer subagent crash → fallback direct API call.
- 2.3 Reviewer context bloat: max 10 episodes в agent system prompt.
- 2.4 Manual override приоритет №1 в prefilter (раньше inheritance).
- 2.5 Cancellation clears state + previous_task_id_rejected marker.
Группа 3 — мелкие упущения:
- 3.1 brain-retro SKILL.md description: раз в 1-2 недели (не sprint).
- 3.2 recommended_chain_id nullable для custom chains.
- 3.3 Embedding только для non-prefilter эпизодов.
- 3.4 PII filter wraps sanity-check comments.
- 3.5 requested_node fuzzy matching fallback.
- 3.6 Anchor word list inline initial.
- 3.7 Self-retrospect counter init в фазе 3 step 3.3.
- 3.8 Sanity-check answer file schema_version=1.
Cost rewrite: 720-1380 USD (v2.0) -> 1940-8200 USD (v2.1) на 6 месяцев
из-за reviewer subagent. Granular rollback через reviewer-mode=direct-api
возвращает к v2.0 ценам.
§21 новый — changelog v2.0 → v2.1 со всеми 16 пунктами и где правка.
Реализация — после закрытия Биллинга v2 Спек C.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Миграция падала на проде:
SQLSTATE[42501]: Insufficient privilege: must be owner of table webhook_log
Причина: default connection 'pgsql' (crm_app_user) не имеет owner-прав на
webhook_log (owner — crm_migrator). Заменено на 'pgsql_supplier'
(BYPASSRLS-роль crm_supplier_worker) — паттерн Спека B Phase 1 (commit 546ca30a),
который выработан ровно под эту проблему prod-ролей.
Эта миграция блокировала выкатку legacy-webhook-removal (Phase 6 deploy
24.05.2026, отменено rollback'ом). После fix миграция применится
no-op (webhook_log будет дропнут моей миграцией 2026_05_24_140000
сразу после).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Remove 2 stale SupplierWebhookLoggingTest.php entries from phpstan-baseline.neon.
3 remaining unmatched inline @phpstan-ignore-next-line are pre-existing
(SupplierProjectGrouping/SupplierConnectionTest/Pest.php, present in origin/main).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
SupplierWebhookLoggingTest.php queried webhook_log table which was dropped
in Phase 4 DROP migration (schema v8.35). This file was missed in Phase 3
cleanup (WebhookReceiveTest.php was deleted but SupplierWebhookLoggingTest
was a separate file testing the same dropped infrastructure).
4 tests deleted — all tested webhook_log INSERT/SELECT which is now gone.
SupplierWebhookTest.php (new controller tests) remains unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Remove stale PdErasureService empty.variable ignore (no longer reported).
3 remaining unmatched inline @phpstan-ignore-next-line in SupplierProjectGrouping/
SupplierConnectionTest/Pest.php are pre-existing (present in origin/main).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
First functional smoke revealed 3 mismatches between agent's templated commands
and the real liderra.ru server layout:
1. App path: /var/www/liderra.ru/app/ → /var/www/liderra/app/ (no .ru segment).
Affected lines: П1, П2, П5, П8 + smoke command examples.
2. Backups path (П4): /var/backups/db/ → /home/ubuntu/backups/.
3. П2 `file .env` — needs sudo (ubuntu user lacks read on .env);
green criterion changed from "ASCII text" to "no CRLF substring"
(UTF-8 is normal when .env has Cyrillic comments).
Without these fixes the agent would issue false NO-GO on every real run
(paths fail / sudo denied) — surfaced by smoke per design ("unexpected
format → escalation, never guess"). Captured in memory.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- parseTranscript получает третий параметр options = {}
- options.routerStateBaseDir пробрасывается в readRouterState
- recommended_node: router-state переопределяет classification-map
- новые поля: recommended_chain, chain_progress, chain_completed
- 2 новых теста (enrich + fallback), 538/538 tools GREEN
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
readRouterState(sessionId, {baseDir}) -- pure read state-файла сторожа.
extractRouterFields(state) -- pure извлечение 4 полей для primary_rationale.
Используется парсером эпизодов на следующем шаге (Task 3).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pre-flight validator before liderra.ru deploys. Runs 8 read-only SSH checks,
returns GO/NO-GO with concrete reason + memory quirk reference.
Driven by 24.05.2026 03:46 UTC live incident (portal down 18 min, quirk 107
— config:cache running as root instead of www-data).
Spec: docs/superpowers/specs/2026-05-24-controller-offload-agents-design.md §4.
Precedent: .claude/agents/pest-parallel-debugger.md format.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
router-prehook, router-stop-gate, router-tool-gate теперь читают stdin
через readStdinAsUtf8 (StringDecoder). Русский в промпте корректно
доходит до Anthropic API и в state-файл — никаких mojibake типа
'посмотри'.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
StringDecoder correctly assembles multi-byte chars (Cyrillic) across
stdin chunk boundaries. Closes Windows Node quirk where Russian prompts
were turned into mojibake before sending to Anthropic API (Layer 2 escalation).
Stage 3 follow-up fix 1/3 (helper).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Project-local agent that applies synchronized version bumps + cross-refs +
footer counters + §9 changelog entries across Pravila/PSR/Tooling/CLAUDE.md
after a completed task. Does NOT commit. Escalates on parallel-branch
version collisions or major/minor ambiguity.
Spec: docs/superpowers/specs/2026-05-24-controller-offload-agents-design.md §3.
Precedent: .claude/agents/rls-reviewer.md format.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3-task TDD-ish plan to create two new project agents:
- Task 1: .claude/agents/normative-sync.md (full Sonnet 4.6 system prompt)
- Task 2: .claude/agents/prod-deploy-validator.md (8 SSH checks + quirks 104-108)
- Task 3: First dry-run smoke test for both + capture lessons in memory
Spec: docs/superpowers/specs/2026-05-24-controller-offload-agents-design.md (71a5dd6).
Also: +2 cspell-words (маппинге, dogfooded).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 дыры обнаружены при инспекции warn-only состояния сторожа 24.05.2026:
1. UTF-8 mojibake в state-файле сторожа (Windows Node stdin без setEncoding).
2. recommended_node не пишется в эпизоды наблюдателя (helper существует, не подключён).
3. chain_progress / chain_completed / recommended_chain — то же.
Без починки brain-retro новых метрик (domainHitRate / chainCompletionRate)
покажет пустоту, а Layer 2 эскалация на русских промптах работает по
испорченному тексту. Stage 3 enforce включать до фиксов нельзя.
Spec scoped к 3 файлам кода + ≤80 строкам нетто; subagent-driven
последовательно (3 Sonnet + closure Opus). Smoke на живой сессии.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Spec for two specialized AI agents to offload the main controller:
- #1 normative-sync: applies 4-file normative sync (Pravila/PSR/Tooling/CLAUDE.md)
after a completed task. ~20 invocations/week, saves ~70K controller tokens
per episode. Model: Sonnet 4.6.
- #2 prod-deploy-validator: 8-check pre-flight before liderra.ru deploy.
~5-7 invocations/week. Driven by 24.05 03:46 UTC 18-min portal incident
(quirk 107 — config:cache not under www-data). Model: Sonnet 4.6.
Based on brainstorming session 24.05 with measured frequencies from
MEMORY.md + CLAUDE.md §6 + push history 16-24.05.
Precedents: pest-parallel-debugger, rls-reviewer project agents.
Also: +7 cspell-words entries for the new spec.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pre-sharing-эпик legacy: ProcessWebhookJob + WebhookReceiveController +
POST /api/webhook/{token} + webhook_log/webhook_dedup_keys/tenants.webhook_token.
На проде 0 вызовов за всю историю. Не часть актуальной архитектуры каналов
(основной = шеринг crm.bp-gr.ru, резервный = CSV reconcile — оба уже на always-rub).
Удаление снимает блокер для Phase B Спека A (DROP COLUMN balance_leads),
закрывает публичный endpoint /api/webhook/{token}, убирает расхождение
биллинг-моделей в коде.
Approach: одним PR, одним релизом. Бэкап перед миграцией, 7д наблюдение.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
active-projects.md обновлён: этап 2 ✅ + влит в main, этап 3 ✅ Phase A+B + влит
в main (warn-only режим, никакой блокировки), bug-fix bec69aa5 (deriveRouterStep)
включён. CHECKPOINT B накапливает реальные наблюдения; Task 9 (переключение
в enforce + 2 метрики) — отдельно после ревью baseline.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Root cause: primary_rationale.step было жёстко прописано как литерал `1` в обоих
episode-builder'ах (observer-transcript-parser.mjs:813, observer-stop-hook.mjs:153).
Поэтому routerStepReached видел { '1': N } и suspicious=true для ВСЕХ данных —
показатель измерял константу, а не дисциплину роутера.
Фикс: новая чистая функция deriveRouterStep(primary_rationale) — берёт максимум
наблюдаемой стадии router-procedure.md из реальных признаков
(task_classification ≠ 'other' → 2; triggers_matched → 3; chain_ref → 4;
node_chosen ≠ 'direct' → 5). routerStepReached теперь вызывает её при чтении,
игнорируя хранимое pr.step. Это делает метрику честной для ВСЕХ существующих
эпизодов (включая исторические 136 за май) — без миграции данных.
Boost для baseline'а CHECKPOINT B этапа 3: на боевых данных
(131 schema-v2+ эпизод) distribution теперь = { 1: 55, 2: 46, 3: 12, 5: 18 },
suspicious=false. Видно реальную картину: ~42% эпизодов остановились на hard-floor,
только ~14% реально дошли до исполнения навыка.
Follow-up: episode-builder'ы продолжают писать step:1 (теперь это безвредно —
метрика игнорирует). Отдельно можно прибрать запись в builder'ах для
self-describing эпизодов.
Test changes:
- tools/discipline-metrics.test.mjs: +describe('deriveRouterStep') (9 cases),
routerStepReached describe переписан под сигналы-источник.
- tools/brain-retro-analyzer.test.mjs: 'returns routerStepReached distribution'
обновлён — эпизоды конструируются с сигналами (triggers vs bare),
не хранимым step.
Full tools/ vitest run: 520/520 GREEN. 4 pre-existing empty test files
(ruflo-*, subagent-prompt-prefix) — не моя регрессия.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Эпизоды реальных сессий после hotfix — сторож пишет state + классификацию
на каждый промпт (verified: UUID-session state files). Данные для brain-retro
warn-only ревью.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two real bugs found via verification (hook didn't fire in live session):
1. UserPromptSubmit block had matcher:"*" — event doesn't support matcher,
non-standard block dropped (claude-code-guide authoritative). Removed →
block now {hooks:[...]} like working observer-stop-hook.
2. stdin field was event.user_prompt; Claude Code sends event.prompt.
Now reads (event.prompt || event.user_prompt) for compat.
Field-fix verified manually with real stdin shape {prompt:...} → #71 pdn-152fz.
Firing fix (matcher) NOT verifiable in-session (hooks load at session start) —
needs restart + next-turn state-file check.
NB stop-gate turn_events field also wrong (Stop sends transcript_path) — separate
follow-up, not on observation critical path (affects chain tracking only).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
UserPromptSubmit → router-prehook (classifier).
PreToolUse Edit|Write|MultiEdit|Bash → router-tool-gate (warn-only).
Stop → router-stop-gate (chain progress).
router-gate-mode.json = warn-only (outside repo, ~/.claude/runtime/).
Переключение в enforce — отдельным шагом после Checkpoint B (24ч наблюдения).
End-to-end smoke verified: «проверь пдн» → #71 pdn-152fz-audit,
warn-only пишет в stderr, не блокирует. Доменная разметка Task 1 работает.
NB активация в основной сессии: хуки в worktree-копии settings.json
версионируются; для реального наблюдения нужна либо merge ветки, либо
ручное применение к основному .claude/settings.json (warn-only безопасен).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
После каждого хода обновляет state.chainProgress по реально вызванным
скилам. chainCompleted=true когда последний шаг достигнут.
skillInvokedThisTurn флажок для PreToolUse gate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Подкрутка classifier'а БЕЗ правки реестра (доменная разметка Task 1 сохранена):
- TASK_TYPE_KEYWORDS +командные глаголы (проверь/составь/поправь/распиши/...);
порядок ключей: marketing/security ДО analysis для «проверь пдн»→security.
- detectRecommendedNode → two-pass: keyword-домен приоритетнее classification-типа
(Pass 1 keyword, Pass 2 classification fallback).
- MICRO_KEYWORDS +увеличь/уменьши/одну строку/bump.
Accuracy regex-only: 68.3% → 80.0% (type 55%→85%, micro 95%→100%, node 55%).
Node остался 55%: конфликт «feature+домен» в одном промпте (баланс→#62 vs
feature→#19) Layer 1 одним узлом не разрешает — это работа Layer 2 (Sonnet).
Ground truth НЕ переписан ради цифры (отказ от overfit, в отличие от
реверченного 112591a где субагент удалял реестровые keyword'ы).
489/489 tools GREEN.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>