# releases#305 design read — "webserver code seems to live in the broker"

doyle, hfenduleam, 2026-09-24. Read at main `a1f4901e`. Ruling for the #331 rider wave.

## The complaint is structurally correct, not a refresh bug

- Serving is started **inside the daemon process at boot**: `crates/spt-daemon/src/daemon.rs:273-332`,
  `docshost::start_serving_with_broker(home, docs_root, node, port, Some(broker_socket_name()))`.
  The comment at `:273` states the intent — *"Node-local HTTP serving stays broker-owned and
  loopback-only"* (`REQ-DOCS-LOCAL-SERVER`, `REQ-WEB-URL-NODE-PREFIX`, `REQ-WEB-SERVING-REGISTRY`).
- `spt node refresh` is `applyhost::refresh_brain` (`applyhost.rs:358-375`, `REQ-DAEMON-REFRESH`):
  it raises the supervisor brain-restart signal — **brain stop → respawn → readiness trial →
  promote**, and the doc comment says outright *"broker + every hosted PTY untouched by
  construction"* and *"the respawn lands on the SAME on-disk binary"*.

So refresh re-executes only the **brain** child. The serving code runs in the broker image, which
refresh never replaces. No amount of work inside the refresh verb can make broker-resident code
new — only a process re-spawned from the on-disk exe runs new bytes. **Any code that must be
refresh-evident has to live in the brain.**

## Fix shape (the only one that delivers the ask)

Relocate the HTTP listener + request handling (`docshost::start_serving*`, `webserve::handle_path`)
into the **brain** child; leave the broker as the cross-node proxy dial target.

Two properties make this cheaper than it looks:

1. **Already address-compatible.** Serving reaches the broker by *socket name*
   (`broker_socket_name()`), not an in-process handle, so the proxy arm
   (`REQ-WEB-CROSS-NODE-PROXY`) keeps working verbatim from another process.
2. **No bind overlap.** The supervisor **kills the current brain child and then respawns it**
   (`brainproc.rs:180`); there is no live-old/live-new window, so a brain-held listener never
   fights itself for the port. Refresh costs a sub-second loopback gap — HTTP serving here is
   stateless and holds no PTY.

## Watch-outs the lane must carry (none is a blocker; all are diffs)

- `docshost::BoundDocsPort` is a **process global**. `servehost.rs:137` answers
  `ServeResult::DocsStatus { port: docshost::bound_docs_port() }` from the broker — that read must
  move with the listener or cross the socket. Reading it in-broker after the move returns a
  confident wrong zero.
- `lanhost::capture_canonical_exe()` is deliberately called at **t=0 in the broker**, before
  anything can apply an update and move the exe (KH 6.11, `REQ-WEB-LAN-BOOTSTRAP-INTEGRITY`). It
  is *adjacent* to the serving block, not part of it. **It stays in the broker.**
- `servehost::reconcile_at(&home, now_ms)` runs on the same boot path; decide explicitly whether
  the registry reconcile is a broker-boot duty (keep) or a serving duty (move).
- **Availability coupling — the one real regression surface.** Today docs serving survives a brain
  crash. After the move it does not: a brain in rollback takes loopback serving with it. Judged
  acceptable (the brain is supervised with a readiness trial + auto-rollback, and serving is
  stateless), but it is a behaviour change and belongs in the REQ text, not in a comment.
- The `:273` comment and the `REQ-DOCS-LOCAL-SERVER` / `REQ-WEB-*` doc stages **assert** broker
  ownership. This is a contract amendment, so the doc stage is re-tagged in the same change and
  the docs-drift gate runs — not a silent relocation.

## Rejected alternatives

- *Restart the serving thread on refresh.* Same process ⇒ same bytes. Cannot deliver the ask.
- *Re-exec the whole daemon on refresh.* Kills the broker and every hosted PTY — the exact thing
  seamless updates exist to prevent.
- *A third "webhost" child process.* Delivers the ask, but adds a supervised lifecycle we already
  have in the brain, for no property the brain lacks. Only revisit if the availability coupling
  above turns out to matter in the field.

## Verdict

#305 is a **relocation**, not a refresh fix. Sized as a single build lane (todlando), with the REQ
text carrying the availability-coupling change and the doc-stage re-tag. The rig is a field-shaped
one: change a served byte, `spt node refresh`, read the served byte — red at base by construction.
