The playbook uses 'gather_facts: no' at the play level, which is fine. However, the ios_facts module returns facts under the key 'ansible_facts' by default, but the registered variable 'device_facts' will contain the entire response, including 'ansible_facts' as a subkey. The debug message tries to access 'device_facts['ansible_facts']['ansible_net_serialnum']', which is correct.
But there is a subtle issue: The module 'ios_facts' requires the connection to be 'network_cli' and privilege escalation. If not set, the task may fail. Also, the 'gather_subset' parameter is misspelled as 'gather_subset' instead of 'gather_subset'? Actually, it's 'gather_subset' in the module.
Wait, the correct parameter is 'gather_subset'? Let me check: In cisco.ios.ios_facts, the parameter is 'gather_subset' (with underscore). The playbook uses 'gather_subset' which is correct. Hmm.
Another common issue: The 'ansible_net_serialnum' fact might not be available if the hardware subset does not include it. But hardware subset does include serial number. The real issue might be that the playbook does not specify 'connection: network_cli' and 'become: yes', which are required for network modules.
That is the most likely problem.