b0030a0482
Addresses 7 issues from Codex P2 + Copilot + CodeRabbit Critical/Major:
1. **TTL enforced BEFORE each attempt** (Codex P2 + Copilot + CodeRabbit Major)
Previously TTL was only checked AFTER a failure, so an attempt could
start with 0ms remaining and run for the full perAttemptTimeoutMs.
Now: check at top of loop. If already exceeded, throw immediately.
2. **Per-attempt timeout capped by remaining TTL** (Copilot)
`attemptTimeoutMs = min(perAttemptTimeoutMs, ttlMs - elapsed)` so a
single attempt cannot run past the wall-clock deadline.
3. **Operation receives AbortSignal for cancellation** (CodeRabbit Critical)
`withTimeout` now aborts an `AbortController` when the timeout fires.
The signal is forwarded to the operation, so callers can opt into
cancelling in-flight work (e.g. `(signal) => fetch({ signal })`).
Without this, a timed-out attempt's network call kept running while
bounded-retry started the next attempt — wasted work + duplicate
listeners.
4. **sleep() removes abort listener on normal completion** (Codex P2 +
Copilot + CodeRabbit)
Previously `signal.addEventListener('abort', onAbort, { once: true })`
was added on every retry but never removed when the timer resolved.
Long-lived signals reused across retries accumulated listeners. Now
explicit cleanup on both timer-resolve and abort paths.
5. **Outer abort listener properly cleaned up in main loop**
Adds + removes via try/finally to prevent leaks when the loop throws.
6. **Metrics use optional chaining** (Copilot)
`metrics.socketEvents?.inc(...)` everywhere, matching the rest of the
codebase pattern (handles partial mocks in tests).
7. **Structured logging** (user request)
New optional `logger?: ILogger` field. Emits:
- debug: each retry scheduling (`{op, attempt, delayMs, elapsedMs, error}`)
- info: recovery after retries (`{op, attempts, elapsedMs}`)
- warn: give-up via TTL or shouldRetry predicate
8. **socket.ts: align uploadPreKeys query timeout with bounded-retry**
`query(node, PER_ATTEMPT_TIMEOUT_MS)` so a stale attempt does not
keep an iq listener registered past bounded-retry's per-attempt
deadline (Copilot).
9. **Doc updated**: removed claim about `maxAttempts` (option doesn't
exist; bounded by ttlMs + delay-cap + per-attempt-timeout).
Tests: 18 pass (was 12; added 6 covering each fix).
Full suite: 35/35 suites, 809/809 tests pass.
NB: The "breaking API change" comments (Types/Socket.ts, Utils/index.ts,
structured-logger.ts) are intentional and not addressed — this fork has
a single consumer; downstream forks merging will absorb the change.