- 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>
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>
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>
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>
После каждого хода обновляет 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>
При каждом prompt'е: classifier → state-файл ~/.claude/runtime/router-state-<session>.json.
isEnforcementRequired — guard: micro/question/memory-sync пропускают.
Cache per-prompt-hash в runtime/router-classification-cache.json.
Любая ошибка прехука — silent fallback, пользовательский поток не ломается.
Smoke-test verified: regex-only path работает без ANTHROPIC_API_KEY.
Fix: CLI guard использует fileURLToPath для корректного сравнения путей с кириллицей (Windows quirk).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
buildLLMPrompt сериализует активные узлы + chains в prompt.
classify() — гибрид regex + LLM с кэшем per-prompt-hash.
callAnthropicAPI через built-in fetch (без SDK).
shouldEscalate: confidence<0.7 AND not micro.
Fallback на regex-result при ошибке LLM.
NB: real-API verification отложена — нет ANTHROPIC_API_KEY на dev-машине;
Phase A 'вариант 2': mock-тесты only. Когда ключ появится, код заработает
без изменений.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
classifyByRegex(prompt, registry) → {taskType, micro, recommendedNode, confidence, source}.
Read-only, без fs/exec/net. RU+EN keyword'ы для типа задачи + детект micro
+ матч по keyword/classification триггерам активных узлов реестра.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Зафиксированы цифры дисциплины роутера на 2026-05-24 перед запуском
enforcement-хука этапа 3. Sanity-check passed: missed_before=17 ==
missed_after=17 (delta=0) после переключения источника правды на реестр.
observer-classification-map.json помечен deprecated — для удаления в этапе 4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Auto-generated блок с разбивкой % дисциплины по типам задач,
router-step distribution + suspicious-флаг, boundaries-applied rate.
Backward-compat: блок опускается, если discipline не передан.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stage 2 Task 2 — заменяет observer-classification-map.json и
extract-node-dormancy.mjs как источник истины для missed-activation
matcher. Реестр nodes.yaml становится single source.
Pure module, read-only, без exec/fs (caller passes loaded registry).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
status-md-generator рендерит блок «Активные многоэтапные проекты»
из repo-local docs/observer/active-projects.md (если файл есть).
renderStatus backward-compatible: без activeProjects блок пустой.
active-projects.md — single source состояния многоэтапного router
overhaul (этап 1 ✅ закрыт, этапы 2-4 pending). Будущая сессия видит
статус в STATUS.md dashboard + memory tracker.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
renderAll() режим без --check переписывает файлы; с --check
возвращает exit 1 на drift (для lefthook).
Сейчас рендерит 2 региона: Tooling summary + routing-table.
Маркеры в Markdown добавим в Task 6 (без них скрипт корректно падает
с понятной ошибкой Markers not found).
Fix: entry-point guard использует fileURLToPath+resolve вместо
string-замены — совместимость с кириллическими путями (Windows quirk).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Покрытие: индексация по classification/keyword, exclude
historic/dormant из индексов, cache lifecycle, schema violation,
chain membership lookup.
Все 11 GREEN на пилотном реестре из 3 узлов.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Экспортирует loadRegistry/findByClassification/findByKeyword/
findActiveNodes/findChainsByNode + clearCache для тестов.
Кэширует в module-scope (per-process); валидирует через ajv при
загрузке (schema + ajv-formats). Keyword индексация case-insensitive
(.toLowerCase()) для последовательности с findByKeyword.
Тестов нет — Task 4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final-review followup. .claude/settings.json uses regex-style combined
matchers like "Edit|Write"; transcript writes per-tool PreToolUse:Edit.
Split on | when building map so per-tool counts resolve. Also sync
spec doc loadHookMap -> buildHookMap (impl name).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors missed-activations dormancy logic (id === false strict).
First live recommended node from classification-map, else null.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code-review followup. TOOL_SCRIPT_RE didn't include \ in delimiter
char class — Windows-native commands like `node tools\foo.mjs` fell
through to inline:<sha> fallback. Added \ to char class + inner
[\/\] alternation, normalize match to forward-slash.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
extractCandidates грузила в primary_rationale.candidates_considered ЛЮБОЙ
нумерованный/маркированный список из ассистентского текста — без
семантического фильтра. В topе оказывались куски прозы («Hard-floor работает
только для §12 Superpowers …»), шаги процедуры («1. Hard-floor check, 2.
Классификация …»), фрагменты кода (regex-паттерны) — не имена узлов реестра.
Фикс: при загрузке модуля собираю KNOWN_NODES из tools/observer-known-nodes.txt
+ ключей observer-chain-map.json + сентинела «direct». После regex-извлечения
item нормализуется (срезаются **/`/_/* обвязки + хвостовая пунктуация) и
проверяется по: точное имя в реестре ИЛИ #NN (Tooling ID) ИЛИ plugin:skill
форма. Если после фильтра <2 элементов — return []. Opt-in <!-- reasoning -->
тег остаётся authoritative и идёт мимо фильтра.
Триггеры/границы не трогал — их regex уже узкий (Pravila §N / ADR-N / PSR_v1
RN / L-цепочки).
Repro-кейсы из живого episodes-2026-05.jsonl добавлены в тесты: prose-bullets,
procedure-steps, code-snippet bullets, mixed list, single survivor.
Pure module. buildHookMap(project, user) reverse-lookup settings.json,
resolveScriptCounts duplicates counts per script. No exec.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug: gitleaks (rule `ru-phone-unmasked`) caught `79135191264` in 3 lines
of docs/observer/episodes-2026-05.jsonl during brain-retro #3 push
(963379c3). Stop-hook PII-filter was not masking bare-format Russian
phone numbers (without the `+` prefix).
Root cause:
const RU_PHONE = /\+7\d{10}/g; // requires literal '+7'
Free-text observer episodes captured phone `79135191264` in field-value
context (`call client 79135191264` / `phone 79135191264 in payload`),
slipping past the existing filter.
Fix:
const RU_PHONE = /(?:\+7|\b7)\d{10}/g;
The `\b7` branch catches bare format with a word-boundary on the left,
avoiding false-positives inside long digit sequences (timestamps, IDs,
hashes). False-positive guard verified via test:
'id 1796133619135191264999 not a phone' → unchanged.
TDD cycle:
- RED: 3 new tests + 1 sanitizeWithCount test (4 fails on bare phone)
- GREEN: regex extended, 24/24 file tests pass, 373/373 full tools
suite GREEN (0 regressions across 18 files).
Cleanup: applied sanitize() to docs/observer/episodes-2026-05.jsonl;
11 lines touched (3 phone-leak lines + 8 with other PII patterns).
gitleaks now finds 0 leaks in the file.
Pravila §5.2 (no PII in commits) + 152-FZ (phone is regulated PD).
Closes DO-PII-1 (see memory observer-pii-leak-2026-05-23).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brain-retro #3 за весь май 2026 — 116 v2-эпизодов / 61 task_ref.
Здоровье: 0 observer_error, 1.7% correction-rate, 19 skill-инвокаций
(vs 6 в ретро #2 — рост в 3×).
Применены 4 кандидата по явному «делай» от заказчика:
A1. observer-classification-map.json: question → [] (был ["#60"])
Разговорные RU-вопросы давали 17/40 false-positive промахов против context7.
A2. observer-classification-map.json: memory-sync → [] (был ["#33"])
#33 claude-md-management — канал ТОЛЬКО для CLAUDE.md (Pravila §5 п.10),
не для memory/*.md. Давало 8/40 false-positive.
B1. Tooling §4.8 #34 Sentry MCP — boundaries +DEFERRED
Sentry instance не задеплоен (pending Б-1). Двойной сигнал
extractor'а → .node-dormancy.json[#34] = true.
D1. memory/feedback_feature_via_writing_plans.md (user-memory вне git).
Effect: missed-activations 40 → 15 после очистки шума. Из 15 реально
значимы 2 эпизода (audit-journaling closure 116 tools без writing-plans;
SyncSupplierProjectJobTest planning без skill). Остальные 13 — шум
классификатора на правках своих документов.
+cspell-words.txt: 20 слов (9 секций Tooling + 11 из retro-note).
NB: docs/observer/episodes-2026-05.jsonl снят со staging — gitleaks
обнаружил 3× RU-phone leak (`ru-phone-unmasked` rule). Это сигнал что
observer PII-фильтр пропустил телефон в free-text record — отдельный
follow-up (PII фильтр Stop-хука).
Retro-отчёт: docs/observer/notes/2026-05-23-brain-retro.md.
STATUS.md перегенерирован.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
phpstan-baseline.neon analyses the whole project and drifts from parallel Claude
sessions + stale ide-helper (ImportLog @mixin etc.) → hundreds of ignore.unmatched
block unrelated commits. Larastan stays in lefthook.yml (CI/Linux) + manual
`composer stan` before push. pint (not baseline-dependent) stays in pre-commit.
C1 (l1-watcher): brand-voice (settings.json ключ brand-voice@knowledge-work-plugins) формализован #76 под человеческим именем — добавлен алиас в tools/.l1-watcher-aliases.txt (как frontend-design).
C6 (chain-map): L16 (marketing chain) была в routing-off-phase.md, но не в observer-chain-map.json — добавлены узлы marketing/marketing-ru/yandex-metrika/wordstat/telegram/postiz + L16 к brainstorming.
Контролёры: l1-watcher 0 drift, chain-map-checker 16 chains in sync.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
lefthook 2.1.x не завершает pre-commit при git commit на пути
"C:\моя\проекты\портал crm\Документация" (кириллица+пробел): проверки
проходят, но движок виснет на git stash/index.lock и плодит node-зомби.
Решение (выбор заказчика «свой простой скрипт»):
- tools/git-hooks/pre-commit.sh — нативная замена, зеркалит джобы lefthook.yml
(gitleaks/markdownlint/cspell/stylelint/pint/larastan/squawk/eslint), но
вызывает инструменты напрямую (node <entry>, не npx) и НЕ модифицирует index
(нет git add/--fix) → нет конфликта за .git/index.lock. Явный exit.
- .git/hooks/pre-commit (локальный, не в git) → диспетчер на этот скрипт.
- lefthook.yml: npx→node в md/cspell/stylelint джобах + убран stage_fixed
(markdownlint/pint) — кросс-платформенно безопасно, для CI/Linux где lefthook
работает штатно (lefthook.yml остаётся источником истины конфигурации).
- lefthook 2.1.6→2.1.8.
post-commit (status-md) и pre-push lefthook работают штатно — не трогаю.
Bypass: LEFTHOOK=0 git commit ...
После A8-эпика 21.05 (#68-73 ZAP/Nuclei/Ward + pdn-152fz/threat-model/security-
go-live) у наблюдателя был пробел: classification-map не содержал security-
категории. Реальный classifier (за май) выдаёт 10 значений (refactor/bugfix/
feature/planning/memory-sync/monitoring/other/cleanup/question/docs) — нет
security. Поэтому missed-activations matcher НИКОГДА не рекомендовал A8-узлы
и не мог флагнуть их пропуск. Заказчик подтвердил выбор «А — расширить».
Добавлено:
- "security": ["#73","#69","#68","#70","#71","#72"] — #73 security-go-live
как orchestrator первый, далее CLI-инструменты #69/#68/#70, затем skill-
audit #71/#72. Порядок — порядок приоритета рекомендации.
Описание расширено: классификатор не имеет жёстко прописанного enum
(brain-retro-analyzer.mjs:166 — это free judgment Claude'а при записи
эпизода), добавление ключа в map делает его 'blessed'. Граница: "security"
= задачи где ЦЕЛЬ верификация/улучшение безопасности (сканы/hardening/
аудиты/STRIDE/go-live); НЕ для bug-fix'ов в security-relevant коде (те
остаются "bugfix").
Smoke: JSON валиден, vitest 9/9 passing — matcher работает с новым ключом.
Связано: Pravila §16.4 (conditional rule), project_a8_infosec, A8 install-
sync 21.05 push 3fc5501. Тулинг: tools/brain-retro-analyzer.mjs (читает),
tools/missed-activations.mjs (matcher), tools/observer-coverage-checker.mjs
(C5 surface в STATUS.md).
LEFTHOOK_EXCLUDE=adr-judge: то же, что c5d360f/640ee51/8e910d02 (ReDoS).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
После A8-эпика 21.05 (Tooling v2.20 +6 узлов #68-73 infosec-tooling) lefthook
job 'extract-node-dormancy' не запустился (стейджились data.js A8-эпика,
glob job — docs/Tooling_v8_3.md → расходимость стейджа vs реальные правки).
.node-dormancy.json остался с 67 узлами, A8 узлы #68-73 отсутствовали.
Эффект для missed-activations matcher (Pravila §16.4): A8-узлы не считались
«доступными» при оценке missed-activation — но и не считались dormant.
Просто отсутствовали в словаре → matcher НЕ мог рекомендовать их (даже если
бы classification-map содержал security-категорию).
Регенерация вручную через `node tools/extract-node-dormancy.mjs`:
- Все 6 A8-узлов добавлены: #68/#69/#70/#71/#72/#73 = false (active).
- ZAP (#68) и Ward (#70) — false после A8 install-sync 21.05
(Tooling §4.43/§4.45 dormant true→false уже было синкнуто).
- Всего 73 узла (было 67) — паритет с Tooling §0 канон.
Связано: project_a8_infosec.md, project_automation_map.md.
LEFTHOOK_EXCLUDE=adr-judge: то же, что c5d360f/640ee51 (ReDoS-обход).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Инцидент 22.05.2026 утро: liderra-queue.service крашился signal=9/KILL
каждые ~60с на RefreshSupplierSessionJob, после 5 крашей systemd блокировал
рестарт. OOM-killer в dmesg пуст, память здорова (peak ~200 МБ из 2 ГБ),
crm.bp-gr.ru отвечает.
Корень: дефолтный Laravel queue:work --timeout=60 убивал worker через
pcntl_alarm+posix_kill за 5 секунд до того, как PlaywrightBridge
успевал поднять Chromium (cold-start на 2GB VM ~65с — это уже знали и
увеличили TIMEOUT_SECONDS=180 в PlaywrightBridge.php HOTPATCH 21.05, но
таймаут самого воркера в systemd-unit упустили).
Поймано через bpftrace tracepoint:signal:signal_generate — sender pid ==
target pid, comm=php (PHP сам себе шлёт SIGKILL).
Fix: --timeout=300 в ExecStart (180s PlaywrightBridge + 120s запас).
На сервере применён через drop-in
/etc/systemd/system/liderra-queue.service.d/timeout.conf как safety-net.
Verified live: RefreshSupplierSessionJob отработал 1 мин. 5 сек. DONE
(до фикса — 1 мин. FAIL → KILL цикл).
Привожу документацию в порядок после фактического развёртывания серверного
слоя защиты на боевом тест-сервере liderra.ru (22.05.2026, на тестовой VM
Yandex Cloud, до закрытия Б-1).
Что сделано:
- docs/security/server-hardening-setup.md (новый) — setup-док серверного
слоя SEC-1..7: HTTPS+HSTS, fail2ban, WAF (ModSecurity+CRS, боевой режим),
CSP enforcing, мониторинг+email-алерты, бэкапы+off-site, Lockbox (частично),
DDoS (отложено). Зеркалит стиль docs/security/pgaudit-anonymizer-setup.md.
- docs/Открытые_вопросы_v8_3.md -> v1.85: SEC-1..7 статусы приведены к факту
(сделано / отложено / частично). Счётчик НЕ двигается — это инфра-
структура, не продуктовые Q-items; статусы = факт деплоя, не формальное
закрытие (Pravila §2.2 соблюдена). v1.84/v1.83 трейл не тронут.
- cspell-words.txt +10 терминов серверного слоя.
- tools/observer-chain-map.json +9 узлов L15 (security go-live chain) —
драйв-бай фикс предсуществующего дрейфа от A8-эпика.
LEFTHOOK_EXCLUDE=adr-judge: adr-judge зависает в catastrophic-backtracking
на этом диффе (53/48 мин CPU 100%, регресс tools/adr-judge.py на длинных
markdown-доках). Диф чисто документация, ADR-нарушений нет. Баг adr-judge —
отдельный follow-up. Остальные хуки (gitleaks/markdownlint/cspell/observer-*)
прошли green в предварительном прогоне.
Источник фактов: memory/project_server_hardening.md, ADR-014 §9.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Инцидент 22.05.2026: liderra.ru 500 Server Error. Корень — повреждённый
APP_KEY в .env (24 строки с CRLF + дубль ключа от key:generate). Каскад:
Laravel не парсил .env → fallback на default sqlite/database cache →
sqlite-файла нет → 500 на каждом HTTP-запросе; liderra-queue в
бесконечном activating-loop'е (Restart=always без лимитов).
Файлы (все LF через локальный .gitattributes — защита от CRLF-инцидента):
liderra-precheck.sh — pre-flight гейт (15 проверок: CRLF в .env, длина
APP_KEY, decrypt(encrypt) round-trip, PG/Redis ping, config-cache
свежее .env, pending migrations, HTTP smoke). exit 1 при любом провале.
liderra-healthcheck.sh + cron */2 — проверка портала каждые 2 минуты;
2 подряд провала (~4 мин downtime) → email DOWN; первый 200 после
DOWN → email RECOVERED.
liderra-queue.service — Restart=on-failure, StartLimitBurst=5/5min,
OnFailure=liderra-queue-alert.service. Очередь больше не крутится в
бесконечном крэше — после 5 крашей systemd останавливает + шлёт email.
liderra-queue-alert.service + liderra-systemd-alert.sh — отправка email
при окончательном fail системного юнита (status + journalctl tail).
msmtprc.template — шаблон для /etc/msmtprc (placeholder
__MAIL_PASSWORD__ подставляется из app/.env MAIL_PASSWORD).
Установлено на /var/www/liderra/app (тест-сервер YC):
/etc/msmtprc, /usr/local/bin/liderra-*.sh,
/etc/cron.d/liderra-healthcheck, /etc/systemd/system/liderra-queue*.service.
Тестовое письмо на kdv1@bk.ru доставлено (smtpstatus=250).
WAF (ModSecurity OWASP CRS 3.3.5) уже было правило 1900200 от A8 infosec
(разрешает PUT/PATCH/DELETE — добавлено в 06:00). Дополнительно:
/etc/nginx/modsec/liderra-exclusions.conf id:1900300 — для /api/*
поднят порог inbound_anomaly_score_threshold с 5 до 10 (чтобы edge-case
JSON-payloads не давали false-positive: PATCH/DELETE и так дают +5 в CRS).
Verification: 9/9 GREEN.
Smoke: liderra.ru → 200, PATCH/DELETE /api/* → 419 (Laravel CSRF, не 403 WAF).
Services: php-fpm/queue/nginx/postgres/redis — все active.
Pre-flight: 15/15 ✓ (был бы DOWN-сигнализатор сегодня за 5 секунд).
Laravel production.ERROR за последние 10 минут: 0.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>