First, for your second line
Node* dummy = reinterpret_cast<Node*>(new int8_t[sizeof(Node)]);
by itself.
new returns a pointer to the first int8_t object in the array of int8_t objects it created.
reinterpret_cast's behavior depends on the alignment of the address represented by the pointer. If it is suitably aligned for an object of type Node, then it will leave the pointer value unchanged (since there is definitively no Node object at the location which is pointer-interconvertible with the int8_t object). If it is not suitably aligned, the returned pointer value will be unspecified.
Unspecified means that we won't know what the value will be, but it wont cause undefined behavior.
Therefore, in any case, the second line and the cast by itself do not have undefined behavior.
The line
dummy->prev = ... /* last node */;
requires that the object dummy points to is actually a Node object. Otherwise it has undefined behavior. As mentioned above, reinterpret_cast gives us either an unspecified value or a pointer to the int8_t object. This already is an issue, that I think at least requires a std::launder call.
Even if the pointer returned from new is correctly aligned, then we still need to check whether a Node object is present. We certainly did not create any such object in any of the shown operations explicitly, but there is implicit object creation which may help out (at least since C++20, but I suppose this was supposed to be a defect report against older standard versions).
Specifically, objects may be created implicitly inside an array of types unsigned char, std::byte and, with some limitations, char (CWG 2489) when the lifetime of the array is started. int8_t is usually signed char and I think is not allowed to be either of the three previously mentioned types (see e.g. this question). This removes the only possible way out of UB.
So your third code line does have undefined behavior.
Even if you remedy this by changing the type form int8_t to std::byte, there are other constraints on the details of Node to make the implicit object creation possible. It may also be necessary to add a std::launder call.
All of this doesn't consider the alignment yet, because although new[] obtains memory with some alignment requirements, I think the standard mandates new[] itself to return a pointer with stronger alignment than required for the element type only for char, unsigned char and std::byte array new.
Many of these issues can probably be avoided by using e.g. operator new directly, possibly with provided alignment request, and making sure that Node is an aggregate.
In any case writing code like this is very risky because it is difficult to be sure that it isn't UB. It should be avoided when ever possible.