Unfortunately, posix_spawn is woefully underpowered. I can't make the child process a session leader (setsid) or process group leader (setpgrp). I can't set a working directory. Etcetera.
The role of posix_spawn is for spawning "helper processes", not starting new process groups. This should overwhelming be your common launch case. posix_spawn is so much faster (on BSD/Mac anyway) that fork should be outright avoided.
Processes requiring other permissions can/should be spawned by asking systemd/chron/init/launchd to launch them for you.
And extensions to let us specify failure-case or other behavior of those extensions, and so on. But at that point, we're already heading down the road of implementing a tiny DSL for "the program that posix_spawn() should run after creating the new process but before exec()ing the new executable". Why not simply write that code in the host language? You could specify a thunk of code to be sent to the new process and executed there. Oh, you'll also need to pass any data structures that code relies on--- pass a closure, not just a thunk. And garbage-collected references to any system objects that those data structures rely on. Congratulations, now you have fork()! If you squint a little, that's exactly what fork() provides you --- a closure and continuation.
Yes, you end up with a tiny DSL for specifying transformations to make to a child process: they key difference is that the kernel can execute this DSL much more efficiently than it can code written in the host language: fork closes over the entire world, and posix_spawn doesn't have to do that.