# drive-transport-https spec — v1 ## ADDED Requirements ### Requirement: Implements TransportLike `HttpsTransport` SHALL implement the structural `TransportLike` contract from `@suaveplan/types/transport`: `connect(peerId, address)`, `disconnect(peerId)`, `send(peerId, envelope)`, `onMessage(handler) → Unsubscribe`, `getConnectedPeers()`. It SHALL add lifecycle methods `start()` / `stop()` so the long-poll loop does not begin until the consumer is ready and so the loop can be cleanly torn down on shutdown. **Verified by:** `tests/lifecycle.test.ts` (eleven scenarios). ### Requirement: Long-poll receive with cursor The transport SHALL maintain a string `cursor` initialised to `""`. Every long-poll request SHALL include the cursor as the `since` query parameter when non-empty; on response, the cursor SHALL be replaced with the `nextCursor` field from the parsed `RecvResponse`. **Verified by:** `tests/recv.test.ts`::"includes since=cursor on subsequent /recv calls" AND "cursor starts empty and updates after a successful /recv" (`tests/lifecycle.test.ts`). ### Requirement: Auth-failure short-circuit A `401` or `403` response from `/recv` SHALL terminate the receive loop. The transport's `state` SHALL transition to `"stopped"` and the `lastAuthError` getter SHALL expose the typed `HttpsTransportError(HTTPS_TRANSPORT.AUTH_FAILED, ...)`. The transport SHALL NOT auto-retry on auth failure — credential refresh belongs to the consumer. **Verified by:** `tests/recv.test.ts`::"AUTH_FAILED on /recv stops the loop" AND "AUTH_FAILED on /recv propagates via lastAuthError". ### Requirement: Exponential backoff with equal jitter On a transient receive failure (`5xx` / `HTTPS_TRANSPORT.RECV_FAILED` / `HTTPS_TRANSPORT.MALFORMED_RESPONSE`), the loop SHALL back off and retry. Wait times SHALL follow an equal-jitter formula `exp / 2 + rand(0, exp/2)` clamped to `[backoffBaseMs, backoffMaxMs]` with `exp = backoffBaseMs * 2^(failures-1)`. **Verified by:** `tests/recv.test.ts`::"backs off on 503 and retries", "backs off on 4xx other than auth (RECV_FAILED) and retries", "backs off on malformed JSON body (MALFORMED_RESPONSE) and retries". ### Requirement: Send wire contract `send(peerId, envelope)` SHALL `POST` to `${relayUrl}/send` with `{ to, envelope }` as JSON. The peer SHALL be in the `connected` set or `HTTPS_TRANSPORT.PEER_UNKNOWN` is thrown. `401`/`403` responses SHALL throw `AUTH_FAILED`; `5xx` responses SHALL throw `RELAY_UNAVAILABLE`; other non-2xx responses SHALL throw `SEND_FAILED`. `200` responses SHALL be Zod-validated against `SendResponseSchema`; `ok: false` SHALL throw `SEND_FAILED` with the relay's `code` (or `"unknown"`) in the message. **Verified by:** `tests/send.test.ts` (fifteen scenarios). ### Requirement: Lifecycle invariants `start()` SHALL throw `HTTPS_TRANSPORT.CLOSED` if called after `stop()`. `start()` SHALL be idempotent while `state === "running"`. `stop()` SHALL be idempotent. `connect()` and `send()` SHALL throw `HTTPS_TRANSPORT.NOT_CONNECTED` if the transport has not been started, and `HTTPS_TRANSPORT.CLOSED` if it has been stopped. **Verified by:** `tests/lifecycle.test.ts` and `tests/send.test.ts`.