// Package telemetry contains operational projections that do not own domain // state. Compatibility events are intentionally redacted and best-effort. package telemetry import ( "crypto/sha256" "encoding/hex" "encoding/json" "log" "strconv" "time" ) // CompatibilityEvent is one bounded invocation observation. Aggregators can // derive counts and unique request/session counts from the redacted keys. // It deliberately contains no content, secret, or user identifier. type CompatibilityEvent struct { Event string `json:"event"` Path string `json:"path"` Component string `json:"component"` Access string `json:"access"` Reason string `json:"reason"` Gen2PathAvailable bool `json:"gen2_path_available"` Fallback bool `json:"fallback"` Primary bool `json:"primary"` Outcome string `json:"outcome"` InvocationCount int `json:"invocation_count"` UniqueRequestKey string `json:"unique_request_key"` UniqueSessionKey string `json:"unique_session_key"` SafeCorrelationID string `json:"correlation_id"` LastUsedAt string `json:"last_used_at"` ObservedAtUnixMillis int64 `json:"observed_at_unix_millis"` } // NewCompatibilityEvent creates a redacted event. requestRef/sessionRef are // hashed immediately; callers must pass identifiers only, never content. func NewCompatibilityEvent(path, component, access, reason string, gen2PathAvailable, fallback, primary bool, outcome, requestRef, sessionRef string, now time.Time) CompatibilityEvent { if now.IsZero() { now = time.Now().UTC() } requestKey := digest(requestRef) sessionKey := digest(sessionRef) correlationInput := requestRef + "\x00" + sessionRef + "\x00" + path + "\x00" + strconv.FormatInt(now.UnixMilli(), 10) return CompatibilityEvent{ Event: "compatibility_observation", Path: path, Component: component, Access: access, Reason: reason, Gen2PathAvailable: gen2PathAvailable, Fallback: fallback, Primary: primary, Outcome: outcome, InvocationCount: 1, UniqueRequestKey: requestKey, UniqueSessionKey: sessionKey, SafeCorrelationID: digest(correlationInput), LastUsedAt: now.UTC().Format(time.RFC3339Nano), ObservedAtUnixMillis: now.UnixMilli(), } } // RecordCompatibility emits a structured redacted event. Telemetry is // best-effort and cannot change the compatibility path's result. func RecordCompatibility(path, component, access, reason string, gen2PathAvailable, fallback, primary bool, outcome, requestRef, sessionRef string, now time.Time) { event := NewCompatibilityEvent(path, component, access, reason, gen2PathAvailable, fallback, primary, outcome, requestRef, sessionRef, now) payload, err := json.Marshal(event) if err != nil { return } log.Printf("%s", payload) } func digest(value string) string { sum := sha256.Sum256([]byte(value)) return hex.EncodeToString(sum[:8]) }