>An assembler produces machine code for the machine (i.e. processor) not the operating system.
Correct, but that doesn't make the machine code portable because it relies on knowledge of the operating system that it's assembled on. It very likely makes system calls that are not portable. It's required to match object file formats if the machine code is to be executable. Just because "in theory" portable machine code for a processor can be produced with some mythical universal assembler, it doesn't mean that the real world works that way.
The difference between an assembler and compiler is that there is a direct one to one correspondence between assembly language instructions and machine code instructions, whereas a line of code in a high level language could correspond to a whole subroutine in machine code.
I take it you're not familiar with macro assembly then. The one-to-one dealie hasn't logically applied in a long time. Sure, after macro expansion it's one-to-one, but that step is usually a part of the assembly and nobody sees it.
>without calls to the operating system, the code is as portable as could be wished for OS wise.
The assembly code might be portable, the machine code cannot be. For example, you can assemble a COFF program on Windows and it won't work on Linux because the two systems use slightly different formats for COFF. You need to take the portable assembly and assemble it individually for each system to get machine code that will work correctly.
>How do you think the operating system itself gets written, if assembly language is operating system dependent?
That question makes no sense at all. When the rules don't exist (such as writing the host itself), you can do anything. When the rules exist (such as writing a hosted program), you're bound by them.