Netpulse_SasS/server/migrations/0062_device_restore.sql
byrsapty ed8fc831bf Дві сесії роботи: 0058–0068, розгортання однією командою, тести
Один коміт, а не десяток тематичних, свідомо: теми переплетені в
спільних файлах (store.go, docker-compose.yml, deploy/README.md), і
розділити їх можна було б лише індексуванням шматків. Коміти, які не
збираються, гірші за один великий — тим паче що це рівно той стан, який
перевірявся разом.

ЩО ПРАЦЮЄ НА СТЕНДІ Й ПЕРЕВІРЕНО ТАМ

  0058  подієві алерти: syslog, ncm, compliance спрацьовують у мить
        події; правило з нереалізованим джерелом більше не зберігається
        мовчки
  0059  snmp.walk і прототипи шаблонів — таблиці з динамічним індексом
        описуються шаблоном, а не Go
  0060  відкат конфігу: план як різниця, маскування паролів із підписом
        плану, обов'язковий контрольний збір, verifying при обриві
  0061  кнопки Telegram: довге опитування, авторизація не з callback_data
  0062  аудит і архів хостів; тест на AST, що падає на ключі без назви
  0063  RLS: три ролі, окремий пул для фонових тактів
  0064  строки зберігання даних і сторінка сховища
  0065  приймач SNMP-трапів; перевірено справжніми пакетами по дроту,
        переклад v1→v2 за RFC 3584 дає правильний OID
  0066  ескалації сповіщень
  0067  алерт про вичерпання диска
  0068  поля заливки конфігу переїхали в каталог профілів

Плюс: 137 тестів вебу з нуля (їх не було взагалі), одинадцять справжніх
вад, знайдених ними й виправлених, і виправлення двох інтеграційних
тестів grpcapi, які мовчки пропускались півтора року.

ЩО ЩЕ НЕ ЗАПУСКАЛОСЬ

  netpulse            установник: одна команда замість 18 змінних і
                      593 рядків інструкції
  RLS з першого запуску  нова інсталяція під політиками одразу;
                      RLS-EXISTING-INSTALL.md лишається тільки для
                      старих інсталяцій
  .forgejo + CI       раннер не зареєстрований

Ці три перевірені компіляцією й міркуванням, але не виконанням.

ГОЛОВНИЙ ВИСНОВОК ДВОХ СЕСІЙ

Зелена перевірка доводить рівно те, що вона перевіряє. Тест ізоляції RLS
був правильний і зелений — і пропустив зламаний вхід, бо перевіряв «чи
не видно чужого», коли зламалось «чи видно своє». Інтеграційні тести
grpcapi були зелені, бо не виконувались. Схема, довідник і протокол
описували те, чого в коді не існувало, і виглядало це як готове.

Тому в кожному завданні цих сесій стояла вимога назвати НЕПОКРИТЕ, а
чотири задачі закінчились не можливістю, а відмовою: правило з
нереалізованим джерелом не зберігається, профіль без команд заливки
каже про це замість мовчазної кнопки, міграція RLS валить сама себе на
таблиці без політики, тест словника аудиту падає на ключі без назви.

Подробиці — HISTORY.md, розділи за 26 і 27 серпня.
2026-08-27 17:32:49 +03:00

95 lines
7.1 KiB
SQL
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- =====================================================================
-- NetPulse :: 0062_device_restore.sql
-- Повернення хоста з архіву: дві колонки, без яких «відновити» означало
-- б «повернути рядок, але не хост».
--
-- Що було. Видалення хоста має два режими: архівне (deleted_at = now())
-- і повне (0057). Архівне задумувалось як оборотне — «прибрали з
-- переліку, зібране лишилось», — але оборотним воно ніколи не було:
-- переліку прибраних хостів у продукті немає, дії «відновити» теж, і
-- рядок із deleted_at недосяжний нізвідки, крім SQL. Тобто кнопка
-- «прибрати з переліку» працювала як пастка: хост не видалений, місце
-- займає, ряди метрик лишає, а ні повернути його, ні добити не можна.
--
-- 0057 закрив половину проблеми — дав спосіб добити. Ця міграція
-- закриває другу: спосіб повернути.
--
-- ЧОМУ ЦЬОГО НЕ ЗРОБИТИ ОДНИМ `deleted_at = NULL`
--
-- Архівування вимикає не лише сам хост: разом із ним воно вимикає всі
-- його увімкнені перевірки (див. DeleteDevice і BulkDeleteDevices —
-- увімкнений чек мертвого хоста назавжди лишався б «таким, що пропустив
-- свій такт» на сторінці «Черги»). Після зняття deleted_at хост
-- повертається в переліки — і мовчки не опитується, бо чеки лишились
-- вимкненими, а enabled лишився false. Людина бачить хост на місці,
-- графіки порожні, і причини цього не видно ніде.
--
-- Вгадати тут нічого не можна: у базі не записано, який чек вимкнула
-- людина, а який — архівування. Тому архівування тепер САМЕ це й
-- записує, а відновлення вмикає рівно те, що вимикало воно, і забуває
-- запис. Дві колонки — рівно на це, і жодна з них не є «станом хоста»:
-- обидві живуть лише між архівуванням і поверненням.
--
-- Міграція нічого не змінює в поведінці сама. Хости, заархівовані ДО
-- неї, мають archived_enabled = NULL — такий повертається вимкненим, і
-- інтерфейс про це прямо каже. Це чесніше, ніж увімкнути навмання те,
-- що людина могла вимкнути свідомо ще до видалення.
-- =====================================================================
-- ---------------------------------------------------------------------
-- Яким хост був до архівування
-- ---------------------------------------------------------------------
ALTER TABLE inv.devices
-- Значення enabled на момент архівування.
--
-- NULL означає «хост не в архіві» або «заархівований до 0062»: у
-- першому випадку колонка не має сенсу, у другому — відповіді немає.
-- Обидва читаються однаково — повертати вимкненим, — і саме тому це
-- одна колонка, а не колонка плюс прапорець.
--
-- Окремої колонки під «була прив'язка до зонда» тут немає навмисно:
-- self_agent_id архівування не чіпає, і після повернення вона на
-- місці. Знімати її доводиться лише тоді, коли за час в архіві ту
-- саму машину оголосив своєю інший ЖИВИЙ хост, — і це вирішується в
-- момент відновлення, а не зберігається наперед.
ADD COLUMN archived_enabled boolean;
COMMENT ON COLUMN inv.devices.archived_enabled IS
'Яким був enabled на момент архівування; NULL — хост не в архіві або заархівований до 0062';
-- Перелік архіву.
--
-- Усі наявні індекси на inv.devices — часткові `WHERE deleted_at IS
-- NULL` (devices_tenant_name_uniq, devices_tenant_status_idx): вони
-- заведені під питання «що зараз працює» і на питання «що прибрано»
-- не відповідають узагалі. Без цього індексу сторінка архіву читала б
-- увесь інвентар тенанта заради десятка рядків — і тим більше, чим
-- більший парк, тобто рівно навпаки до того, як воно має поводитись.
CREATE INDEX devices_archived_idx
ON inv.devices (tenant_id, deleted_at DESC)
WHERE deleted_at IS NOT NULL;
-- ---------------------------------------------------------------------
-- Який чек вимкнула людина, а який — архівування
-- ---------------------------------------------------------------------
ALTER TABLE core.checks
-- Чек вимкнено разом з архівуванням хоста, а не рукою людини.
--
-- Прапорець живе рівно до відновлення й гаситься ним же. Через це
-- він не «історія», яку хтось колись читатиме, а те єдине, чого
-- бракує, щоб повернення хоста не вимагало від людини згадати
-- напам'ять, які з двадцяти перевірок вона тримала вимкненими
-- свідомо.
--
-- DEFAULT false, а не NULL: «не архівуванням» — це стан кожного
-- рядка, який існує, а не відсутність відповіді.
ADD COLUMN archived_off boolean NOT NULL DEFAULT false;
COMMENT ON COLUMN core.checks.archived_off IS
'Чек вимкнено архівуванням хоста; відновлення вмикає назад саме такі';
-- Окремого індексу під archived_off немає й не треба: єдиний запит, що
-- його читає, уже звужений до одного хоста (checks_device_idx), а
-- прапорець там лише відсіює зайве.