Historically, goroutines worked like in libdill, only passing control to the scheduler on certain calls. So did the Go runtime suddenly become a VM at version 1.14, when preemption was added?
And why the distinction between OS threads and green threads? Sure, in case of OS threads, it's not the language runtime that does the scheduling, but that just means that the operating system is now our virtual machine.
I would argue that what makes a language runtime a virtual machine is not the presence of a garbage collector, a jit compiler, or a scheduler (with or without preemption), but that it has well-defined semantics in terms of something that looks a bit like a real machine - hence the name! In particular, there's a set of instructions it understands. In case of the JVM, the instruction set is defined in the Java Virtual Machine Specification (with Java bytecode its representation), in case of the CLR, it's defined in the Common Language Infrastructure specification (with CIL bytecode its representation), in case of Smalltalk, it's defined in the Blue Book, in case of WASM, in the WebAssembly core specification.