So at a minimum, dlopen can invoke Rosetta again in this case. But why thrown an error? Why not allow an x86 application to open an ARM library if it is actually ARM code at run-time?
The situation is actually even weirder than that because there is an application (Clozure Common Lisp) that produces x86 code at run time but does not go through dlopen, it just writes x86 opcodes directly into memory (AFAIK). And yet, it works when run under Rosetta. How is that possible?
All of the evidence indicates to me that Rosetta is actually an emulator and not a translator. But why would Apple misrepresent this? It makes no sense.
Because the Application Binary Interface (ABI) of ARM and x86-64 is different. Function calling conventions don't map 1:1, structure layouts may differ, exceptions can't be propagated properly, etc. etc. etc..
That's why Rosetta only allows you to mix code of a single architecture in a given address space, so that it can be emulated as a whole. That way all code that's being executed has the same ABI.
Lots of useful information in this reverse-engineering article: https://ffri.github.io/ProjectChampollion/part1/