topology: drop the parent field and the hierarchy it fed
`topology.json` was a map of `name -> parent | null`, and that value fed the whole agent hierarchy: `<parent>` / `<children>` recipient sentinels, the reparenting API (CLI verb, wire verb, dashboard endpoints, DAG node), the dashboard tree, the rebuild depth sort, and an unconditional bind-mount grant giving every agent RW on its direct children's state. Per the operator's ruling the field goes, and with it all of the above. The file survives as what remains once the value is gone: the roster of agent names, which is the set `ManageRootAgent` grants mounts over. It is now a JSON array; `read` still accepts the old map shape and keeps its keys, so a hive that upgrades across this does not blank its roster (and so no capability holder loses its mounts for the length of that window). Two sites kept their behaviour under a different recipient rather than losing it. Both addressed `<parent>`, which the broker already resolved to `operator` for a root agent, and every agent is now what that fallback called a root: - the harness's turn-failure / plugin-failure notification (`Surface::send_to_parent` -> `send_to_operator`), and - the send allow-list's always-permitted escape hatch, so an agent with a restrictive allow-list still has a way to say it is stuck. What is NOT preserved, deliberately: an agent with no capability no longer sees any other agent's dirs. `ManageRootAgent`'s own grant is unchanged -- still every agent in the roster, still state RW + config RO, still no `harness`. The dashboard's reparenting control (the M0V3 picker) is deleted with its CSS. The tree rendering that reads `ContainerView.parent` is left for the frontend owner -- it degrades to a flat list with the field gone.
This commit is contained in:
parent
392f16cbc0
commit
d94bc2188d
28 changed files with 236 additions and 1513 deletions
|
|
@ -712,36 +712,9 @@ fn check_can_cancel_approval(canceller: &str) -> Result<(), String> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Fan out one message to each recipient in `targets`. Skips the sender
|
||||
/// itself. Returns a list of `"<agent>: <error>"` strings for any delivery
|
||||
/// failures (empty = all good).
|
||||
pub(crate) fn fan_out_send(
|
||||
coord: &Arc<Coordinator>,
|
||||
from: &str,
|
||||
body: &str,
|
||||
in_reply_to: Option<i64>,
|
||||
targets: &[String],
|
||||
) -> Vec<String> {
|
||||
let mut errors = Vec::new();
|
||||
for target in targets {
|
||||
if target == from {
|
||||
continue;
|
||||
}
|
||||
if let Err(e) = coord.broker.send(&Message {
|
||||
from: hive_sh4re::manager::trusted_sender(from),
|
||||
to: target.clone(),
|
||||
body: body.to_owned(),
|
||||
in_reply_to,
|
||||
}) {
|
||||
errors.push(format!("{target}: {e}"));
|
||||
}
|
||||
}
|
||||
errors
|
||||
}
|
||||
|
||||
/// Common Send handler shared between dispatch arms. Applies the
|
||||
/// 4 KiB body cap, then routes broadcast (`to == "*"`) / children fan-out
|
||||
/// (`to == "<children>"`) / unicast through their respective broker calls.
|
||||
/// 4 KiB body cap, then routes broadcast (`to == "*"`) / unicast through
|
||||
/// their respective broker calls.
|
||||
/// `pub(crate)` so `dispatch_shared` can use it across both socket paths.
|
||||
pub(crate) fn handle_send(
|
||||
coord: &Arc<Coordinator>,
|
||||
|
|
@ -763,50 +736,30 @@ pub(crate) fn handle_send(
|
|||
}
|
||||
};
|
||||
}
|
||||
// `<children>`: fan out to every direct descendant of the sender per
|
||||
// topology.json. Bypasses the allow-list check — structural fan-out
|
||||
// targets are never user-listed peers. No-op (returns Ok) for leaf
|
||||
// agents that have no children.
|
||||
if to == hive_sh4re::manager::CHILDREN_RECIPIENT {
|
||||
let children = crate::topology::children_of(agent);
|
||||
let errors = fan_out_send(coord, agent, body, in_reply_to, &children);
|
||||
return if errors.is_empty() {
|
||||
Response::Ok
|
||||
} else {
|
||||
Response::Err {
|
||||
message: format!("children fan-out failed for agents: {}", errors.join(", ")),
|
||||
}
|
||||
};
|
||||
}
|
||||
// Resolve magic-recipient sentinels (`<parent>`) against topology.json;
|
||||
// no-op for ordinary names. Lets agents address structural roles without
|
||||
// learning the label — runtime reparenting propagates for free. See
|
||||
// `docs/process/conventions.md::Recipient sentinels`.
|
||||
let resolved = crate::topology::resolve_recipient(agent, to);
|
||||
// Validate that the resolved recipient is a known local agent or the
|
||||
// Validate that the recipient is a known local agent or the
|
||||
// special "operator" recipient. Without this check a typo in `to`
|
||||
// silently queues a message nobody will ever read.
|
||||
//
|
||||
// Cross-hive messaging (`name@hive` qualified names) is not routed
|
||||
// through the broker — use the Matrix MCP tools for that instead.
|
||||
if resolved.contains('@') {
|
||||
if to.contains('@') {
|
||||
return Response::Err {
|
||||
message: format!(
|
||||
"send failed: cross-hive recipient `{resolved}` is not supported \
|
||||
"send failed: cross-hive recipient `{to}` is not supported \
|
||||
via the broker — use Matrix MCP tools for cross-hive messaging"
|
||||
),
|
||||
};
|
||||
}
|
||||
if resolved != hive_sh4re::manager::OPERATOR_RECIPIENT {
|
||||
if to != hive_sh4re::manager::OPERATOR_RECIPIENT {
|
||||
// A name that doesn't parse as an Ident can't be a local agent, so
|
||||
// it collapses into the same "unknown recipient" error as a valid
|
||||
// name with no state dir.
|
||||
let exists = hive_types::Ident::parse(&resolved)
|
||||
let exists = hive_types::Ident::parse(to)
|
||||
.is_ok_and(|id| crate::paths::agent_state_dir(&id).exists());
|
||||
if !exists {
|
||||
return Response::Err {
|
||||
message: format!(
|
||||
"send failed: unknown recipient `{resolved}` \
|
||||
"send failed: unknown recipient `{to}` \
|
||||
(no agent with that name exists on this hive)"
|
||||
),
|
||||
};
|
||||
|
|
@ -814,7 +767,7 @@ pub(crate) fn handle_send(
|
|||
}
|
||||
match coord.broker.send(&Message {
|
||||
from: hive_sh4re::manager::trusted_sender(agent),
|
||||
to: resolved,
|
||||
to: to.to_owned(),
|
||||
body: body.to_owned(),
|
||||
in_reply_to,
|
||||
}) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue