4.8.5 Connector Foundations
connect-handshake creates the transport for one outgoing candidate and returns
a new Handshake. It is internal implementation code, not Network.connect!.
The module also supplies the attempt’s credential-choice queue and sequential
connector-connect! driver. Reservation/election transitions, joining callers and
actual connection publication belong to network.ss.
4.8.5.1 Retry Driver
connector-connect!(monitor, context, tls-context, host, peer, addresses, credentials,
start, deadline, min-auth-expire, adaptive?: #f, config:, limits:) returns a confirmed Handshake,
returns false for ordinary terminal rejection/credential exhaustion, or propagates
the original failure. It always closes the credential queue when finished.
Addresses are stably ordered Unix-first. Setup fallback preserves the current credential. An eligible precommit credential rejection selects another credential and starts at the rejecting concrete endpoint, replacing its DNS entry in the current complete ordered sequence. Repeated retries do not reintroduce that DNS entry; other addresses, including earlier failed endpoints, remain available. Each physical retry has a fresh start/headroom, but deadline and min-auth-expire never move. Only proven peer auth-failed/lifetime refusals or local no-credentials permit a new credential. Admission refusal, unproven refusal, peer no-credentials, and any postcommit rejection terminate.
adaptive?: is an optional boolean keyword and is forwarded through transport
setup to make-handshake. Adaptive establishment requires min-auth-expire = 0;
nonzero targets are rejected before reservation/dialing. The zero is not an
expired lease: operation deadlines still apply, and the handshake enforces fixed
headroom and actual credential validity. Fixed callers keep the old positive-target
liveness checks and retry behavior. Neither setup mode nor target changes during
fallback or credential retry.
ConnectorMonitor extends HandshakeMonitor. connecting!(address, start)
reserves/checks owner liveness before setup and must roll itself back if it raises.
attach!(socket) exposes the raw StreamSocket immediately after connect, before
TLS, then exposes the upgraded view before Handshake construction. The monitor
atomically attaches or rejects against owner shutdown, without holding owner locks
for socket close/I/O. Attachment does not transfer setup’s cleanup responsibility.
Every subsequent setup exception calls failed!(address, error) after transport
cleanup, including terminal exceptions. Once a candidate exists, the inherited
closed!/complete! hooks retire or transfer it instead. Monitor exceptions are
never fallback signals and are not suppressed by cleanup.
Setup tracks callback provenance separately from exception type: even an attachment
callback throwing SSLHandshakeError or another retryable transport type terminates
the driver with the original exception after cleanup and failed!.
Setup fallback accepts only these source-level failures:
-
SocketConnectErrorderives from OSError and IOError and retains positive errno. Only ECONNREFUSED, ECONNRESET, ECONNABORTED, EHOSTUNREACH, ENETUNREACH, ENETDOWN, ETIMEDOUT, ENOENT, ENOTDIR, EACCES, and EPERM advance endpoints. EINVAL, EBADF, allocation/fd exhaustion, and other OS operations terminate. -
ResolverErrorderives from IOError and translates only native OS exceptions raised byhost-info, preserving its original argument list plus the hostname and source context. All native OS failures of that lookup permit fallback; no facility/status classifier is used. Non-OS argument, type, allocation, and programming exceptions propagate. Address conversion is outside the catch. Synchronous resolver work is not preempted. -
SSLHandshakeErrorderives from SSLError and marks negotiation failure, not proof of peer fault. Native result details remain available. Known allocation and internal failures, and SSL_ERROR_SYSCALL (whose errno the native wrapper does not retain) stay terminal SSLError, as do local SSL setup failures. -
TLSPeerIdentityErrorderives from IOError and marks certificate policy or expected-DID mismatch, not arbitrary crypto or contract failures.
Timeout, Closed, broad IOError/SSLError, arbitrary exceptions, and all exceptions after setup terminate. No messages or irritants are inspected by the retry driver.
4.8.5.2 Credential Choices
make-connector-credentials([auth]) creates an internal ConnectorCredentials
queue. When the initiating caller supplied no auth, its first choice is provide!.
Otherwise its parent enters the supplied-parent queue. Joining callers add only
explicit parents through connector-add-credential!(choices, parent); a caller
without auth adds no new choice.
connector-next-credential!(choices, min-auth-expire, headroom, now, adaptive?: #f) returns two
values: available? and parent. #t, #f selects the initial provide! path;
#t, token selects that parent; #f, #f means exhausted. Supplied parents are
selected longest-expiration-first, with arrival order breaking ties. A later
addition cannot preempt a choice already returned to the driver.
The queue uses Tokens directly as keys in make-hash-table, relying on their
transparent structural equality to deduplicate both queued and previously selected
parents. Caller tokens are borrowed and must remain immutable while used as keys.
Every distinct parent is considered at most once. Parents failing the ordinary
auth-lifetime? check for the supplied threshold/headroom/clock are discarded.
The clock and thresholds are integer Unix seconds supplied by the driver; the
queue reads no clock and does not change the attempt’s operation deadline.
With adaptive?: #t, selection preserves the same queue order and exhaustion
rules but does not discard a selected seed for insufficient/expired leaf lifetime.
The adaptive issuer must inspect the entire chain and still consider direct
authority and fresh context parents in the same handshake. This is important for
make-connector-credentials(expired-parent), whose fixed-mode queue intentionally
has no initial provide! choice. Adaptive setup must not exhaust it before querying
fresh context authority. No extra initial-seed retrieval API or queue record is
needed; the driver supplies this keyword internally. Structural queue dedup and
the adaptive issuer’s seed/context parent dedup are separate responsibilities.
One driver consumes choices, while joining callers may add them concurrently.
No token marshaling is needed for deduplication. Selection and additions are
serialized; exhaustion atomically closes the queue, so a racing addition either
participates or raises Closed, never silently revives exhausted work. The owner
calls connector-close-credentials! on success, cancellation, or terminal failure
to release retained choices. Close is idempotent; add/next on closed queues raise.
These helpers do not interpret handshake outcomes or catch construction/decoding exceptions. The driver requests another choice only for an ordinary eligible credential rejection; exceptions abort the operation. Context selection does not bypass the recipient’s verification or the existing lifetime/headroom checks.
4.8.5.3 API And Ownership
connect-handshake(context, tls-context, host, peer, address, start, deadline,
min-auth-expire, auth:, adaptive?: #f, config:, limits:, monitor:) borrows the capability and TLS contexts.
monitor: uses :? ConnectorMonitor, defaulting to #f for
standalone single-candidate callers. connector-connect! always supplies its monitor.
The TLS context must be the network’s mutual-TLS context for the host principal.
Unix candidates borrow it as well but do not use it. All timing values are
integer Unix seconds; config and limits have the same defaults and immutable
ownership conventions as make-handshake.
The owner reserves and tracks the attempt before calling and passes the already
captured start, absolute deadline, and required authorization expiration. The
connector does not reset these values or mint new credentials. It checks liveness
before dialing and after connection establishment. The same absolute IOTimeout
is supplied to stream-connect and TCP’s ssl-client-upgrade, and the handshake
constructor checks completion and reinstalls that deadline after TLS clears it.
stream-connect handles endpoint resolution, including DNS addresses, and selects
Unix or TCP. The returned socket’s domain selects plaintext Unix versus mandatory
TLS. TLS uses the expected peer DID’s hostname, and make-handshake additionally
checks the actual peer certificate key against that DID. A hostname match alone
is insufficient. Local identities are normalized and self-connections rejected
before dialing.
Success transfers the socket to the returned candidate in phase new. TCP peer
identity is already proven; Unix identity still requires the signed HELLO/AUTH
exchange. No application-handshake frames or application callbacks are emitted here.
The caller drives the staged handshake and eventual publication.
Any exception after dialing closes the current raw/TLS socket and propagates the original exception. There is no exception-content inspection or silent fallback. The owner must retain its pending reservation until the call and cleanup finish. The raw attachment allows shutdown to interrupt blocked TLS. A late completed upgrade must attach again and can be rejected. Raw/upgraded views share a device and lock; SSL close releases its native state independently of whether raw close already closed that device. Certificate lookup is also synchronized with closure. Resolver calls remain synchronous: the fixed budget is checked when they return, not a promise of interruptible DNS lookup. The Network owner remains responsible for closing attached sockets, joining workers and retaining reservations through cleanup.
4.8.5.4 Tests
connector-test.ss exercises real Unix and DNS-address TCP/TLS candidates and
then drives both application handshake endpoints through confirmation. It checks
deadline/headroom/input forwarding and bidirectional bytes. DNS fixtures reuse
the listener’s resolved loopback endpoint to avoid randomly selecting a different
address family for the same ephemeral port.
Negative cases cover a certificate with the expected hostname but a different key, expired operations that never dial, and a silent TCP peer causing TLS timeout and transport closure. Driver tests cover real address fallback, both DID orderings, credential retries, repeated DNS pinning through exhaustion, terminal reservation cleanup, exact exception identity, and monitor failures before/after commitment. Additional cases cover atomic attachment versus shutdown, silent TLS interruption before the original deadline, completed-upgrade rejection after raw closure, and terminal attachment callback exceptions with transport-error types. Failure hooks observe the closed transport before reservation retirement.
Credential tests cover initial/default ordering, stable ties, structurally equal
Token copies, lifetime boundaries, additions between choices, close/exhaustion, and
concurrent add versus exhaustion. A real Unix test drives an initial provide!
grant to rejection and then confirms a fresh candidate using the queued parent.
These tests use the production retry driver directly; shared Network.connect!
ownership and Connection publication are outside this suite. Internal election
transitions have their own owner tests.
Adaptive tests exercise both the standalone setup and full driver with target zero, an expired explicit seed, fresh context authority, actual finite selected expiration, and unchanged deadlines. They also cover invalid nonzero adaptive targets before reservation/dialing and fixed-versus-adaptive queue filtering. They are included in the integrated network regression; current build and runtime evidence is in the main-owned checkpoint.
4.8.5.5 Integration Handoff
The public network owner maps new lease: 'renewable setup to target zero and
connector-connect!(..., adaptive?: #t), retaining its original
ConnectorCredentials argument. connect-handshake accepts the same keyword;
run-handshake! remains a driver of the already configured candidate with unchanged
arguments. Incoming constructors need no mode argument because the peer HELLO
chooses it. Setup mode is not persistent automatic-interest state. The
network owner implements serialized policy/seed
attachment and session cutover; the connection engine
owns protected control capacity and renewal scheduling. These are not missing
connector features or additional public exports.