feat(maestro): daemon brokers cross-daemon create/kill instead of 501/404 (NAP-327)
The home daemon's `POST /api/sessions` (peer `daemon_id`) and
`DELETE /api/sessions/:id` (peer-owned) now broker the op to the owning daemon
over the registry control link rather than returning 501/404. This fixes
cross-daemon spawn/kill for *all* local clients — notably maestro-web, which
talks only to the local daemon and had no registry fallback.
- broker: `BrokerHandle::{forward_create,forward_kill}` + completion handlers,
mirroring `resolve_attach` (fail-fast while disconnected, RAII pending
cleanup, RESOLVE_TIMEOUT).
- registry_client: route `Forward{Create,Kill}Result` back to the broker.
- local_api: create forwards on peer `daemon_id`; kill looks up the owner in
the catalog mirror and forwards (only a truly-unknown session is 404).
`broker_error_response` maps BrokerError → HTTP (503/504/502/404/...).
- cli: thread the broker into `LocalApiState`.
Tests: broker round-trip / fail-fast / registry-err propagation; updated the
local_api integration test (peer create now 503s without a registry link
instead of 501, proving it brokers).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -34,17 +34,19 @@ async fn spawn(daemon_id: &str, hostname: &str, daemon_url: &str) -> Harness {
|
||||
let manager = fx.manager.clone();
|
||||
let catalog = CatalogMirror::new();
|
||||
let events = LocalEventBus::new();
|
||||
// NAP-247/NAP-327: tests don't exercise the cross-host broker path; hand the
|
||||
// routers an isolated BrokerHandle whose receiver is dropped, so any
|
||||
// resolve_attach / forward_create / forward_kill fails fast with
|
||||
// NoControlLink (the handle starts disconnected).
|
||||
let (broker, _broker_rx) = BrokerHandle::new();
|
||||
let state = local_api::state_for(
|
||||
manager.clone(),
|
||||
hostname,
|
||||
daemon_url,
|
||||
catalog.clone(),
|
||||
events,
|
||||
broker.clone(),
|
||||
);
|
||||
// NAP-247: tests don't exercise the cross-host broker path; hand the
|
||||
// router an isolated BrokerHandle whose receiver is dropped, so any
|
||||
// resolve_attach attempt fails fast with NoControlLink.
|
||||
let (broker, _broker_rx) = BrokerHandle::new();
|
||||
let broker_state = local_api::BrokerState {
|
||||
manager: manager.clone(),
|
||||
catalog: catalog.clone(),
|
||||
@@ -210,9 +212,11 @@ async fn list_sessions_starts_empty_and_reflects_creates() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn post_to_peer_daemon_id_returns_501_not_proxy() {
|
||||
// Cross-daemon broker is NAP-247; until it lands a daemon must refuse
|
||||
// (rather than silently misroute) a create targeting a peer.
|
||||
async fn post_to_peer_daemon_id_brokers_and_503s_without_registry_link() {
|
||||
// Cross-daemon create now brokers to the owner over the registry control
|
||||
// link (NAP-327). This harness has no registry link (the broker starts
|
||||
// disconnected), so the daemon fails fast with a retryable 503 instead of
|
||||
// the old 501 — proving it attempts to broker rather than refusing.
|
||||
let h = spawn("laptop", "naps-laptop", "ws://10.0.0.1:7070").await;
|
||||
let req = CreateSessionRequest {
|
||||
daemon_id: "konishi".into(),
|
||||
@@ -228,9 +232,9 @@ async fn post_to_peer_daemon_id_returns_501_not_proxy() {
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), reqwest::StatusCode::NOT_IMPLEMENTED);
|
||||
assert_eq!(resp.status(), reqwest::StatusCode::SERVICE_UNAVAILABLE);
|
||||
let body: serde_json::Value = resp.json().await.unwrap();
|
||||
assert_eq!(body["error"]["code"], "remote_create_unsupported");
|
||||
assert_eq!(body["error"]["code"], "no_registry_link");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
Reference in New Issue
Block a user