# erldistpy roadmap Phases below are sized to land as discrete commits. Each phase ends with `make all` green and a real-VM interop test where applicable. ## Phase 0 — Repo bones ✅ - LICENSE, README, .gitignore, Makefile, pyproject.toml - Package skeleton, ruff config matches unfeed - `make bootstrap test lint` works on a fresh checkout ## Phase 1 — ETF codec ✅ - Subset of External Term Format we need for gen_call to Elixir: small_int, int, big_int, atom_utf8 (legacy atom_ext on decode), binary, nil, list, tuple (small + large), pid (new_pid), ref (newer_reference) - Booleans and `None` ride as atoms `true` / `false` / `nil` - Golden vectors decoded from real `term_to_binary/1` output - Round-trip tests for the encoder - Generator script committed at `docs/etf_vectors.erl` ## Phase 2 — EPMD client EPMD (Erlang Port Mapper Daemon) maps node names to TCP ports. Tiny synchronous TCP client. - Connect `epmd_host:4369` (configurable) - `PORT_PLEASE2_REQ` (113) → port + dist version range - Return `EpmdNodeInfo(port, lo_ver, hi_ver, node_type, proto)` - Tests: live EPMD on localhost (skip if not running), unit tests against a recorded byte stream ## Phase 3 — Distribution handshake - TCP connect to the resolved port - `send_name` / `recv_status` / `recv_challenge` / `send_challenge_reply` / `recv_challenge_ack` - Cookie digest via `erlang:phash2`-equivalent (md5-based per spec) - Version 6 ("v6") handshake, the modern one Elixir 1.15+ uses - Tests: handshake against a live Erlang node started in conftest ## Phase 4 — Distribution channel - After handshake, the socket carries control + payload messages framed by a 4-byte length prefix - Send `SEND_TT` / `REG_SEND` for outgoing messages - Receive replies, route by ref - Tick loop for keepalive (60s default per OTP) ## Phase 5 — gen_call convenience layer - `Node.call(name_or_pid, request, timeout=5.0)` → reply term - Wraps the `$gen_call` protocol used by `:gen_server` - Idempotency-key handling lives in the caller; we just pass the term ## Phase 6 — TLS dist - Wrap the post-EPMD socket in TLS - Match `inet_tls_dist` config on the Erlang side (cert + key + ca paths) - Same handshake, just runs inside the TLS tunnel ## Phase 7 — unfeed integration - `ErlangDistTransport` implementing unfeed's `WalletTransport` Protocol - Maps `/v1/health`, `/v1/address`, ... to gen_call requests - `unfeed` deploys with a config switch: `wallet_transport = http | erldist` - Cutover after a soak period on staging