Lockstep release of @raasimpact/arduino-uno-q-bridge@0.4.0 and n8n-nodes-uno-q@0.4.0. Adds a fourth transport — reverse-SSH (Variant B) — for NAT-ed Qs that can dial out but can't accept inbound connections. Ships alongside a robustness fix in Bridge.close() that prevents MCU code from hanging when an n8n bridge closes with router-forwarded requests in flight.
The Q-side autossh container ships separately under deploy/relay-ssh/ (deploy tooling shipped 2026-04-25, n8n-side this release).
@raasimpact/arduino-uno-q-bridge 0.3.0 → 0.4.0
Added
SshTransport— fourth transport, sitting on top of an externally-suppliedDuplexstream rather than dialing a socket itself. The Variant B deployment routes each device through the n8n-side singleton (master plan §14); the bridge just receives theforwardOutchannel for one specific device. Construct withnew SshTransport({ connect: () => Promise<Duplex> })and pass it viaBridge.connect({ transportInstance }).'ssh'discriminant onTransportDescriptorwithlistenAddress,listenPort,deviceNick(the cert KeyID — the only routing key on the n8n side per §14.4). The factory throws if a caller tries to construct an SSH transport from descriptor alone — theDuplexmust come from the singleton, sotransportInstanceis mandatory for this kind.
Changed
Bridge.close()now drains in-flight router-forwarded requests before tearing down the transport. Each pendingprovidehandler invocation gets an explicit[1, "bridge closing while handling <method>"]error response written to the wire, so any caller blocked on a synchronous reply (notably an MCU executingBridge.call(...)inside itsloop()) unblocks instead of hanging forever. The handler's own response writes use the in-flight Map'sdeleteas a CAS guard to avoid double-sends.Bridge.close()now sends\$/resetto the router (with a 500 ms cap so a slow router doesn't block close) to drop every method this connection registered. Without this, the router kept routing for a dead socket; the next caller for one of those methods would either hang or surface a transport-layer error rather than a clean method not available. Sent unconditionally because callers (tests, drift recovery paths) may have mutatedprovidersdirectly, so the local view ≠ router-side truth.
n8n-nodes-uno-q 0.3.0 → 0.4.0
Added
- Reverse-SSH transport mode on the
Arduino UNO Q Routercredential. Selectable from the same dropdown as the Unix / TCP / mTLS variants. Configuration: a listen address + port the n8n side binds, the host private key the embedded SSH server presents, the user CA public key it trusts, and a Required principal (defaults totunnel). Routing is by user-cert KeyID — the only n8n-side routing key per master plan §14.4. - Process-singleton
SshServerthat accepts inbound connections from Q autossh clients, validates user certs against a single CA, evicts zombie reconnects when a new client claims the same KeyID, and exposesconnect(deviceNick)to nodes — opens aforwarded-tcpipchannel back through the SSH session and returns theDuplexforSshTransportto wrap. LikeBridgeManager, stashes itself onglobalThisunder aSymbol.for(...)key so each esbuild bundle of a node file shares the same instance at process scope. sshCertParser— manual OpenSSH user-cert parser + ed25519 signature verification, written because ssh2 v1.17 doesn't surface user certs through itsparseKeyAPI. Validates KeyID, principals, validity window, extensions (permit-port-forwardingrequired), and rejects any unrecognised critical option per the OpenSSH spec.- Test Connection for the SSH transport runs the standard
\$/versionround-trip end-to-end through the spawned forward, surfacing transport-specific failures (no device registered, cert rejected, host key mismatch) as legible messages at credential save rather than at first execution. BridgeManagerconnection-pool entry for the'ssh'descriptor kind — keyed by(listenAddress, listenPort, deviceNick)so the same Q reached from two nodes shares one Bridge, while two Qs on the same listener share the listener but get separate Bridges. TheSshServeris brought up lazily when the first SSH-mode credential acquires, and torn down when the last reference drops.
Changed
- Bumped
@raasimpact/arduino-uno-q-bridgeto^0.4.0— required forSshTransport. The same bump brings in the close-time drain +\$/resetbehaviour that prevents MCU code from hanging when a bridge closes with router-forwarded requests in flight (relevant whenever a workflow saves, an agent deactivates, or n8n restarts mid-call).
This release was validated against a real UNO Q across all four transport variants (unix, tcp, mtls, ssh) plus the arduino-cloud suite — 56/56 integration assertions pass, and a post-suite probe confirms the MCU's loop() stays unwedged, which it didn't reliably before the close-path fix. The same orchestrator (./scripts/run-integration.sh) is now part of the repo for future releases.