// Package telemetry provides shared edge observability helpers used by the // gateway transports and internal event consumers. package telemetry import ( "net/http" "strings" "google.golang.org/grpc/codes" ) // EdgeOutcome is the stable low-cardinality outcome vocabulary shared by REST, // gRPC, push shutdown, and observability backends. type EdgeOutcome string const ( EdgeOutcomeSuccess EdgeOutcome = "success" EdgeOutcomeMalformedRequest EdgeOutcome = "malformed_request" EdgeOutcomeRequestTooLarge EdgeOutcome = "request_too_large" EdgeOutcomeUnsupportedProtocol EdgeOutcome = "unsupported_protocol" EdgeOutcomeUnknownSession EdgeOutcome = "unknown_session" EdgeOutcomeRevokedSession EdgeOutcome = "revoked_session" EdgeOutcomeInvalidSignature EdgeOutcome = "invalid_signature" EdgeOutcomeStaleRequest EdgeOutcome = "stale_request" EdgeOutcomeReplayDetected EdgeOutcome = "replay_detected" EdgeOutcomeRateLimited EdgeOutcome = "rate_limited" EdgeOutcomePolicyDenied EdgeOutcome = "policy_denied" EdgeOutcomeDownstreamUnavailable EdgeOutcome = "downstream_unavailable" EdgeOutcomeBackendUnavailable EdgeOutcome = "backend_unavailable" EdgeOutcomeInternalError EdgeOutcome = "internal_error" EdgeOutcomeGatewayShuttingDown EdgeOutcome = "gateway_shutting_down" ) // RejectReason returns the stable reject reason for outcome. Success does not // produce a reject reason. func RejectReason(outcome EdgeOutcome) string { if outcome == EdgeOutcomeSuccess { return "" } return string(outcome) } // OutcomeFromPublicErrorCode maps the stable public REST error envelope into // the shared edge-outcome vocabulary. func OutcomeFromPublicErrorCode(statusCode int, code string) EdgeOutcome { switch strings.TrimSpace(code) { case "": if statusCode < http.StatusBadRequest { return EdgeOutcomeSuccess } return EdgeOutcomeInternalError case "invalid_request", "method_not_allowed", "not_found": return EdgeOutcomeMalformedRequest case "request_too_large": return EdgeOutcomeRequestTooLarge case "rate_limited": return EdgeOutcomeRateLimited case "service_unavailable": return EdgeOutcomeBackendUnavailable default: if statusCode >= http.StatusInternalServerError { return EdgeOutcomeInternalError } return EdgeOutcomeMalformedRequest } } // OutcomeFromGRPCStatus maps the stable authenticated gRPC reject contract // into the shared edge-outcome vocabulary. func OutcomeFromGRPCStatus(code codes.Code, message string) EdgeOutcome { switch { case code == codes.OK: return EdgeOutcomeSuccess case code == codes.InvalidArgument: return EdgeOutcomeMalformedRequest case code == codes.FailedPrecondition && strings.Contains(message, "unsupported protocol_version"): return EdgeOutcomeUnsupportedProtocol case code == codes.Unauthenticated && message == "unknown device session": return EdgeOutcomeUnknownSession case code == codes.FailedPrecondition && message == "device session is revoked": return EdgeOutcomeRevokedSession case code == codes.Unauthenticated && message == "invalid request signature": return EdgeOutcomeInvalidSignature case code == codes.FailedPrecondition && message == "request timestamp is outside the freshness window": return EdgeOutcomeStaleRequest case code == codes.FailedPrecondition && message == "request replay detected": return EdgeOutcomeReplayDetected case code == codes.ResourceExhausted && message == "authenticated request rate limit exceeded": return EdgeOutcomeRateLimited case code == codes.PermissionDenied && message == "authenticated request rejected by edge policy": return EdgeOutcomePolicyDenied case code == codes.Unavailable && message == "downstream service is unavailable": return EdgeOutcomeDownstreamUnavailable case code == codes.Unavailable && message == "gateway is shutting down": return EdgeOutcomeGatewayShuttingDown case code == codes.Unavailable: return EdgeOutcomeBackendUnavailable default: return EdgeOutcomeInternalError } }