Skip to content

The ConnectionErrorKind taxonomy ​

Status: both classifications below are decided and built, each stating the condition that would reopen it. Non-normative: the kinds and their meanings are documented on ConnectionErrorKind in the core library, and the reader-facing account of the taxonomy is in COMMUNICATION.md. See docs/notes/README.md.

This note records the implementation-level classification decisions behind the core library's terminal ConnectionErrorKind taxonomy -- the deferred-decision rationale for how a clean remote close and a non-attributable local runtime fault are tagged, and the conditions under which each would be revisited. ConnectionErrorKind is a transport-contract concern spanning every channel (SFTP, file-drop, WebRTC) and is part of the core library's public API.

Those two are the whole of what this note records. The other kinds took no decision worth a record: usage and security are the caller-misuse and tampering classifications the type itself defines, and protocol is the one every partner-deviation refusal takes -- a frame that fails validation, a partner-supplied index outside the agreed range, an inbound frame past a transport bound, a payload disclosure other than the one consented to -- each specified where its own control is, so nothing about the kind is open here.

It does not cover the channels, synchronization, message-delivery contract, or supporting services (see COMMUNICATION.md), the channel-security construction (see CHANNEL_SECURITY.md), the file-sync state machine and its UsageError -> exit-code mapping (see FILE_SYNC.md), or the PSI and key-exchange protocol (see PROTOCOL.md). Intended readers are developers extending the core library's transport layer.

Clean remote close versus local close ​

A local close - a signal-driven MessageConnection.close() - cancels any parked operation with kind "closed", signaling that nothing went wrong on this side. A clean remote close is instead reported as "transport": WebRTC tags its close event "transport" in apps/web/src/psi/transport/peerMessageConnection.ts, the same kind a dropped link or peer timeout gets. The two are kept distinct rather than unified under "closed". A local close() is an intent this side knows; a remote channel-close event is not - an orderly peer shutdown and a killed peer process close the channel identically - so the local side cannot accurately report "nothing went wrong" about the other end, and "transport" ("the link ended; a retry is reasonable") is the safe reading. The label has behavior behind it: the CLI's exit boundary reads a "transport" failure as retryable (69). This would only need revisiting if a consumer arises that must act on a clean peer close - one that has to tell a peer ending the session apart from a link that dropped. The resolution then is to give the clean-close path its own ConnectionErrorKind (for example "peer-closed"), rather than reusing "closed" (which would conflate a peer's close with a local one) or leaving it as "transport" (which conflates it with an abnormal drop). The change would be small and contained: the WebRTC transport already routes a clean close through finish() and an abnormal drop through fail() (apps/web/src/psi/transport/peerMessageConnection.ts), so only the kind stamped on the clean-close path would change - the mechanism that separates the two cases already exists.

Non-attributable local runtime faults ​

A failure to encrypt our own outbound data - or any comparable local crypto/runtime fault, such as resource exhaustion or a crypto-subsystem error - is tagged "transport", not "security" and not "usage". It is not tampering (nothing the peer did caused it) and it is not caller misuse (no API was used incorrectly); like a dropped link it is not the caller's fault and a retry is reasonable, which is exactly "transport"'s contract. A dedicated kind for non-attributable runtime faults (for example "system") was considered and deferred: today it would have a single call site (the AEAD send path's crypto.subtle.encrypt catch in packages/core/src/connection/encryptedMessageConnection.ts) and no consumer reaction distinct from "transport", and ConnectionErrorKind is part of the core library's public API, so a new value is not free. This would only need revisiting if a consumer arises that must report an internal runtime fault differently from a link failure - for example a kind-to-exit-code table that maps "security" to a distinct tampering code. The resolution then is to add the "system" kind in the same change that adds that consumer, rather than reusing "transport" (which conflates it with a link drop) or "usage" (which would misreport it as a configuration error).

See also ​