# Hardware Ingest Contract Version: 2.4 Updated: 2026-03-15 Source: Reanimator Core `hardware-ingest-contract.md` This file mirrors the external Reanimator hardware-ingest contract that LOGPile targets. The Reanimator endpoint uses strict JSON decoding. Any field not listed here must not be emitted. ## Endpoint ```http POST /ingest/hardware Content-Type: application/json ``` The ingest request is asynchronous. Accepted response: ```json { "status": "accepted", "job_id": "job_01J..." } ``` Final result is available from: ```http GET /ingest/hardware/jobs/{job_id} ``` ## Top-level payload ```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": {} } } ``` ## Top-level rules - `collected_at` is required and must be RFC3339 - `hardware.board.serial_number` is required - `source_type` allowed values: `api`, `logfile`, `manual` - `protocol` allowed values: `redfish`, `ipmi`, `snmp`, `ssh` - Unknown JSON keys are rejected by Reanimator ## Shared component status fields Allowed on `cpus`, `memory`, `storage`, `pcie_devices`, `power_supplies`: - `status` - `status_checked_at` - `status_changed_at` - `status_history` - `error_description` `status_history[]` items: - `status` - `changed_at` - `details` Do not emit `status_at_collection`; it is not part of the current strict ingest schema. ## `hardware.board` Allowed fields: - `serial_number` required - `manufacturer` - `product_name` - `part_number` - `uuid` String values equal to `"NULL"` should be omitted. ## `hardware.firmware` Allowed fields: - `device_name` - `version` Only system-level firmware belongs here. Device-bound firmware must stay on the relevant device record and must not be duplicated at the top level. ## `hardware.cpus` Allowed fields: - `socket` - `model` - `manufacturer` - `cores` - `threads` - `frequency_mhz` - `max_frequency_mhz` - `temperature_c` - `power_w` - `throttled` - `correctable_error_count` - `uncorrectable_error_count` - `life_remaining_pct` - `life_used_pct` - `serial_number` - `firmware` - `present` - shared status fields Exporter rule: - if CPU serial is missing, generate `{board_serial}-CPU-{socket}` ## `hardware.memory` Allowed fields: - `slot` - `location` - `present` - `serial_number` - `part_number` - `manufacturer` - `size_mb` - `type` - `max_speed_mhz` - `current_speed_mhz` - `temperature_c` - `correctable_ecc_error_count` - `uncorrectable_ecc_error_count` - `life_remaining_pct` - `life_used_pct` - `spare_blocks_remaining_pct` - `performance_degraded` - `data_loss_detected` - shared status fields Exporter rules: - skip memory items with missing `serial_number` - skip memory items with `present=false` - skip memory items with `status=Empty` ## `hardware.storage` Allowed fields: - `slot` - `serial_number` - `model` - `manufacturer` - `type` - `interface` - `size_gb` - `temperature_c` - `power_on_hours` - `power_cycles` - `unsafe_shutdowns` - `media_errors` - `error_log_entries` - `written_bytes` - `read_bytes` - `life_used_pct` - `firmware` - `present` - `remaining_endurance_pct` - `life_remaining_pct` - `available_spare_pct` - `reallocated_sectors` - `current_pending_sectors` - `offline_uncorrectable` - shared status fields Exporter rule: - skip storage items with missing `serial_number` ## `hardware.pcie_devices` Allowed fields: - `slot` - `vendor_id` - `device_id` - `numa_node` - `temperature_c` - `power_w` - `life_remaining_pct` - `life_used_pct` - `ecc_corrected_total` - `ecc_uncorrected_total` - `hw_slowdown` - `battery_charge_pct` - `battery_health_pct` - `battery_temperature_c` - `battery_voltage_v` - `battery_replace_required` - `sfp_temperature_c` - `sfp_tx_power_dbm` - `sfp_rx_power_dbm` - `sfp_voltage_v` - `sfp_bias_ma` - `bdf` - `device_class` - `manufacturer` - `model` - `serial_number` - `firmware` - `link_width` - `link_speed` - `max_link_width` - `max_link_speed` - `mac_addresses` - `present` - shared status fields Known `device_class` values: - `MassStorageController` - `StorageController` - `NetworkController` - `EthernetController` - `FibreChannelController` - `VideoController` - `ProcessingAccelerator` - `DisplayController` Exporter rules: - if PCIe serial is missing or placeholder-like, generate `{board_serial}-PCIE-{slot}` - `numa_node` is allowed again in the upstream contract - network adapters should emit `mac_addresses` when available - do not emit fields outside the upstream contract ## `hardware.power_supplies` Allowed fields: - `slot` - `present` - `serial_number` - `part_number` - `model` - `vendor` - `wattage_w` - `firmware` - `input_type` - `input_voltage` - `input_power_w` - `output_power_w` - `temperature_c` - `life_remaining_pct` - `life_used_pct` - shared status fields Exporter rule: - skip PSUs with missing `serial_number` ## `hardware.sensors` Shape: ```json { "fans": [], "power": [], "temperatures": [], "other": [] } ``` ### `sensors.fans` Allowed fields: - `name` required - `location` - `rpm` - `status` ### `sensors.power` Allowed fields: - `name` required - `location` - `voltage_v` - `current_a` - `power_w` - `status` ### `sensors.temperatures` Allowed fields: - `name` required - `location` - `celsius` - `threshold_warning_celsius` - `threshold_critical_celsius` - `status` ### `sensors.other` Allowed fields: - `name` required - `location` - `value` - `unit` - `status` Sensor rules: - dedupe within one payload by `(section, name)`, keeping the first item - skip sensors without `name` - the current LOGPile exporter maps generic `SensorReading` values into these four groups heuristically