`init()` reset `_instanceId` but left `snowflakeIdGenerator` in place, and
`load()` only builds the generator when there is none. A reload therefore
allocated a fresh worker id while the generator kept minting ids under the
previous one — a worker id the process had just released, and which another
replica is free to take. Two processes then share a worker id, their per-second
sequences both start at 0, and they emit byte-identical ids until the second
rolls over, so concurrent inserts collide on the primary key.
Rebuild the generator whenever the worker id is re-allocated by clearing it in
`init()`, next to `_instanceId`.
Release the worker id before closing the connections in `disposeServices()`.
The allocator deletes its lease over Redis, so running after the connection
manager closed turned every release into a failed delete, leaving the lease in
place but no longer renewed until it expired.
Treat `_instanceId` of 0 as allocated. `0` is a valid worker id, so the falsy
check discarded an explicitly configured one and re-allocated on every load.
Validate the app name in `getRequestHandleAppName`, the single place a
request-controlled identifier becomes an app name, so the websocket server and
the supervisor adapters are covered as well as the http handler. Rejections go
through the shared gateway error table.
- `getLoggerFilePath` requires the resolved path to stay under the log root.
- `MainOnlyAdapter` keeps its status for `main` alone instead of in one field
shared by every name.
- Gateway errors are logged under the application they concern again, once the
supervisor has that application.
- A failed websocket upgrade destroys the socket instead of rejecting unhandled.
- Names that already exist on `Object.prototype` are rejected.
`LegacyAdapter` in `plugin-multi-app-manager` is deliberately untouched.