broker: resolve <parent> sentinel to topology parent at send time (#692)
This commit is contained in:
parent
1a9d940a4a
commit
7142e95c8f
7 changed files with 154 additions and 8 deletions
|
|
@ -328,9 +328,14 @@ fn handle_send(
|
|||
}
|
||||
};
|
||||
}
|
||||
// Resolve magic-recipient sentinels (currently `<parent>`) against
|
||||
// topology.json; no-op for ordinary names. Lets agents address
|
||||
// structural roles without learning the label — runtime reparenting
|
||||
// (#486) propagates for free (#692).
|
||||
let resolved = crate::topology::resolve_recipient(agent, to);
|
||||
match coord.broker.send(&Message {
|
||||
from: agent.to_owned(),
|
||||
to: to.to_owned(),
|
||||
to: resolved,
|
||||
body: body.to_owned(),
|
||||
in_reply_to,
|
||||
}) {
|
||||
|
|
|
|||
|
|
@ -113,9 +113,15 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc<Coordinator>) -> ManagerResp
|
|||
}
|
||||
}
|
||||
} else {
|
||||
// Resolve magic-recipient sentinels (currently `<parent>`)
|
||||
// against topology.json; no-op for ordinary names. The
|
||||
// manager has no parent in topology, so `<parent>`
|
||||
// resolves to OPERATOR_RECIPIENT — matching mara's
|
||||
// "no parent → tell the operator" rule (#692).
|
||||
let resolved = crate::topology::resolve_recipient(MANAGER_AGENT, to);
|
||||
match coord.broker.send(&Message {
|
||||
from: MANAGER_AGENT.to_owned(),
|
||||
to: to.clone(),
|
||||
to: resolved,
|
||||
body: body.clone(),
|
||||
in_reply_to: *in_reply_to,
|
||||
}) {
|
||||
|
|
|
|||
|
|
@ -58,15 +58,51 @@ pub fn read() -> BTreeMap<String, Option<String>> {
|
|||
/// or absent from the file. Cheap convenience over `read()` for
|
||||
/// callers that want a single entry.
|
||||
#[must_use]
|
||||
#[allow(
|
||||
dead_code,
|
||||
reason = "convenience API; callers go through `read()` today, kept for the \
|
||||
dashboard/manager-server surfaces landing in #361 follow-ups"
|
||||
)]
|
||||
pub fn parent_of(name: &str) -> Option<String> {
|
||||
read().get(name).cloned().flatten()
|
||||
}
|
||||
|
||||
/// Resolve a magic recipient sentinel (currently just
|
||||
/// [`hive_sh4re::PARENT_RECIPIENT`]) to a real broker recipient at
|
||||
/// send time. Lets agents address structural roles
|
||||
/// (`send(to: "<parent>", ...)`) without learning their parent's
|
||||
/// label — runtime reparenting (#486) propagates for free.
|
||||
///
|
||||
/// Resolution rules:
|
||||
/// - `<parent>` → `topology.json`'s parent for `sender` if some,
|
||||
/// else [`hive_sh4re::OPERATOR_RECIPIENT`] (the "no parent → tell
|
||||
/// mara" fallback from #692 / #8592).
|
||||
/// - Anything else: returned unchanged.
|
||||
///
|
||||
/// Returns an owned `String` for the rewritten recipient so callers
|
||||
/// can plug it straight into [`crate::broker::Broker::send`] without
|
||||
/// borrow-juggling around the temporary lookup. Cheap — the resolved
|
||||
/// path clones twice in the worst case (parent name + return), no-op
|
||||
/// in the common case (recipient already a real label).
|
||||
#[must_use]
|
||||
pub fn resolve_recipient(sender: &str, to: &str) -> String {
|
||||
resolve_recipient_in(&read(), sender, to)
|
||||
}
|
||||
|
||||
/// Pure form of [`resolve_recipient`] taking the topology map
|
||||
/// explicitly. Split out so unit tests can exercise the sentinel
|
||||
/// rules without writing a `topology.json` to disk.
|
||||
#[must_use]
|
||||
pub fn resolve_recipient_in(
|
||||
topo: &BTreeMap<String, Option<String>>,
|
||||
sender: &str,
|
||||
to: &str,
|
||||
) -> String {
|
||||
if to == hive_sh4re::PARENT_RECIPIENT {
|
||||
topo.get(sender)
|
||||
.cloned()
|
||||
.flatten()
|
||||
.unwrap_or_else(|| hive_sh4re::OPERATOR_RECIPIENT.to_owned())
|
||||
} else {
|
||||
to.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
/// True when `candidate` is `ancestor` or any descendant of
|
||||
/// `ancestor` per the current topology. Walks parents from
|
||||
/// `candidate` upward; the walk terminates at root or on a cycle
|
||||
|
|
@ -351,4 +387,61 @@ mod tests {
|
|||
let next = apply_set_parent(&topo_three_level(), "bob", Some("alice")).unwrap();
|
||||
assert_eq!(next, topo_three_level());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_recipient_passes_through_ordinary_names() {
|
||||
let topo = topo_three_level();
|
||||
// Real labels, broadcast, and the operator literal all
|
||||
// shortcut through unchanged — no resolution magic.
|
||||
assert_eq!(resolve_recipient_in(&topo, "bob", "alice"), "alice");
|
||||
assert_eq!(resolve_recipient_in(&topo, "bob", "*"), "*");
|
||||
assert_eq!(
|
||||
resolve_recipient_in(&topo, "bob", hive_sh4re::OPERATOR_RECIPIENT),
|
||||
hive_sh4re::OPERATOR_RECIPIENT
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_recipient_rewrites_parent_sentinel_to_parent_label() {
|
||||
let topo = topo_three_level();
|
||||
// bob's parent is alice → `<parent>` from bob goes to alice.
|
||||
assert_eq!(
|
||||
resolve_recipient_in(&topo, "bob", hive_sh4re::PARENT_RECIPIENT),
|
||||
"alice"
|
||||
);
|
||||
// alice's parent is the manager — same one-hop rewrite.
|
||||
assert_eq!(
|
||||
resolve_recipient_in(&topo, "alice", hive_sh4re::PARENT_RECIPIENT),
|
||||
crate::lifecycle::MANAGER_NAME
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_recipient_falls_back_to_operator_for_root_agent() {
|
||||
let topo = topo_three_level();
|
||||
// Manager is structurally root (parent = None) → `<parent>`
|
||||
// resolves to the operator (matching the "no parent → tell mara"
|
||||
// rule from #692#issuecomment-8592).
|
||||
assert_eq!(
|
||||
resolve_recipient_in(
|
||||
&topo,
|
||||
crate::lifecycle::MANAGER_NAME,
|
||||
hive_sh4re::PARENT_RECIPIENT
|
||||
),
|
||||
hive_sh4re::OPERATOR_RECIPIENT
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_recipient_falls_back_to_operator_for_unknown_sender() {
|
||||
// Sender absent from topology entirely — defensive fallback
|
||||
// covers the race window where an agent's spawn has registered
|
||||
// its socket but the meta-flake `sync_agents` hasn't yet added
|
||||
// its row.
|
||||
let topo = topo_three_level();
|
||||
assert_eq!(
|
||||
resolve_recipient_in(&topo, "nobody", hive_sh4re::PARENT_RECIPIENT),
|
||||
hive_sh4re::OPERATOR_RECIPIENT
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue