Files
core/bible-local/docs/hardware-ingest-contract.md
2026-03-15 22:24:41 +03:00

36 KiB
Raw Blame History

title, version, updated, maintainer, audience, language
title version updated maintainer audience language
Hardware Ingest JSON Contract 2.7 2026-03-15 Reanimator Core external-integrators, ai-agents ru

Интеграция с Reanimator: контракт JSON-импорта аппаратного обеспечения

Версия: 2.7 · Дата: 2026-03-15

Документ описывает формат JSON для передачи данных об аппаратном обеспечении серверов в систему Reanimator (управление жизненным циклом аппаратного обеспечения). Предназначен для разработчиков смежных систем (Redfish-коллекторов, агентов мониторинга, CMDB-экспортёров) и может быть включён в документацию интегрируемых проектов.

Актуальная версия документа: https://git.mchus.pro/reanimator/core/src/branch/main/bible-local/docs/hardware-ingest-contract.md


Changelog

Версия Дата Изменения
2.7 2026-03-15 Явно запрещён синтез данных в event_logs; интеграторы не должны придумывать серийные номера компонентов, если источник их не отдал
2.6 2026-03-15 Добавлена необязательная секция event_logs для dedup/upsert логов host / bmc / redfish вне history timeline
2.5 2026-03-15 Добавлено общее необязательное поле manufactured_year_week для компонентных секций (YYYY-Www)
2.4 2026-03-15 Добавлена первая волна component telemetry: health/life поля для cpus, memory, storage, pcie_devices, power_supplies
2.3 2026-03-15 Добавлены component telemetry поля: pcie_devices.temperature_c, pcie_devices.power_w, power_supplies.temperature_c
2.2 2026-03-15 Добавлено поле numa_node у pcie_devices для topology/affinity
2.1 2026-03-15 Добавлена секция sensors (fans, power, temperatures, other); поле mac_addresses у pcie_devices; расширен список значений device_class
2.0 2026-02-01 История статусов (status_history, status_changed_at); поля telemetry у PSU; async job response
1.0 2026-01-01 Начальная версия контракта

Принципы

  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):

{
  "status": "accepted",
  "job_id": "job_01J..."
}

Импорт выполняется асинхронно. Результат доступен по:

GET /ingest/hardware/jobs/{job_id}

Ответ при успехе задачи:

{
  "status": "success",
  "bundle_id": "lb_01J...",
  "asset_id": "mach_01J...",
  "collected_at": "2026-02-10T15:30:00Z",
  "duplicate": false,
  "summary": {
    "parts_observed": 15,
    "parts_created": 2,
    "parts_updated": 13,
    "installations_created": 2,
    "installations_closed": 1,
    "timeline_events_created": 9,
    "failure_events_created": 1
  }
}

Ответ при дубликате:

{
  "status": "success",
  "duplicate": true,
  "message": "LogBundle with this content hash already exists"
}

Ответ при ошибке (400 Bad Request):

{
  "status": "error",
  "error": "validation_failed",
  "details": {
    "field": "hardware.board.serial_number",
    "message": "serial_number is required"
  }
}

Частые причины 400:

  • Неверный формат collected_at (требуется RFC3339).
  • Пустой hardware.board.serial_number.
  • Наличие неизвестного 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":     [ ... ]
  }
}

Поля верхнего уровня

Поле Тип Обязательно Описание
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 История переходов статусов (см. ниже)
error_description string Текст ошибки/диагностики
manufactured_year_week string Дата производства в формате YYYY-Www, например 2024-W07

Объект status_history[]:

Поле Тип Обязательно Описание
status string да Статус в этот момент
changed_at string RFC3339 да Время перехода (без этого поля запись игнорируется)
details string нет Пояснение к переходу

Правила приоритета времени события:

  1. status_changed_at
  2. Последняя запись status_history с совпадающим статусом
  3. Последняя парсируемая запись status_history
  4. status_checked_at

Правила передачи статусов:

  • Передавайте status как текущее состояние компонента в snapshot.
  • Если источник хранит историю — передавайте status_history отсортированным по changed_at по возрастанию.
  • Не включайте записи status_history без changed_at.
  • Все даты — RFC3339, рекомендуется UTC (Z).
  • manufactured_year_week используйте, когда источник знает только год и неделю производства, без точной календарной даты.

Секции hardware

board

Основная информация о сервере. Обязательная секция.

Поле Тип Обязательно Описание
serial_number string да Серийный номер (ключ идентификации Asset)
manufacturer string нет Производитель
product_name string нет Модель
part_number string нет Партномер
uuid string нет UUID системы

Значения "NULL" в строковых полях трактуются как отсутствие данных.

"board": {
  "manufacturer": "Supermicro",
  "product_name": "X12DPG-QT6",
  "serial_number": "21D634101",
  "part_number": "X12DPG-QT6-REV1.01",
  "uuid": "d7ef2fe5-2fd0-11f0-910a-346f11040868"
}

firmware

Версии прошивок системных компонентов (BIOS, BMC, CPLD и др.).

Поле Тип Обязательно Описание
device_name string да Название устройства (BIOS, BMC, CPLD, …)
version string да Версия прошивки

Записи с пустым device_name или version игнорируются. Изменение версии создаёт событие FIRMWARE_CHANGED для Asset.

"firmware": [
  { "device_name": "BIOS", "version": "06.08.05" },
  { "device_name": "BMC",  "version": "5.17.00"  },
  { "device_name": "CPLD", "version": "01.02.03" }
]

cpus

Поле Тип Обязательно Описание
socket int да Номер сокета (используется для генерации serial)
model string нет Модель процессора
manufacturer string нет Производитель
cores int нет Количество ядер
threads int нет Количество потоков
frequency_mhz int нет Текущая частота
max_frequency_mhz int нет Максимальная частота
temperature_c float нет Температура CPU, °C (telemetry)
power_w float нет Текущая мощность CPU, Вт (telemetry)
throttled bool нет Зафиксирован thermal/power throttling
correctable_error_count int нет Количество корректируемых ошибок CPU
uncorrectable_error_count int нет Количество некорректируемых ошибок CPU
life_remaining_pct float нет Остаточный ресурс / health, %
life_used_pct float нет Использованный ресурс / wear, %
serial_number string нет Серийный номер (если доступен)
firmware string нет Версия микрокода; если логгер отдает Microcode level, передавайте его сюда как есть
present bool нет Наличие (по умолчанию true)
+ общие поля статуса см. раздел выше

Генерация serial_number при отсутствии: {board_serial}-CPU-{socket}

Если источник использует поле/лейбл Microcode level, его значение передавайте в cpus[].firmware без дополнительного преобразования.

"cpus": [
  {
    "socket": 0,
    "model": "INTEL(R) XEON(R) GOLD 6530",
    "cores": 32,
    "threads": 64,
    "frequency_mhz": 2100,
    "max_frequency_mhz": 4000,
    "temperature_c": 61.5,
    "power_w": 182.0,
    "throttled": false,
    "manufacturer": "Intel",
    "status": "OK",
    "status_checked_at": "2026-02-10T15:28:00Z"
  }
]

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 int нет Максимальная частота
current_speed_mhz int нет Текущая частота
temperature_c float нет Температура DIMM/модуля, °C (telemetry)
correctable_ecc_error_count int нет Количество корректируемых ECC-ошибок
uncorrectable_ecc_error_count int нет Количество некорректируемых ECC-ошибок
life_remaining_pct float нет Остаточный ресурс / health, %
life_used_pct float нет Использованный ресурс / wear, %
spare_blocks_remaining_pct float нет Остаток spare blocks, %
performance_degraded bool нет Зафиксирована деградация производительности
data_loss_detected bool нет Источник сигнализирует риск/факт потери данных
+ общие поля статуса см. раздел выше

Модуль без serial_number игнорируется. Модуль с present=false или status=Empty игнорируется.

"memory": [
  {
    "slot": "CPU0_C0D0",
    "present": true,
    "size_mb": 32768,
    "type": "DDR5",
    "max_speed_mhz": 4800,
    "current_speed_mhz": 4800,
    "temperature_c": 43.0,
    "correctable_ecc_error_count": 0,
    "manufacturer": "Hynix",
    "serial_number": "80AD032419E17CEEC1",
    "part_number": "HMCG88AGBRA191N",
    "status": "OK"
  }
]

storage

Поле Тип Обязательно Описание
slot string нет Канонический адрес установки PCIe-устройства; передавайте BDF (0000:18:00.0)
serial_number string нет Серийный номер
model string нет Модель
manufacturer string нет Производитель
type string нет Тип: NVMe, SSD, HDD
interface string нет Интерфейс: NVMe, SATA, SAS
size_gb int нет Размер в ГБ
temperature_c float нет Температура накопителя, °C (telemetry)
power_on_hours int64 нет Время работы, часы
power_cycles int64 нет Количество циклов питания
unsafe_shutdowns int64 нет Нештатные выключения
media_errors int64 нет Ошибки носителя / media errors
error_log_entries int64 нет Количество записей в error log
written_bytes int64 нет Всего записано байт
read_bytes int64 нет Всего прочитано байт
life_used_pct float нет Использованный ресурс / wear, %
life_remaining_pct float нет Остаточный ресурс / health, %
available_spare_pct float нет Доступный spare, %
reallocated_sectors int64 нет Переназначенные сектора
current_pending_sectors int64 нет Сектора в ожидании ремапа
offline_uncorrectable int64 нет Некорректируемые ошибки offline scan
firmware string нет Версия прошивки
present bool нет Наличие (по умолчанию true)
+ общие поля статуса см. раздел выше

Диск без serial_number игнорируется. Изменение firmware создаёт событие FIRMWARE_CHANGED.

"storage": [
  {
    "slot": "OB01",
    "type": "NVMe",
    "model": "INTEL SSDPF2KX076T1",
    "size_gb": 7680,
    "temperature_c": 38.5,
    "power_on_hours": 12450,
    "unsafe_shutdowns": 3,
    "written_bytes": 9876543210,
    "life_remaining_pct": 91.0,
    "serial_number": "BTAX41900GF87P6DGN",
    "manufacturer": "Intel",
    "firmware": "9CV10510",
    "interface": "NVMe",
    "present": true,
    "status": "OK"
  }
]

pcie_devices

Поле Тип Обязательно Описание
slot string нет Идентификатор слота
vendor_id int нет PCI Vendor ID (decimal)
device_id int нет PCI Device ID (decimal)
numa_node int нет NUMA node / CPU affinity устройства
temperature_c float нет Температура устройства, °C (telemetry)
power_w float нет Текущее энергопотребление устройства, Вт (telemetry)
life_remaining_pct float нет Остаточный ресурс / health, %
life_used_pct float нет Использованный ресурс / wear, %
ecc_corrected_total int64 нет Всего корректируемых ECC-ошибок
ecc_uncorrected_total int64 нет Всего некорректируемых ECC-ошибок
hw_slowdown bool нет Устройство вошло в hardware slowdown / protective mode
battery_charge_pct float нет Заряд батареи / supercap, %
battery_health_pct float нет Состояние батареи / supercap, %
battery_temperature_c float нет Температура батареи / supercap, °C
battery_voltage_v float нет Напряжение батареи / supercap, В
battery_replace_required bool нет Требуется замена батареи / supercap
sfp_temperature_c float нет Температура SFP/optic, °C
sfp_tx_power_dbm float нет TX optical power, dBm
sfp_rx_power_dbm float нет RX optical power, dBm
sfp_voltage_v float нет Напряжение SFP, В
sfp_bias_ma float нет Bias current SFP, мА
bdf string нет Deprecated alias для slot; при наличии ingest нормализует его в slot
device_class string нет Класс устройства (см. список ниже)
manufacturer string нет Производитель
model string нет Модель
serial_number string нет Серийный номер
firmware string нет Версия прошивки
link_width int нет Текущая ширина линка
link_speed string нет Текущая скорость: Gen3, Gen4, Gen5
max_link_width int нет Максимальная ширина линка
max_link_speed string нет Максимальная скорость
mac_addresses string[] нет MAC-адреса портов (для сетевых устройств)
present bool нет Наличие (по умолчанию true)
+ общие поля статуса см. раздел выше

numa_node передавайте для NIC / InfiniBand / RAID / GPU, когда источник знает CPU/NUMA affinity. Поле сохраняется в snapshot-атрибутах PCIe-компонента и дублируется в telemetry для topology use cases. Поля temperature_c и power_w используйте для device-level telemetry GPU / accelerator / smart PCIe devices. Они не влияют на идентификацию компонента.

Генерация serial_number при отсутствии или "N/A": {board_serial}-PCIE-{slot}, где slot для PCIe равен BDF.

slot — единственный канонический адрес компонента. Для PCIe в slot передавайте BDF. Поле bdf сохраняется только как переходный alias на входе и не должно использоваться как отдельная координата рядом со slot.

Значения device_class:

Значение Назначение
MassStorageController RAID-контроллеры
StorageController HBA, SAS-контроллеры
NetworkController Сетевые адаптеры (InfiniBand, общий)
EthernetController Ethernet NIC
FibreChannelController Fibre Channel HBA
VideoController GPU, видеокарты
ProcessingAccelerator Вычислительные ускорители (AI/ML)
DisplayController Контроллеры дисплея (BMC VGA)

Список открытый: допускаются произвольные строки для нестандартных классов.

"pcie_devices": [
  {
    "slot": "0000:3b:00.0",
    "vendor_id": 5555,
    "device_id": 4401,
    "numa_node": 0,
    "temperature_c": 48.5,
    "power_w": 18.2,
    "sfp_temperature_c": 36.2,
    "sfp_tx_power_dbm": -1.8,
    "sfp_rx_power_dbm": -2.1,
    "device_class": "EthernetController",
    "manufacturer": "Intel",
    "model": "X710 10GbE",
    "serial_number": "K65472-003",
    "firmware": "9.20 0x8000d4ae",
    "mac_addresses": ["3c:fd:fe:aa:bb:cc", "3c:fd:fe:aa:bb:cd"],
    "status": "OK"
  }
]

power_supplies

Поле Тип Обязательно Описание
slot string нет Идентификатор слота
present bool нет Наличие (по умолчанию true)
serial_number string нет Серийный номер
part_number string нет Партномер
model string нет Модель
vendor string нет Производитель
wattage_w int нет Мощность в ваттах
firmware string нет Версия прошивки
input_type string нет Тип входа (например ACWideRange)
input_voltage float нет Входное напряжение, В (telemetry)
input_power_w float нет Входная мощность, Вт (telemetry)
output_power_w float нет Выходная мощность, Вт (telemetry)
temperature_c float нет Температура PSU, °C (telemetry)
life_remaining_pct float нет Остаточный ресурс / health, %
life_used_pct float нет Использованный ресурс / wear, %
+ общие поля статуса см. раздел выше

Поля telemetry (input_voltage, input_power_w, output_power_w, temperature_c, life_remaining_pct, life_used_pct) сохраняются в атрибутах компонента и не влияют на его идентификацию.

PSU без serial_number игнорируется.

"power_supplies": [
  {
    "slot": "0",
    "present": true,
    "model": "GW-CRPS3000LW",
    "vendor": "Great Wall",
    "wattage_w": 3000,
    "serial_number": "2P06C102610",
    "firmware": "00.03.05",
    "status": "OK",
    "input_type": "ACWideRange",
    "input_power_w": 137,
    "output_power_w": 104,
    "input_voltage": 215.25,
    "temperature_c": 39.5,
    "life_remaining_pct": 97.0
  }
]

sensors

Показания сенсоров сервера. Секция опциональная, не привязана к компонентам. Данные хранятся как последнее известное значение (last-known-value) на уровне Asset.

"sensors": {
  "fans":         [ ... ],
  "power":        [ ... ],
  "temperatures": [ ... ],
  "other":        [ ... ]
}

event_logs

Нормализованные операционные логи сервера из host, bmc или redfish.

Эти записи не попадают в history timeline и не создают history events. Они сохраняются в отдельной deduplicated log store и отображаются в отдельном UI-блоке asset logs / host logs.

Поле Тип Обязательно Описание
source string да Источник лога: host, bmc, redfish
event_time string RFC3339 нет Время события из источника; если отсутствует, используется время ingest/collection
severity string нет Уровень: OK, Info, Warning, Critical, Unknown
message_id string нет Идентификатор/код события источника
message string да Нормализованный текст события
component_ref string нет Ссылка на компонент/устройство/слот, если извлекается
fingerprint string нет Внешний готовый dedup-key; если не передан, система вычисляет свой
is_active bool нет Признак, что событие всё ещё активно/не погашено, если источник умеет lifecycle
raw_payload object нет Сырой vendor-specific payload для диагностики

Правила event_logs:

  • Логи дедуплицируются в рамках asset + source + fingerprint.
  • Если fingerprint не передан, система строит его из нормализованных полей (source, message_id, message, component_ref, временная нормализация).
  • Интегратор/сборщик логов не должен синтезировать содержимое событий: не придумывайте message, message_id, component_ref, serial/device identifiers или иные поля, если они отсутствуют в исходном логе или не были надёжно извлечены.
  • Повторное получение того же события обновляет last_seen_at/счётчик повторов и не должно создавать новый timeline/history event.
  • event_logs используются для отдельного UI-представления логов и не изменяют canonical state компонентов/asset по умолчанию.
"event_logs": [
  {
    "source": "bmc",
    "event_time": "2026-03-15T14:03:11Z",
    "severity": "Warning",
    "message_id": "0x000F",
    "message": "Correctable ECC error threshold exceeded",
    "component_ref": "CPU0_C0D0",
    "raw_payload": {
      "sensor": "DIMM_A1",
      "sel_record_id": "0042"
    }
  },
  {
    "source": "redfish",
    "event_time": "2026-03-15T14:03:20Z",
    "severity": "Info",
    "message_id": "OpenBMC.0.1.SystemReboot",
    "message": "System reboot requested by administrator",
    "component_ref": "Mainboard"
  }
]

sensors.fans

Поле Тип Обязательно Описание
name string да Уникальное имя сенсора в рамках секции
location string нет Физическое расположение
rpm int нет Обороты, RPM
status string нет Статус: OK, Warning, Critical, Unknown

sensors.power

Поле Тип Обязательно Описание
name string да Уникальное имя сенсора
location string нет Физическое расположение
voltage_v float нет Напряжение, В
current_a float нет Ток, А
power_w float нет Мощность, Вт
status string нет Статус

sensors.temperatures

Поле Тип Обязательно Описание
name string да Уникальное имя сенсора
location string нет Физическое расположение
celsius float нет Температура, °C
threshold_warning_celsius float нет Порог Warning, °C
threshold_critical_celsius float нет Порог Critical, °C
status string нет Статус

sensors.other

Поле Тип Обязательно Описание
name string да Уникальное имя сенсора
location string нет Физическое расположение
value float нет Значение
unit string нет Единица измерения
status string нет Статус

Правила sensors:

  • Идентификатор сенсора: пара (sensor_type, name). Дубли в одном payload — берётся первое вхождение.
  • Сенсоры без name игнорируются.
  • При каждом импорте значения перезаписываются (upsert по ключу).
"sensors": {
  "fans": [
    { "name": "FAN1", "location": "Front", "rpm": 4200, "status": "OK" },
    { "name": "FAN_CPU0", "location": "CPU0", "rpm": 5600, "status": "OK" }
  ],
  "power": [
    { "name": "12V Rail", "location": "Mainboard", "voltage_v": 12.06, "status": "OK" },
    { "name": "PSU0 Input", "location": "PSU0", "voltage_v": 215.25, "current_a": 0.64, "power_w": 137.0, "status": "OK" }
  ],
  "temperatures": [
    { "name": "CPU0 Temp", "location": "CPU0", "celsius": 46.0, "threshold_warning_celsius": 80.0, "threshold_critical_celsius": 95.0, "status": "OK" },
    { "name": "Inlet Temp", "location": "Front", "celsius": 22.0, "threshold_warning_celsius": 40.0, "threshold_critical_celsius": 50.0, "status": "OK" }
  ],
  "other": [
    { "name": "System Humidity", "value": 38.5, "unit": "%", "status": "OK" }
  ]
}

Обработка статусов компонентов

Статус Поведение
OK Нормальная обработка
Warning Создаётся событие COMPONENT_WARNING
Critical Создаётся событие COMPONENT_FAILED + запись в failure_events
Unknown Компонент считается рабочим, создаётся событие COMPONENT_UNKNOWN
Empty Компонент не создаётся/не обновляется

Обработка отсутствующих serial_number

Общее правило для всех секций: если источник не вернул серийный номер и сборщик не смог его надёжно извлечь, интегратор не должен подставлять вымышленные значения, хеши, локальные placeholder-идентификаторы или серийные номера "по догадке". Разрешены только явно оговорённые ниже server-side fallback-правила ingest.

Тип Поведение
CPU Генерируется: {board_serial}-CPU-{socket}
PCIe Генерируется: {board_serial}-PCIE-{slot} (если serial = "N/A" или пустой; slot для PCIe = BDF)
Memory Компонент игнорируется
Storage Компонент игнорируется
PSU Компонент игнорируется

Если serial_number не уникален внутри одного payload для того же model:

  • Первое вхождение сохраняет оригинальный серийный номер.
  • Каждое следующее дублирующее получает placeholder: NO_SN-XXXXXXXX.

Минимальный валидный пример

{
  "collected_at": "2026-02-10T15:30:00Z",
  "target_host": "192.168.1.100",
  "hardware": {
    "board": {
      "serial_number": "SRV-001"
    }
  }
}

Полный пример с историей статусов

{
  "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": {
      "manufacturer": "Supermicro",
      "product_name": "X12DPG-QT6",
      "serial_number": "21D634101"
    },
    "firmware": [
      { "device_name": "BIOS", "version": "06.08.05" },
      { "device_name": "BMC",  "version": "5.17.00"  }
    ],
    "cpus": [
      {
        "socket": 0,
        "model": "INTEL(R) XEON(R) GOLD 6530",
        "manufacturer": "Intel",
        "cores": 32,
        "threads": 64,
        "status": "OK"
      }
    ],
    "storage": [
      {
        "slot": "OB01",
        "type": "NVMe",
        "model": "INTEL SSDPF2KX076T1",
        "size_gb": 7680,
        "serial_number": "BTAX41900GF87P6DGN",
        "manufacturer": "Intel",
        "firmware": "9CV10510",
        "present": true,
        "status": "OK",
        "status_changed_at": "2026-02-10T15:22:00Z",
        "status_history": [
          { "status": "Critical", "changed_at": "2026-02-10T15:10:00Z", "details": "I/O timeout on NVMe queue 3" },
          { "status": "OK",       "changed_at": "2026-02-10T15:22:00Z", "details": "Recovered after controller reset" }
        ]
      }
    ],
    "pcie_devices": [
      {
        "slot": "0000:18:00.0",
        "device_class": "EthernetController",
        "manufacturer": "Intel",
        "model": "X710 10GbE",
        "serial_number": "K65472-003",
        "mac_addresses": ["3c:fd:fe:aa:bb:cc", "3c:fd:fe:aa:bb:cd"],
        "status": "OK"
      }
    ],
    "power_supplies": [
      {
        "slot": "0",
        "present": true,
        "model": "GW-CRPS3000LW",
        "vendor": "Great Wall",
        "wattage_w": 3000,
        "serial_number": "2P06C102610",
        "firmware": "00.03.05",
        "status": "OK",
        "input_power_w": 137,
        "output_power_w": 104,
        "input_voltage": 215.25
      }
    ],
    "sensors": {
      "fans": [
        { "name": "FAN1", "location": "Front", "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": "%" }
      ]
    }
  }
}