# M4-D4 — broker QUIC ownership (JIT plan)

**Status:** not started. D3 complete (REQ-INST-7,9,10,11,12,13 + REQ-HAZARD-REGISTRY-EPOCH-LEASE
all `[impl,unit]`, CI-green at `2766898`). D4 makes Spike #3's validated *shape* real: the
**broker owns the Iroh endpoint and every live QUIC stream** (ADR-0004 §B ownership table),
so a brain restart never interrupts a cross-node stream — the routine *brain-only* update
class ADR-0004 §A must preserve.

## Goal

The daemon's broker kernel holds the node's `NetEndpoint` (iroh) and all accepted/dialed
QUIC connections + streams. The brain attaches over the existing versioned IPC, drives net
operations through new net frames, detaches/reattaches freely. A live cross-node QUIC
stream (loopback two-daemon in CI; two-host at D9) survives a brain kill/restart **gapless
and exactly-once** — composing the M3b handoff substrate (seq + cursor resume + effect
journal) with the network surface. PresenceChannel gets its day-one broker seam (REQ-EP-4).

## Decisions (locked)

- **Runtime placement:** the broker hosts a **dedicated tokio runtime** for the Iroh
  endpoint (iroh requires tokio; the broker is otherwise sync/thread-based). The runtime
  lives on a named broker thread; QUIC objects never leave it. Bridge to the sync broker
  core via the same channel/lock discipline the PTY side uses. (Spike #3's broker binary
  proved exactly this shape.)
- **Net frames ride the existing IPC catalog** (`codec`/`frame`/`msg`): new kinds for
  endpoint-status, dial, stream-open, stream-send, stream-subscribe (cursor-resumed, like
  PTY output), peer-event. Same `Envelope` versioning — a newer brain talks to an older
  broker; unknown kinds are not a decode error (the `frame.rs` invariant already tested).
- **Exactly-once discipline extends to net side effects** (REQ-HAZARD-RESTART-IDEMPOTENT):
  side-effectful net ops (dial, stream-open, send) carry the durable `op_id` and dedup at
  the effect through the existing `EffectJournal`. Stream *reads* to the brain reuse the
  seq+ring+cursor-resume pattern (`OutputLog` generalized or mirrored for net streams).
- **mDNS rides the endpoint** (§B open row): `MdnsAddressLookup` is constructed inside the
  iroh endpoint, so broker ownership of the endpoint closes the "mDNS socket" row by
  construction. Note it in the ownership table when D4a lands. The *relay session* row
  closes the same way (iroh owns it inside the endpoint).
- **Registry replication seam (D4d):** inbound wire updates feed D3's existing
  `merge_instance` seam — a minimal registry-update message over `SPT_NET_ALPN` carrying
  `(subnet, endpoint_id, Instance{node,status,epoch})`, authored with `EpochSource::
  next_epoch()`. The merge rule is already hermetically tested; D4d only wires transport →
  merge. Join/advertise paths go through D3d/D3e gates (`advertise_if_visible`).
- **PresenceChannel = seam, not feature** (REQ-EP-4): a broker-side endpoint registration
  surface (subscribe presence-interest, emit presence events on peer connect/disconnect)
  with a minimal v1 consumer — enough that the deferred PresenceChannel generalization
  (ADR-0004 consequences) has a stable attach point. No dispatch UX.

## Pieces (build order)

1. **D4a — broker-owned endpoint** (ADR-0004 §B). Move `NetEndpoint` construction into the
   broker (tokio runtime thread); brain requests endpoint-status/dial over new IPC frames;
   accepted connections live in a broker-side conn table keyed like sessions. The
   `NetEndpoint::iroh` escape hatch is the take-ownership hook the endpoint module already
   documents.
2. **D4b — gapless stream survival**. QUIC stream reads buffered in a seq'd ring (the
   OutputLog pattern); brain subscribes with cursor; kill/restart brain mid-stream in a
   loopback two-daemon test → no gap, no dup (the Spike #3 invariants A–D as a real unit/
   int test). Send path journaled exactly-once.
3. **D4c — PresenceChannel broker seam** (REQ-EP-4). Peer connect/disconnect events surface
   to the brain as presence frames; minimal subscribe surface.
4. **D4d — registry replication over the wire**. Registry-update messages feed
   `merge_instance`/`advertise_if_visible`; stamped from `EpochSource`. Loopback test: two
   daemons converge a registry row; stale-epoch update dropped (lease holds over the wire).
5. **Fault-injection matrix start** (ADR-0002 SERIOUS #6, tracked): crash/hang the iroh
   task, mDNS, registry feed independently — begin the acceptance matrix doc.

## THIS slice — D4a (broker-owned endpoint + IPC net frames)

- Broker: tokio runtime thread hosting `NetEndpoint` (RelayPolicy from config; disabled in
  hermetic tests), conn table, endpoint-status + dial frames.
- Reqs: activate the ADR-0004 §B ownership rows as impl evidence under the existing
  daemon/hazard reqs (`REQ-DAEMON-*`, `REQ-HAZARD-RESTART-IDEMPOTENT` where ops journal);
  REQ-EP-4 activates at D4c; check whether a new `REQ-NET-*` activation is owed (M4-PLAN
  lists D4 under ADR-0004 §B + REQ-EP-4 only).
- Tests: endpoint constructed broker-side with relay disabled; status frame roundtrips;
  dial to a second in-process broker endpoint over loopback; unknown-net-frame tolerance.

## NOT in D4 (this milestone)
- WAN message delivery / remote-drive / access control — D5 (REQ-NET-1, REQ-INST-8,
  REQ-REACH-1, REQ-SEC-1).
- Psyche-context sync over P2P — D6 (consumes D3e's `synced` gate).
- Two-host integration — D9 (loopback shapes only here).
- Broker *binary* hot-swap (broker-compatible update class) — separate track, not D4.

## Conventions (carried)
- NO `cargo fmt`. Tag `[impl->REQ-…]` / `[unit->REQ-…]` on real evidence. `traceable-reqs
  check` from repo root before done. Local clippy can't see `#[cfg]` arms — Linux CI clippy
  `-D warnings` is the real gate. Push each slice → `gh run watch` green before next.
  Commit trailer: `Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>`.
