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:
atlas 2026-09-21 21:04:22 +02:00 committed by atlas
commit d94bc2188d
28 changed files with 236 additions and 1513 deletions

View file

@ -12,20 +12,23 @@
const SEND_ALLOW_PATH: &str = "/etc/hyperhive/send-allow.json";
/// Enforce the per-agent send allow-list. Returns `Ok` when the
/// recipient is permitted (no list configured, `<parent>` sentinel
/// always allowed, or `to` is in the list); returns `Err(refusal)`
/// with a claude-readable string when blocked — the harness surfaces
/// the refusal as the tool result so claude knows the message didn't
/// land and can react (e.g. route via `<parent>` instead).
/// recipient is permitted (no list configured, the operator, or `to` is
/// in the list); returns `Err(refusal)` with a claude-readable string
/// when blocked — the harness surfaces the refusal as the tool result so
/// claude knows the message didn't land and can react (e.g. route to the
/// operator instead).
pub fn check_send_allowed(to: &str) -> Result<(), String> {
if to == hive_sh4re::manager::PARENT_RECIPIENT {
// Always allow `<parent>` — the allow-list constrains peer
// chatter, not the structural reporting line; the operator
// can rewire who the parent IS via `set_parent` without
// having to remember to update the per-agent allow-list.
// The broker resolves the sentinel to the real parent label
// on the host side per topology.json (falls back to `operator`
// for root agents).
if to == hive_sh4re::manager::OPERATOR_RECIPIENT {
// Always allow the operator — the allow-list constrains peer
// chatter, not the reporting line out, and an agent with no way
// to say "I am stuck" is an agent that fails silently.
//
// This bypass used to be spelled `<parent>`, which the broker
// resolved per `topology.json` and which fell back to `operator`
// for a root agent. #4472 removed the parent field, so every
// agent is what that fallback called a root — the exemption is
// now written as the name it always resolved to. Same reachable
// set, one fewer indirection.
return Ok(());
}
let Ok(raw) = std::fs::read_to_string(SEND_ALLOW_PATH) else {
@ -50,9 +53,9 @@ pub fn check_send_allowed(to: &str) -> Result<(), String> {
}
Err(format!(
"send refused: recipient '{to}' not in services.hyperhive.agent.allowedRecipients \
(configured in agent.nix). Allowed: {allow:?}. Your structural \
parent is always reachable route through `send(to: \"{}\", …)` \
if you need to reach someone outside the allow-list.",
hive_sh4re::manager::PARENT_RECIPIENT
(configured in agent.nix). Allowed: {allow:?}. The operator is always \
reachable route through `send(to: \"{}\", …)` if you need to reach \
someone outside the allow-list.",
hive_sh4re::manager::OPERATOR_RECIPIENT
))
}