Files
zooqueen 3730e4b9c9 fix(boot): non-fatal ZAP backend dial — gateway boots with backends absent
Since the HIP-0110 ZAP-relay rearchitecture, cmd/gateway/main's
dialBackends eagerly dialed cloud:9090 / base:9091 at startup and
os.Exit(1)'d on any failure. When those backends are not deployed (the
steady state in prod), every gateway image past v2.14.1 crashlooped —
freezing the gateway on v2.14.1 and blocking the merged CORS fix and the
billing gate. HTTP serving (what api.hanzo.ai actually needs) worked fine.

Decomplect boot from backend reachability:

  - startNode (was dialBackends) only starts the ZAP node. The single
    fatal case left is a genuine local error — failure to bind the
    ephemeral port. No dialing at startup.
  - peerResolver (new, peer_resolver.go) lazily resolves a backend
    address to its handshake-learned peerID, dialing on first use with
    single-flight, caching once connected. ConnectDirectID is idempotent,
    so a healthy backend is dialed exactly once and ZAP is unchanged.
  - NewLazyPicker best-effort warms both backends (failure -> WARN, not
    fatal) and returns the forward.PeerPicker. A dead backend makes the
    picker return "" -> node.Call yields a per-request "peer not found"
    error that forward.Relay surfaces as an HTTP error, never a crash.
    The dial is retried on the next matching request (heals with no
    restart once the backend appears).
  - isBasePath is the single definition of the /v1/base/* split, shared
    by the static pickPeer (tests) and the lazy picker (prod).
  - RelayDeps.Pick lets main pass the lazy picker; nil falls back to the
    static pickPeer for the pre-existing e2e test.

Tests (peer_resolver_test.go): boot succeeds with backends absent (no
hang/crash), healthz still serves 200, picker returns "" for dead
backends, ZAP routes+caches correctly when backends exist, and recovers
when a backend comes up. All pass under -race.
2026-06-19 22:33:58 -07:00
..