// Package events defines safe inspection records shared by logs and the future viewer. package events import ( "encoding/base64" "encoding/json" "time" "unicode/utf8" "robot-market/test-mqtt-5/internal/contracts" ) type Direction string const ( In Direction = "in" Out Direction = "out" Internal Direction = "internal" ) type Kind string const ( PublishAttempt Kind = "publish_attempt" TransportAck Kind = "transport_ack" MessageReceived Kind = "message_received" ValidationFailed Kind = "validation_failed" CommandAccepted Kind = "command_accepted" CommandCompleted Kind = "command_completed" CommandTimeout Kind = "command_timeout" LateAck Kind = "late_ack" DuplicateRequest Kind = "duplicate_request" ConnectionUp Kind = "connection_up" ConnectionDown Kind = "connection_down" EventGap Kind = "event_gap" ) type Properties struct { ResponseTopic string `json:"response_topic,omitempty"` CorrelationData []byte `json:"correlation_data,omitempty"` MessageExpirySeconds *uint32 `json:"message_expiry_seconds,omitempty"` } // Event contains inspection data only. Configuration and connection credentials // are never attached. Raw MQTT payloads require the explicit diagnostics opt-in. type Event struct { Scenario *ScenarioResult `json:"scenario,omitempty"` ID uint64 `json:"id"` ObservedAt time.Time `json:"observed_at"` Kind Kind `json:"kind"` Direction Direction `json:"direction"` ClientID string `json:"client_id,omitempty"` DeviceID string `json:"device_id,omitempty"` Topic string `json:"topic,omitempty"` QoS byte `json:"qos"` Retain bool `json:"retain"` Duplicate *bool `json:"duplicate,omitempty"` Payload Preview `json:"payload"` Properties Properties `json:"properties"` RequestID string `json:"request_id,omitempty"` Outcome string `json:"outcome,omitempty"` LatencyMS *int64 `json:"latency_ms,omitempty"` } // ScenarioResult is reported test evidence, never inferred hardware compatibility. type ScenarioResult struct { Feature string `json:"feature"` Environment string `json:"environment"` Endpoint string `json:"endpoint"` Version string `json:"version"` Status string `json:"status"` Observation string `json:"observation"` Time time.Time `json:"time"` } type Preview struct { Bytes int `json:"bytes"` Text string `json:"text,omitempty"` Omitted bool `json:"omitted"` Truncated bool `json:"truncated"` } // SafePreview only renders validated contract fields. Unknown fields (including // credentials) cause the entire payload to be omitted. Free-form error messages // are always redacted. The original payload is never retained in this value. func SafePreview(kind, device string, payload []byte, limit int) Preview { p := Preview{Bytes: len(payload), Omitted: true} if limit < 1 { return p } if limit > 4096 { limit = 4096 } msg, err := contracts.Decode(kind, payload, device) if err != nil { return p } if ack, ok := msg.(*contracts.Ack); ok && ack.Error != nil { ack.Error.Message = "[redacted]" } encoded, err := json.Marshal(msg) if err != nil { return p } if len(encoded) > limit { encoded = encoded[:limit] for len(encoded) > 0 && !utf8.Valid(encoded) { encoded = encoded[:len(encoded)-1] } p.Truncated = true } p.Text = string(encoded) p.Omitted = false return p } // RawPreview is opt-in diagnostics for this isolated test harness. It displays // received bytes without treating them as a valid contract or execution evidence. func RawPreview(payload []byte, limit int) Preview { p := Preview{Bytes: len(payload)} if limit < 1 { p.Omitted = true return p } limit = min(limit, 4096) text := string(payload) if !utf8.Valid(payload) { text = "[base64] " + base64.StdEncoding.EncodeToString(payload) } if len(text) > limit { text = text[:limit] for !utf8.ValidString(text) { text = text[:len(text)-1] } p.Truncated = true } p.Text = text return p }