From 3699ece6e3d0a863f1ca5737c4db3d3bcaca0a11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Fri, 7 Aug 2026 15:54:24 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B7=D0=B0=D1=89=D0=B8=D1=82=D0=B0:=20=D0=B2?= =?UTF-8?q?=20=D0=B3=D0=BB=D0=B0=D0=B2=D0=BD=D1=83=D1=8E=20=D0=B2=D0=B5?= =?UTF-8?q?=D1=82=D0=BA=D1=83=20=D0=B4=D0=BE=D1=81=D1=82=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D0=BF=D1=8F=D1=82=D1=8C=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=87=D0=B8=D0=BD=D0=BE=D0=BA=20=D1=81=D1=82=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D0=B6=D0=B0=20=D0=B1=D0=BE=D0=B5=D0=B2=D0=BE=D0=B9=20=D0=B1?= =?UTF-8?q?=D0=B0=D0=B7=D1=8B=20=E2=80=94=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5?= =?UTF-8?q?=20=D1=80=D0=B0=D0=B1=D0=BE=D1=87=D0=B8=D0=B5=20=D0=BF=D0=B0?= =?UTF-8?q?=D0=BF=D0=BA=D0=B8=20=D0=B1=D0=BE=D0=BB=D1=8C=D1=88=D0=B5=20?= =?UTF-8?q?=D0=BD=D0=B5=20=D1=80=D0=BE=D0=B6=D0=B4=D0=B0=D1=8E=D1=82=D1=81?= =?UTF-8?q?=D1=8F=20=D0=B4=D1=8B=D1=80=D1=8F=D0=B2=D1=8B=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit По прямому слову владельца 07.08.2026 («выкати в мэйн, только всё проверь, чтоб никого не потёр»). 🔴 ЗАЧЕМ. Замерено смено́й 33: тело сторожа `172931cf` лежало в 13 рабочих папках из 23, включая ЭТУ ветку. В нём не было НИ ОДНОЙ починки от 05.08 и 07.08. Хук подключён в настройках ОТНОСИТЕЛЬНЫМ путём, значит исполняется тело, лежащее рядом с папкой сессии, — и всякая НОВАЯ рабочая папка, отведённая от главной ветки, рождалась с дырявым сторожем. 🔴 ЧТО ПРОПУСКАЛО СТАРОЕ ТЕЛО (замерено живым вызовом, одним прибором по обоим файлам): · php artisan db:wipe --force --database=liderra · php artisan migrate:fresh --force · dropdb "$PROD_DB" (цель спрятана в переменной) · psql "$PROD_URL" -c "DROP SCHEMA public CASCADE" Четыре красных из семи образцов. Контрольный образец (снос кластера) старое тело останавливало — значит замер работал и не красил всё подряд. 🟢 ЧТО СТАЛО. Тот же замер по этому файлу после доставки: 0 красных из 7. Обе мирные команды (обычная миграция `php artisan migrate`, снос тестовой базы `dropdb liderra_testing`) по-прежнему проходят. ЧТО ИМЕННО ДОБАВЛЕНО В СТОРОЖА: 4. штатная программа dropdb (стоит на этой машине) 5. SQL DROP SCHEMA … CASCADE — базу оставляет, содержимое выносит 6. снос средствами приложения: db:wipe / migrate:fresh / migrate:refresh + короткие имена облака (yc mdb pg / yc mdb postgresql) + цель, спрятанная в переменной, обязана быть названа словами 🔴 Цена строгости, названная честно: снос ТЕСТОВОЙ базы по связи из переменной теперь тоже останавливается — сторож не может знать, что под переменной тестовая. Обе двери владельца (маркер PROD-DESTROY-OK и ALLOW_PROD_DB_DESTROY=1) работают. Указатель `prod-db-pointer.mjs` доставлен тем же коммитом: он рассказывает сессии, что ловит сторож, и в старом виде ВРАЛ — называл 5 приёмов из 6 и прямо обещал, что снос со спрятанной целью пройдёт. 🔴 ЧТО ПРОВЕРЕНО, ЧТОБЫ НИКОГО НЕ ЗАТЕРЕТЬ: 1. различия обоих файлов просмотрены построчно: из прежних тел не пропало НИ ОДНОЙ мысли — все прежние строки либо сохранены, либо заменены своими же расширенными видами; 2. в этой папке идёт ЧУЖАЯ незакоммиченная работа по СМС (правка MtsSmsProvider.php, новые SmsConnectionBreaker.php и MtsSmsProviderConnectionTest.php, ПРОМТ-следующей-смене.md). Она НЕ тронута и в этот коммит НЕ входит: добавлены строго два файла поимённо; 3. 🔴 на сервер GitHub НИЧЕГО не отправлено. Замерено: тамошний main разошёлся с местным очень сильно — местный впереди на 3572 коммита, серверный впереди на 1525, последний серверный коммит от 03.06.2026. Любая отправка туда либо будет отвергнута, либо снесёт полторы тысячи чужих коммитов. Это отдельное решение владельца, а не часть этой работы. Co-Authored-By: Claude Opus 5 --- .claude/hooks/prod-db-guard.mjs | 105 ++++++++++++++++++++++++++---- .claude/hooks/prod-db-pointer.mjs | 30 +++++++-- 2 files changed, 119 insertions(+), 16 deletions(-) diff --git a/.claude/hooks/prod-db-guard.mjs b/.claude/hooks/prod-db-guard.mjs index 172931cf..85b05354 100644 --- a/.claude/hooks/prod-db-guard.mjs +++ b/.claude/hooks/prod-db-guard.mjs @@ -10,6 +10,34 @@ // Боевая база = Managed PG кластер c9q2cvtjpq3hgq6l0r96 (rw-endpoint *.mdb.yandexcloud.net). // Тест-база = отдельная liderra_testing (её сносить можно). // +// Что считается сносом (проверено живым вызовом, сторож проверок — tools/prod-db-guard.test.mjs): +// 1. yc managed-postgresql cluster delete — и короткие имена yc mdb pg / yc mdb postgresql +// 2. yc managed-postgresql database delete — и те же короткие имена +// 3. SQL DROP DATABASE +// 4. штатная программа dropdb (стоит на этой машине: C:\Program Files\PostgreSQL\16\bin\dropdb.exe) +// 5. SQL DROP SCHEMA … CASCADE — базу оставляет, всё содержимое выносит +// 6. снос средствами самого приложения: artisan db:wipe / migrate:fresh / migrate:refresh +// Пункты 4-5 и короткие имена добавлены 05.08.2026: до этого они проходили насквозь. +// Пункт 6 добавлен 07.08.2026 (смена 32, по слову владельца) — см. ниже. +// +// 🟢 ДВЕ ДЫРЫ ИЗ ЗАМЕРА М-400 ЗАКРЫТЫ 07.08.2026. Прежде здесь стояло примечание +// «чего сторож не видит», и оно было верным: обе дыры замерены живым вызовом смены 23. +// +// 🔴 ПЕРВАЯ БЫЛА ДВОЙНОЙ. `php artisan db:wipe --force` не числился разрушительной командой +// ВООБЩЕ — хотя выносит из базы все таблицы разом. Значит даже с прямо названной боевой +// базой этот способ сноса сторожу был неизвестен. Туда же `migrate:fresh` и `migrate:refresh`: +// оба сперва сносят, и только потом наливают заново. +// +// 🔴 ВТОРАЯ: снос виден словами, а цель спрятана — ни имени базы, ни кластера, ни хоста +// в тексте нет, всё в переменной (`psql "$PROD_URL" -c "DROP SCHEMA public CASCADE"`). +// 🔑 Отличить по тексту боевую связь от тестовой НЕЛЬЗЯ — значит решать надо не «какая это +// база», а «названа ли она вообще». Разрушительная команда со спрятанной целью теперь +// останавливается: назови цель словами либо поставь маркер владельца. +// +// 🔴 ЦЕНА ЭТОЙ СТРОГОСТИ, НАЗВАННАЯ ЧЕСТНО: снос ТЕСТОВОЙ базы по связи из переменной +// тоже остановится — сторож не может знать, что под переменной тестовая. Это плата за то, +// что боевую нельзя снести молча. Обе двери владельца работают и здесь. +// // Override владельца: маркер `PROD-DESTROY-OK` в самой команде ИЛИ env ALLOW_PROD_DB_DESTROY=1. import { readFileSync } from 'node:fs'; @@ -39,21 +67,74 @@ const targetsProd = /\bc-[a-z0-9]+\.(rw|ro)\.mdb\.yandexcloud\.net/i.test(cmd) || /\bliderra\b(?!_)/i.test(cmd); +// Имя команды PostgreSQL у `yc` пишется ТРЕМЯ способами: полным именем и двумя +// короткими алиасами. Замерено живым вызовом 05.08.2026: сторож знал только полное, +// и `yc mdb pg cluster delete --id c9q2cvtjpq3hgq6l0r96` проходил насквозь. +const YC_PG = String.raw`(?:managed-postgresql|mdb\s+(?:postgresql|postgres|pg))`; + // Деструктив над управляемой БД/кластером. -const clusterDelete = /managed-postgresql\s+cluster\s+delete/i.test(cmd); // снос кластера — всегда катастрофа -const databaseDelete = /managed-postgresql\s+database\s+delete/i.test(cmd); // снос управляемой БД -const dropDatabase = /\bdrop\s+database\b/i.test(cmd); // SQL DROP DATABASE +const clusterDelete = new RegExp(`${YC_PG}\\s+cluster\\s+delete`, 'i').test(cmd); // снос кластера — всегда катастрофа +const databaseDelete = new RegExp(`${YC_PG}\\s+database\\s+delete`, 'i').test(cmd); // снос управляемой БД +const dropDatabase = /\bdrop\s+database\b/i.test(cmd); // SQL DROP DATABASE -const destructive = clusterDelete || databaseDelete || dropDatabase; +// Штатная программа PostgreSQL `dropdb` — сносит базу так же насмерть, как SQL DROP DATABASE, +// и она СТОИТ на этой машине (C:\Program Files\PostgreSQL\16\bin\dropdb.exe). Ловим имя +// программы в любом виде: голым словом, с расширением, полным путём (слэши любые, кавычки). +// 🪤 Границы руками, а не `\b`: `\b` не отличает `dropdb` от `--dropdb`, а путь `bin\dropdb.exe` +// должен попадаться. Сноса ТЕСТОВОЙ базы это не касается — ниже решает targetsProd. +const dropdbTool = /(?