Each daemon connection now also subscribes to the registry's event broadcast.
A per-connection task pumps peer-filtered events through the existing
outbound mpsc as `CatalogEvent` frames; the daemon side mirrors them
locally (NAP-246).
Two pre-existing race fixes that NAP-246's extra traffic exposed:
* `create_session` / `kill_session` now eagerly mirror Ok results into
state.sessions (via the same `add_session` / `remove_session` calls the
daemon's async `SessionAdded` / `SessionRemoved` will hit later — both
are idempotent). HTTP callers no longer see a transient empty catalog
immediately after a successful POST.
* Catalog frames flow through the same select! arm as RPC frames so the
WS sink+stream coupling axum imposes doesn't stall the inbound reader.
Updates daemon_ws.rs tests to skip CatalogEvent frames when asserting on
RPC frames.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the client-facing surface on the registry (NAP-215). Endpoints:
- GET /api/daemons — list connected daemons (with daemon_url)
- GET /api/sessions — flat session catalog
- POST /api/sessions — forwards CreateSession to chosen daemon, 504 on
10s daemon timeout
- DELETE /api/sessions/:id — forwards KillSession
- GET /api/sessions/:id/connect-info — returns {daemon_url, jwt} with a
5min HS256-signed attach JWT
- WS /api/events — snapshot + delta broadcast of catalog and presence
- GET / placeholder served until NAP-218 web bundle lands
Cookie auth middleware mints a long-lived httpOnly JWT cookie on first
request and reissues silently on missing/tampered cookies. Per spec the
perimeter is the operator's network — the cookie is a second factor.
Protocol additions: daemon_url on Hello, DaemonInfo, ConnectInfo,
RegistryEvent. Daemon config + RegistryClient now carry daemon_url.
Registry state now exposes an event broadcast channel (subscribe gives
a snapshot + tokio::sync::broadcast::Receiver) and helper lookups
daemon_for_session / daemon_url.
Tests: 11 reqwest-driven HTTP integration tests cover every endpoint,
the cookie flow, 504 timeout path, worktree forwarding, and events WS
propagation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Registry-side primitives for the daemon↔registry conversation. No
client-facing HTTP yet (that's NAP-215).
- `state::Registry`: in-memory store of connected daemons + a session
catalog keyed by (daemon_id, session_id). Tracks pending CreateSession
/ KillSession RPCs so a single daemon connection can multiplex them.
- `handler::handle_socket`: per-connection loop that authenticates the
Hello, applies snapshot/diff/heartbeat frames, and resolves matching
*Result frames. Heartbeat watchdog is a `tokio::time::timeout` around
the read side — no separate janitor task.
- `server::router_with_timeout`: axum router mounting `WS /daemon`. The
timeout knob lets tests verify the 30s drop path in milliseconds.
- `token::ensure_token`: first-run bootstrap that generates a 256-bit
hex token, prints it once to stderr, and persists it to
`~/.config/maestro/registry.toml`.
- Outbound `create_session` / `kill_session` exposed as the internal
API NAP-215's HTTP handlers will call.
Integration tests use a real axum server bound to `:0` and drive it via
`tokio-tungstenite`, covering: bad-token reject, snapshot accept, diff
apply (add/update/remove), heartbeat timeout dropping the daemon,
re-registration replacing the catalog, and a full CreateSession RPC
round-trip.
Closes NAP-213.