set_role previously removed empty entries after revoke, causing
reconcile_roles to re-seed the role on the next tick (absent key =
never seen = seed). Fix: never remove empty entries; an empty list
is a tombstone meaning "explicitly revoked".
Also adds unit tests for has_role_in, set_role revoke semantics, and
the reconcile_roles seed/no-seed distinction (pure in-memory, no disk).
Removes the MANAGER_NAME special-case from set_nspawn_flags in favour of
two general mechanisms:
1. Topology-driven child mounts: every agent now gets its direct children's
state, harness, and config dirs bind-mounted (RW). Root's children are
the top-level agents, so root gets the same access it did before via the
old /agents blob bind — but derived from topology, not a hardcoded name
check.
2. can_manage_top_level_agents role: agents holding this role additionally
get every top-level agent treated as a virtual child (same RW mounts)
plus /applied and /meta as RO. Designed for recovery: a role holder can
update a top-level agent's config even when that agent is down.
Root receives this role by default on first reconcile_roles call.
Operator can revoke it with set_role.
Every agent (including root) now gets its own state/harness/config dirs via
the standard path. Roles are stored in meta/roles.json (same dir as
topology.json); reconcile_roles is called from reconcile so both files stay
in sync.
Per mara feedback: don't show disabled actions at all.
- restart/stop only rendered when agent is running
- start only rendered when agent is stopped
- removed .agent-menu-item:disabled CSS rule (no longer needed)
Also switch Escape handler to stopImmediatePropagation so the
selection-clear keydown listener doesn't co-fire when closing a menu.
Each agent card in the SW4RM tab now has a ⋮ button that reveals a
dropdown with single-agent lifecycle actions: R3ST4RT, ST0P, ST4RT,
R3BU1LD, DESTR0Y, PURG3. Items are state-aware (disabled when action
doesn't apply). The button is CSS-invisible until the row is hovered
or the menu is open — quiet rows stay clean. DESTR0Y and PURG3 are
hidden for the manager container.
Click-outside and Escape close the menu. A tree-rebuild (on any SSE
state update) also closes it to avoid stale DOM references.
All functional renames (MANAGER_NAME, MANAGER_AGENT → "root") were done in
earlier commits. This cleans up the stale `hm1nd` strings that remained in
doc comments, test fixture labels, and the manager MCP server instructions
(which told the manager its config lived at /agents/hm1nd/config/agent.nix).
nginx forbids a URI part on proxy_pass inside named locations, so the
split-mode `@<name>_dynamic` fallback (introduced when the gateway
started serving static dist directly) failed config-test with:
"proxy_pass" cannot have URI part in location given by regular
expression, or inside named location
Render two upstream forms: prefix locations keep the trailing slash so
nginx auto-strips the location prefix; the named-location path strips
`/agent/<name>/` via `rewrite ... break` and uses a bare upstream.
Without [actions] ENABLED = true in forgejo's config, the
GET /api/v1/admin/runners/registration-token endpoint returns
"runner registration token not found" regardless of token scopes,
causing hive-ci-register.service to fail on first boot.
`@hyperhive` (no dot) is rejected by Forgejo's PATCH /admin/users API
with 422 Unprocessable Entity. Switch to `@hyperhive.local` across all
four files that construct hive-c0re/agent git emails and Forgejo account
emails, so both stay in sync and pass RFC-valid domain validation.
BindMount was imported at line 10 via the priv_proto glob-style import,
then re-exported again with `pub use` at line 69. E0252: two definitions
of BindMount in the same type namespace. Remove the redundant pub use —
callers that need BindMount should import from hive_sh4re::priv_proto.
listenfd was declared as a direct dep in hive-c0re/Cargo.toml but was
never resolved into Cargo.lock. The nix crane build vendors deps from
the lock, so the missing entry caused a build failure.
Three post-rename artifacts:
1. journalUnit: was 'hive-m1nd.service' for the manager branch.
Both roles use 'hive-ag3nt.service' since the harness unification —
always use 'hive-ag3nt.service'.
2. buildTargetChips: hardcoded ['operator', 'manager'] candidates +
filter by n !== 'manager'. After the rename the manager's c.name
is 'root', so it was excluded from the filter but not from the
hardcoded initial list — the manager could appear twice or be listed
as 'manager' when its real broker name is 'root'. Fix: derive
the manager's name from c.is_manager on containersState; filter
sub-agents by !c.is_manager rather than by hardcoded string.
3. schedulesTableAgentSet: same hardcoded 'manager' + filter pattern
as buildTargetChips. Same fix applied.
gateway_nginx.rs reads HIVE_AGENT_FRONTEND_DIR (injected by hive-c0re.nix
as ${cfg.frontend}/agent). When set, agents.conf emits per-agent split
blocks instead of the old single proxy_pass:
# Compiled assets — immutable nix store path, cache 1y
location ^~ /agent/<name>/static/ {
alias <frontend>/static/;
expires 1y; add_header Cache-Control "public, immutable, ...";
}
# Static dist + proxy fallback
location /agent/<name>/ {
alias <frontend>/;
try_files $uri $uri.html $uri/index.html @<name>_dynamic;
}
location @<name>_dynamic {
proxy_pass <upstream>; # api, events, icon, login, …
…proxy headers unchanged…
}
try_files path resolution (nginx applies alias mapping first):
$uri — exact file (/static/app.js → static/app.js)
$uri.html — bare-path fallback (/stats → stats.html)
$uri/index.html — directory index (/ → index.html)
@<name>_dynamic — proxy catchall for anything not in the dist
Adding pages to the frontend dist works automatically — no generator
change needed. Per-agent extraFiles (in mergedDist, not in the base
nix-store path) continue to proxy to the agent daemon.
frontend is a nix store path injected at build time — only [a-z0-9/._-],
no shell metacharacters — safe to interpolate without sanitization;
comment added documenting this assumption.
Without HIVE_AGENT_FRONTEND_DIR the existing single-proxy block is
emitted unchanged — backward-compatible for deployments without the env.
render() takes frontend_dir as a parameter so tests exercise both code
paths safely in parallel. 13 tests: 7 legacy, 6 split-mode. No clippy
warnings in changed files.
nix/modules/hive-c0re.nix: inject HIVE_AGENT_FRONTEND_DIR = "${cfg.frontend}/agent".
Nix side of the hive-priv phase 1 work. hive-priv is socket-activated
exclusively: systemd holds /run/hive/priv.sock (mode 0660) and starts
the service on first connection. LISTEN_FDS + LISTEN_PID are passed;
hive-priv reads them in socket_listener() to accept the pre-bound fd.
Phase 2 note (comment in file): when hive-c0re drops to a non-root
user, add SocketGroup = hive-core to the socket unit so the
unprivileged caller can still connect. No code change needed in
hive-priv itself.