diff --git a/rules/patterns/go-database/contract.md b/rules/patterns/go-database/contract.md index b1e0316..d7e5610 100644 --- a/rules/patterns/go-database/contract.md +++ b/rules/patterns/go-database/contract.md @@ -8,6 +8,8 @@ See `README.md` for examples, migration snippets, and Docker test commands. - Never execute SQL on the same transaction while iterating an open result cursor. Use a two-phase flow: read all rows, close the cursor, then execute writes. - This rule applies to `database/sql`, GORM transactions, and any repository call made while another cursor in the same transaction is still open. +- Violation symptoms: `[mysql] invalid connection`, `unexpected EOF`, `driver: bad connection` in Go logs; `Got an error reading communication packets` in MariaDB/MySQL error log. These are driver-level failures, not application errors — the root cause is always a nested SQL call on an open cursor. +- Recompute/rebuild/repair flows are the most common violation sites: audit them explicitly. - User-visible records use soft delete or archive flags. Do not hard-delete records with history or foreign-key references. - Archive operations must be reversible from the UI. - Use `gorm:"-"` only for fields that must be ignored entirely. Use `gorm:"-:migration"` for fields populated by queries but excluded from migrations. diff --git a/rules/patterns/hardware-ingest-json/contract.md b/rules/patterns/hardware-ingest-json/contract.md new file mode 100644 index 0000000..24cfab2 --- /dev/null +++ b/rules/patterns/hardware-ingest-json/contract.md @@ -0,0 +1,293 @@ +# Contract: Hardware Ingest JSON + +Version: 2.10 +Source: `bee/bible-local/docs/hardware-ingest-contract.md` (canonical upstream) + +Стандартный JSON-контракт для передачи данных об аппаратном обеспечении серверов в Reanimator. +Используется в `bee`, `logpile`, `core` и внешних интеграторах (Redfish-коллекторы, CMDB-экспортёры). + +> Актуальная версия: https://git.mchus.pro/reanimator/core/src/branch/main/bible-local/docs/hardware-ingest-contract.md + +## Принципы + +1. **Snapshot** — JSON описывает состояние сервера на момент сбора. Может включать историю изменений статуса. +2. **Идемпотентность** — повторная отправка идентичного payload не создаёт дублей (дедупликация по хешу). +3. **Частичность** — можно передавать только те секции, данные по которым доступны. Пустой массив и отсутствие секции эквивалентны. +4. **Строгая схема** — endpoint использует строгий JSON-декодер; неизвестные поля приводят к `400 Bad Request`. +5. **Event-driven** — импорт создаёт события в timeline (LOG_COLLECTED, INSTALLED, REMOVED, FIRMWARE_CHANGED и др.). +6. **Без синтеза** — сборщик передаёт только фактически собранные значения. Запрещено придумывать `serial_number`, `component_ref`, `message`, `message_id` или иные идентификаторы, если источник их не предоставил. + +## Endpoint + +``` +POST /ingest/hardware +Content-Type: application/json +``` + +Ответ `202 Accepted` с `job_id`. Результат: `GET /ingest/hardware/jobs/{job_id}`. + +## Структура верхнего уровня + +```json +{ + "filename": "redfish://10.10.10.103", + "source_type": "api", + "protocol": "redfish", + "target_host": "10.10.10.103", + "collected_at": "2026-02-10T15:30:00Z", + "hardware": { + "board": { ... }, + "firmware": [ ... ], + "cpus": [ ... ], + "memory": [ ... ], + "storage": [ ... ], + "pcie_devices": [ ... ], + "power_supplies": [ ... ], + "sensors": { ... }, + "event_logs": [ ... ], + "platform_config": { ... } + } +} +``` + +| Поле | Тип | Обязательно | Описание | +|------|-----|-------------|----------| +| `collected_at` | string RFC3339 | **да** | Время сбора данных | +| `hardware` | object | **да** | Аппаратный снапшот | +| `hardware.board.serial_number` | string | **да** | Серийный номер платы/сервера | +| `target_host` | string | нет | IP или hostname | +| `source_type` | string | нет | `api`, `logfile`, `manual` | +| `protocol` | string | нет | `redfish`, `ipmi`, `snmp`, `ssh` | +| `filename` | string | нет | Идентификатор источника | + +## Общие поля статуса компонентов + +Применяются ко всем компонентным секциям (`cpus`, `memory`, `storage`, `pcie_devices`, `power_supplies`). + +| Поле | Тип | Описание | +|------|-----|----------| +| `status` | string | `OK`, `Warning`, `Critical`, `Unknown`, `Empty` | +| `status_checked_at` | string RFC3339 | Время последней проверки | +| `status_changed_at` | string RFC3339 | Время последнего изменения | +| `status_history` | array | История переходов (`status`, `changed_at` обязательны) | +| `error_description` | string | Текст ошибки/диагностики | +| `manufactured_year_week` | string | Дата производства `YYYY-Www`, например `2024-W07` | + +Правила статуса: +- Не включайте записи `status_history` без `changed_at`. +- `status_history` сортировать по `changed_at` по возрастанию. +- Все даты — RFC3339, рекомендуется UTC (`Z`). + +| Статус | Поведение | +|--------|-----------| +| `OK` | Нормальная обработка | +| `Warning` | Создаётся событие `COMPONENT_WARNING` | +| `Critical` | Создаётся событие `COMPONENT_FAILED` + запись в `failure_events` | +| `Unknown` | Компонент считается рабочим, создаётся событие `COMPONENT_UNKNOWN` | +| `Empty` | Компонент не создаётся/не обновляется | + +## Секции hardware + +### board (обязательная) + +| Поле | Тип | Обязательно | Описание | +|------|-----|-------------|----------| +| `serial_number` | string | **да** | Серийный номер (ключ идентификации Asset) | +| `manufacturer` | string | нет | Производитель | +| `product_name` | string | нет | Модель | +| `part_number` | string | нет | Партномер | +| `uuid` | string | нет | UUID системы | + +Значения `"NULL"` в строковых полях трактуются как отсутствие данных. + +### firmware + +| Поле | Тип | Обязательно | +|------|-----|-------------| +| `device_name` | string | **да** | +| `version` | string | **да** | + +Записи с пустым `device_name` или `version` игнорируются. Изменение версии создаёт событие `FIRMWARE_CHANGED`. + +### cpus + +| Поле | Тип | Описание | +|------|-----|----------| +| `socket` | int | **обязательно**; используется для генерации serial | +| `model` | string | Модель процессора | +| `manufacturer` | string | | +| `cores` / `threads` | int | | +| `frequency_mhz` / `max_frequency_mhz` | int | | +| `temperature_c` | float | Telemetry, °C | +| `power_w` | float | Telemetry, Вт | +| `throttled` | bool | Thermal/power throttling | +| `correctable_error_count` / `uncorrectable_error_count` | int | | +| `life_remaining_pct` / `life_used_pct` | float | Health/wear, % | +| `serial_number` | string | Если доступен | +| `firmware` | string | Версия микрокода (Microcode level — передавать как есть) | +| `present` | bool | По умолчанию `true` | + +Генерация serial при отсутствии: `{board_serial}-CPU-{socket}` + +### memory + +| Поле | Тип | Описание | +|------|-----|----------| +| `slot` | string | | +| `present` | bool | По умолчанию `true` | +| `serial_number` | string | Обязателен для создания записи | +| `part_number` | string | Используется как модель | +| `manufacturer` | string | | +| `size_mb` | int | | +| `type` | string | `DDR3`, `DDR4`, `DDR5` | +| `max_speed_mhz` / `current_speed_mhz` | int | | +| `temperature_c` | float | Telemetry | +| `correctable_ecc_error_count` / `uncorrectable_ecc_error_count` | int | | +| `life_remaining_pct` / `life_used_pct` / `spare_blocks_remaining_pct` | float | | +| `performance_degraded` / `data_loss_detected` | bool | | + +Модуль без `serial_number`, с `present=false` или `status=Empty` игнорируется. + +### storage + +| Поле | Тип | Описание | +|------|-----|----------| +| `slot` | string | BDF (`0000:18:00.0`) для PCIe-подключённых | +| `serial_number` | string | Обязателен для создания записи | +| `model` / `manufacturer` | string | | +| `type` | string | `NVMe`, `SSD`, `HDD` | +| `interface` | string | `NVMe`, `SATA`, `SAS` | +| `size_gb` | int | | +| `logical_block_size_bytes` | int64 | Логический размер блока, например `512` или `4096` | +| `physical_block_size_bytes` | int64 | Физический размер блока | +| `metadata_bytes_per_block` | int64 | Metadata/protection bytes на блок, например `0` или `8` | +| `temperature_c` | float | Telemetry | +| `power_on_hours` / `power_cycles` / `unsafe_shutdowns` | int64 | | +| `media_errors` / `error_log_entries` | int64 | | +| `written_bytes` / `read_bytes` | int64 | | +| `life_used_pct` / `life_remaining_pct` / `available_spare_pct` | float | | +| `reallocated_sectors` / `current_pending_sectors` / `offline_uncorrectable` | int64 | | +| `firmware` | string | Изменение создаёт `FIRMWARE_CHANGED` | +| `present` | bool | По умолчанию `true` | + +Формат `512+8` не передаётся строкой — только через `logical_block_size_bytes` + `metadata_bytes_per_block`. +Диск без `serial_number` игнорируется. + +### pcie_devices + +| Поле | Тип | Описание | +|------|-----|----------| +| `slot` | string | Канонический адрес (BDF). `bdf` — deprecated alias, нормализуется при ingest | +| `vendor_id` / `device_id` | int | PCI ID (decimal) | +| `numa_node` | int | NUMA/CPU affinity | +| `device_class` | string | `MassStorageController`, `StorageController`, `NetworkController`, `EthernetController`, `FibreChannelController`, `VideoController`, `ProcessingAccelerator`, `DisplayController` (список открытый) | +| `manufacturer` / `model` / `serial_number` / `firmware` | string | | +| `link_width` / `max_link_width` | int | | +| `link_speed` / `max_link_speed` | string | `Gen3`, `Gen4`, `Gen5` | +| `mac_addresses` | string[] | MAC-адреса портов | +| `temperature_c` / `power_w` | float | Device-level telemetry | +| `life_remaining_pct` / `life_used_pct` | float | | +| `ecc_corrected_total` / `ecc_uncorrected_total` | int64 | | +| `hw_slowdown` | bool | | +| `battery_charge_pct` / `battery_health_pct` / `battery_temperature_c` / `battery_voltage_v` | float | | +| `battery_replace_required` | bool | | +| `sfp_temperature_c` / `sfp_tx_power_dbm` / `sfp_rx_power_dbm` / `sfp_voltage_v` / `sfp_bias_ma` | float | Optical telemetry | +| `present` | bool | По умолчанию `true` | + +Генерация serial при отсутствии или `"N/A"`: `{board_serial}-PCIE-{slot}` + +### power_supplies + +| Поле | Тип | Описание | +|------|-----|----------| +| `slot` | string | | +| `present` | bool | По умолчанию `true` | +| `serial_number` | string | Обязателен для создания записи | +| `part_number` / `model` / `vendor` | string | | +| `wattage_w` | int | | +| `firmware` | string | | +| `input_type` | string | Например `ACWideRange` | +| `input_voltage` / `input_power_w` / `output_power_w` / `temperature_c` | float | Telemetry | +| `life_remaining_pct` / `life_used_pct` | float | | + +PSU без `serial_number` игнорируется. + +### sensors (опционально) + +Данные хранятся как last-known-value на уровне Asset. Идентификатор: `(sensor_type, name)`. +Поле `location` передавать не нужно — игнорируется. Сенсоры без `name` игнорируются. + +```json +"sensors": { + "fans": [{ "name": "FAN1", "rpm": 4200, "status": "OK" }], + "power": [{ "name": "12V Rail", "voltage_v": 12.06, "status": "OK" }], + "temperatures": [{ "name": "CPU0 Temp", "celsius": 46.0, "threshold_warning_celsius": 80.0, "threshold_critical_celsius": 95.0, "status": "OK" }], + "other": [{ "name": "System Humidity", "value": 38.5, "unit": "%" }] +} +``` + +### event_logs (опционально) + +Нормализованные операционные логи. Не попадают в history timeline. Дедуплицируются по `(asset, source, fingerprint)`. + +| Поле | Тип | Обязательно | Описание | +|------|-----|-------------|----------| +| `source` | string | **да** | `host`, `bmc`, `redfish` | +| `message` | string | **да** | Нормализованный текст события | +| `event_time` | string RFC3339 | нет | | +| `severity` | string | нет | `OK`, `Info`, `Warning`, `Critical`, `Unknown` | +| `message_id` | string | нет | Код события источника | +| `component_ref` | string | нет | Ссылка на компонент/слот | +| `fingerprint` | string | нет | Внешний dedup-key; если нет — система вычисляет свой | +| `is_active` | bool | нет | Событие всё ещё активно | +| `raw_payload` | object | нет | Сырой vendor-specific payload | + +Запрещено синтезировать `message`, `message_id`, `component_ref`, serial/device identifiers. + +### platform_config (опционально) + +Произвольный объект с настройками платформы (BIOS/Redfish/IPMI) как есть из источника. +При каждом импорте хранится latest-snapshot per machine. + +## Обработка отсутствующих serial_number + +Интегратор не подставляет вымышленные значения, хеши или placeholder-идентификаторы. +Разрешены только server-side fallback-правила: + +| Тип | Поведение | +|-----|-----------| +| CPU | Генерируется: `{board_serial}-CPU-{socket}` | +| PCIe | Генерируется: `{board_serial}-PCIE-{slot}` | +| Memory | Компонент игнорируется | +| Storage | Компонент игнорируется | +| PSU | Компонент игнорируется | + +Если `serial_number` не уникален внутри payload для того же `model`: первое вхождение — оригинальный serial, дубли получают `NO_SN-XXXXXXXX`. + +## Минимальный валидный пример + +```json +{ + "collected_at": "2026-02-10T15:30:00Z", + "target_host": "192.168.1.100", + "hardware": { + "board": { + "serial_number": "SRV-001" + } + } +} +``` + +## Changelog + +| Версия | Дата | Изменения | +|--------|------|-----------| +| 2.10 | 2026-04-29 | `hardware.storage[]`: добавлены `logical_block_size_bytes`, `physical_block_size_bytes`, `metadata_bytes_per_block` | +| 2.9 | 2026-03-19 | Добавлена секция `hardware.platform_config` | +| 2.8 | 2026-03-15 | Поле `location` удалено из всех `sensors.*` | +| 2.7 | 2026-03-15 | Явно запрещён синтез данных в `event_logs` | +| 2.6 | 2026-03-15 | Добавлена секция `event_logs` | +| 2.5 | 2026-03-15 | Добавлено `manufactured_year_week` для всех компонентов | +| 2.4 | 2026-03-15 | Component telemetry: health/life поля для всех секций | +| 2.0 | 2026-02-01 | `status_history`, `status_changed_at`; async job response | +| 1.0 | 2026-01-01 | Начальная версия | diff --git a/rules/patterns/submodule-integration/contract.md b/rules/patterns/submodule-integration/contract.md new file mode 100644 index 0000000..840eae9 --- /dev/null +++ b/rules/patterns/submodule-integration/contract.md @@ -0,0 +1,41 @@ +# Contract: Git Submodule Integration + +Version: 1.0 + +Правила для проектов, использующих git submodules (shared libraries, viewers, bible, tooling). + +Применяется в: `bee` (internal/chart/, bible/), `chart`, `logpile/internal/chart/`, `PriceForge`. + +## Основное правило + +**Embedded submodules — read-only с точки зрения host-проекта.** + +## Запрещено + +- Реализовывать project-specific поведение путём редактирования кода submodule. +- Вносить в submodule изменения, специфичные для одного host-проекта. +- Держать в submodule локальные неотправленные коммиты как часть feature host-проекта. + +## Разрешено + +- Обновлять указатель submodule на upstream-коммит после merge там. +- Если нужна новая возможность в submodule — предложить и влить её в upstream как generic-изменение, затем подтянуть через обновление указателя. + +## Когда нужны новые данные + +Если host-проект нуждается в новых данных, которые должен отображать submodule-viewer: + +1. Производить, нормализовывать и сериализовывать новые данные в самом host-проекте. +2. Обновить JSON-контракт (например, `bible-local/docs/hardware-ingest-contract.md`), чтобы viewer мог читать их из стандартного snapshot. +3. Предложить поддержку нового поля в upstream viewer как generic-изменение. + +## Почему + +Конкретный провал: попытка добавить telemetry storage в `bee` через редактирование `internal/chart/` создала coupling shared viewer с одним host-проектом и риск скрытых регрессий в других проектах, использующих тот же `chart`. + +## Документирование интеграции + +В `bible-local/` host-проекта должен быть явный контракт: +- Какие данные ожидает submodule на входе. +- Как host-проект их производит (какой модуль/файл). +- Текущий upstream commit/tag submodule.