feat(13-01): add DeviceInterfaceEvent publisher and wire into PollDevice

- DeviceInterfaceEvent type publishes to device.interfaces.{device_id}
- PublishDeviceInterfaces method follows existing publisher pattern
- DEVICE_EVENTS stream includes device.interfaces.> subject
- PollDevice collects interface info after traffic counters, before health
- Non-fatal errors with Prometheus metrics for publish success/failure

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jason Staack
2026-03-19 06:05:55 -05:00
parent 6939584428
commit 397a33abef
2 changed files with 62 additions and 0 deletions

View File

@@ -326,6 +326,29 @@ func PollDevice(
observability.NATSPublishTotal.WithLabelValues("metrics", "success").Inc()
}
// Interface identity data for link discovery (MAC addresses, types).
cmdCtx, cmdCancel = context.WithTimeout(ctx, cmdTimeout)
ifaceInfo, err := withTimeout[[]device.InterfaceInfo](cmdCtx, func() ([]device.InterfaceInfo, error) {
return device.CollectInterfaceInfo(client)
})
cmdCancel()
if err != nil {
slog.Warn("failed to collect interface info", "device_id", dev.ID, "error", err)
}
if len(ifaceInfo) > 0 {
if pubErr := pub.PublishDeviceInterfaces(ctx, bus.DeviceInterfaceEvent{
DeviceID: dev.ID,
TenantID: dev.TenantID,
CollectedAt: collectedAt,
Interfaces: ifaceInfo,
}); pubErr != nil {
slog.Warn("failed to publish device interfaces", "device_id", dev.ID, "error", pubErr)
observability.NATSPublishTotal.WithLabelValues("interfaces_info", "error").Inc()
} else {
observability.NATSPublishTotal.WithLabelValues("interfaces_info", "success").Inc()
}
}
// System health (CPU, memory, disk, temperature).
cmdCtx, cmdCancel = context.WithTimeout(ctx, cmdTimeout)
health, err := withTimeout[device.HealthMetrics](cmdCtx, func() (device.HealthMetrics, error) {