hive-sh4re: split tool-group + capability enums into their own topic module
This commit is contained in:
parent
138f6b6c10
commit
d3ac4de8fb
11 changed files with 313 additions and 296 deletions
|
|
@ -9,6 +9,7 @@ pub mod assets;
|
|||
pub mod bash_task;
|
||||
pub mod manager;
|
||||
pub mod paths;
|
||||
pub mod permissions;
|
||||
pub mod wire_time;
|
||||
|
||||
/// Server-side hard cap on `Recv.max` (see the `Recv` request). Bounds
|
||||
|
|
@ -290,191 +291,6 @@ pub struct MatrixIdentity {
|
|||
pub homeserver: String,
|
||||
}
|
||||
|
||||
/// Named group of MCP tools an agent may be granted. The harness reads
|
||||
/// `HIVE_TOOL_GROUPS` from the environment (a comma-separated list of
|
||||
/// `snake_case` group names written by the meta renderer from per-agent
|
||||
/// config) and expands it to the matching tool names for `--allowedTools`.
|
||||
/// When the env var is absent the harness falls back to `AGENT_DEFAULT`.
|
||||
/// See `docs/conventions.md::Tool groups`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ToolGroup {
|
||||
/// `send`, `recv`, `ask`, `answer`
|
||||
Messaging,
|
||||
/// `get_agent_meta` (`set_status` is always-on — see `ALWAYS_ON_TOOLS`)
|
||||
Meta,
|
||||
/// `get_loose_ends`, `cancel_loose_end`, `remind`
|
||||
Inbox,
|
||||
/// `kill`, `start`, `restart`, `update` - *(privileged)*
|
||||
Lifecycle,
|
||||
/// `request_init_config`, `request_update_meta_inputs` - *(privileged)*
|
||||
Approvals,
|
||||
/// `request_schedule_prompt`, `fire_schedule_now`, `cancel_schedule`,
|
||||
/// `edit_schedule`, `list_schedules` - *(privileged)*
|
||||
Scheduling,
|
||||
/// `get_logs` - *(privileged)*
|
||||
Diagnostics,
|
||||
/// `create_repo` — create git repos through hive-c0re (the only path
|
||||
/// now that agents can't create them directly). Opt-in per
|
||||
/// agent so the operator controls who can spin up repos.
|
||||
Forge,
|
||||
/// `run`, `status` (via `mcp__bash__*`)
|
||||
Execution,
|
||||
/// Claude built-in web egress tools: `WebFetch` (retrieve a URL) and
|
||||
/// `WebSearch` (search the web). Both are omitted from `--tools` by
|
||||
/// default; adding this group to an agent enables them in the session
|
||||
/// and in `--allowedTools` so they run without a confirmation prompt.
|
||||
/// Does not gate any MCP tools — `tools()` returns `&[]`.
|
||||
WebTools,
|
||||
}
|
||||
|
||||
impl ToolGroup {
|
||||
/// The MCP tool names (without the `mcp__hyperhive__` prefix) in this group.
|
||||
/// Returns `&[]` for `WebTools` — it enables Claude built-in tools,
|
||||
/// not MCP tools; see `builtin_tools()`.
|
||||
#[must_use]
|
||||
pub fn tools(self) -> &'static [&'static str] {
|
||||
match self {
|
||||
Self::Messaging => &["send", "recv", "ack_until", "ask", "answer"],
|
||||
Self::Meta => &["get_agent_meta"],
|
||||
Self::Inbox => &["get_loose_ends", "cancel_loose_end", "remind"],
|
||||
Self::Lifecycle => &["kill", "start", "restart", "update", "list_containers"],
|
||||
Self::Approvals => &["request_init_config", "request_update_meta_inputs"],
|
||||
Self::Scheduling => &[
|
||||
"request_schedule_prompt",
|
||||
"fire_schedule_now",
|
||||
"cancel_schedule",
|
||||
"edit_schedule",
|
||||
"list_schedules",
|
||||
],
|
||||
Self::Diagnostics => &["get_logs"],
|
||||
Self::Forge => &["create_repo"],
|
||||
Self::Execution => &["run", "status"],
|
||||
Self::WebTools => &[],
|
||||
}
|
||||
}
|
||||
|
||||
/// MCP tools that are always exposed regardless of which tool groups an
|
||||
/// agent is granted. `set_status` lives here because the operator
|
||||
/// dashboard depends on every agent being able to report its status
|
||||
/// chip — gating it behind a group would let a misconfigured agent go
|
||||
/// dark on the dashboard. The server-side `SetStatus` handler has no
|
||||
/// tool-group check either (only length validation), so listing it here
|
||||
/// keeps the `--allowedTools` list honest with that reality.
|
||||
///
|
||||
/// `compact` lives here too: it's pure self-management (no cross-agent
|
||||
/// effect, no privilege), gated server-side on context usage rather
|
||||
/// than on tool groups, and every agent should be able to reach for it
|
||||
/// regardless of which optional groups it's been granted — same
|
||||
/// reasoning as `set_status`.
|
||||
///
|
||||
/// `mark_todos_done` too (a critical bug every agent hit): it was
|
||||
/// declared as a `#[tool]` fn but never added to *any*
|
||||
/// group's [`tools`](Self::tools), including `Inbox`, so no agent could
|
||||
/// ever get it into `--allowedTools` and every call prompted for
|
||||
/// approval it can't get. Todos are pushed to an agent independent of
|
||||
/// whether it holds `Inbox` (that group only gates
|
||||
/// `get_loose_ends`/`cancel_loose_end`/`remind`), so an agent without
|
||||
/// `Inbox` could accumulate todos it can never clear — same
|
||||
/// "every agent needs this regardless of optional groups" shape as
|
||||
/// `set_status`/`compact`, not a narrower `Inbox`-only fix.
|
||||
pub const ALWAYS_ON_TOOLS: &'static [&'static str] =
|
||||
&["set_status", "compact", "mark_todos_done"];
|
||||
|
||||
/// The Claude built-in tool names enabled by this group. Only
|
||||
/// `WebTools` returns a non-empty slice; all other groups return `&[]`
|
||||
/// (they control MCP tools via `tools()` instead).
|
||||
#[must_use]
|
||||
pub fn builtin_tools(self) -> &'static [&'static str] {
|
||||
match self {
|
||||
Self::WebTools => &["WebFetch", "WebSearch"],
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
|
||||
/// Default tool groups for an agent harness. Used when `HIVE_TOOL_GROUPS` is unset.
|
||||
pub const AGENT_DEFAULT: &'static [Self] =
|
||||
&[Self::Messaging, Self::Meta, Self::Inbox, Self::Execution];
|
||||
|
||||
/// Convenience preset for a fully-privileged agent (all groups).
|
||||
/// Use this as a starting point in `tool-groups.json` for root/manager agents.
|
||||
pub const MANAGER_DEFAULT: &'static [Self] = &[
|
||||
Self::Messaging,
|
||||
Self::Meta,
|
||||
Self::Inbox,
|
||||
Self::Lifecycle,
|
||||
Self::Approvals,
|
||||
Self::Scheduling,
|
||||
Self::Diagnostics,
|
||||
Self::Execution,
|
||||
];
|
||||
|
||||
/// Every known tool group in a stable order. Use this to enumerate
|
||||
/// columns in the capabilities UI or any other place that needs the
|
||||
/// full list without hard-coding it at the call site.
|
||||
pub const ALL: &'static [Self] = &[
|
||||
Self::Messaging,
|
||||
Self::Meta,
|
||||
Self::Inbox,
|
||||
Self::Lifecycle,
|
||||
Self::Approvals,
|
||||
Self::Scheduling,
|
||||
Self::Diagnostics,
|
||||
Self::Forge,
|
||||
Self::Execution,
|
||||
Self::WebTools,
|
||||
];
|
||||
|
||||
/// The `snake_case` wire name for this group (matches `serde(rename_all =
|
||||
/// "snake_case")` serialisation).
|
||||
#[must_use]
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Messaging => "messaging",
|
||||
Self::Meta => "meta",
|
||||
Self::Inbox => "inbox",
|
||||
Self::Lifecycle => "lifecycle",
|
||||
Self::Approvals => "approvals",
|
||||
Self::Scheduling => "scheduling",
|
||||
Self::Diagnostics => "diagnostics",
|
||||
Self::Forge => "forge",
|
||||
Self::Execution => "execution",
|
||||
Self::WebTools => "web_tools",
|
||||
}
|
||||
}
|
||||
|
||||
/// Short human-readable description suitable for a tooltip or help text.
|
||||
#[must_use]
|
||||
pub fn description(self) -> &'static str {
|
||||
match self {
|
||||
Self::Messaging => "send, recv, ask, answer — core agent communication",
|
||||
Self::Meta => {
|
||||
"get_agent_meta — identity introspection (set_status is always available)"
|
||||
}
|
||||
Self::Inbox => "get_loose_ends, cancel_loose_end, remind — self-scheduling",
|
||||
Self::Lifecycle => {
|
||||
"kill, start, restart, update, list_containers — container lifecycle (privileged)"
|
||||
}
|
||||
Self::Approvals => {
|
||||
"request_init_config, request_update_meta_inputs — config change flow (privileged)"
|
||||
}
|
||||
Self::Scheduling => {
|
||||
"request_schedule_prompt and related — operator-visible scheduled prompts (privileged)"
|
||||
}
|
||||
Self::Diagnostics => {
|
||||
"get_logs — read a sub-agent container's systemd journal (privileged)"
|
||||
}
|
||||
Self::Forge => {
|
||||
"create_repo — create git repos through hive-c0re (operator-gated merge)"
|
||||
}
|
||||
Self::Execution => {
|
||||
"run, status — run shell commands via mcp__bash__run / mcp__bash__status"
|
||||
}
|
||||
Self::WebTools => "WebFetch, WebSearch — Claude built-in web egress; not MCP tools",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Per-agent capability grants. Stored in `meta/capabilities.json`
|
||||
/// (same shape as `tool-groups.json`: `{ "alice": ["read_host_journal"] }`).
|
||||
/// Capabilities control system-level access that hive-c0re enforces
|
||||
|
|
@ -514,83 +330,6 @@ impl JournalPriority {
|
|||
}
|
||||
}
|
||||
|
||||
/// at dispatch time; they are orthogonal to tool groups (which control
|
||||
/// which MCP tools the harness exposes to claude).
|
||||
///
|
||||
/// Injected into containers as `HIVE_CAPABILITIES` (comma-separated
|
||||
/// `snake_case`) via `meta::render_flake`. The harness reads this to
|
||||
/// conditionally register capability-gated MCP tools so claude only
|
||||
/// sees tools it can actually invoke. See `docs/conventions.md::Capabilities`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Capability {
|
||||
/// Agent can lifecycle-manage the root agent (kill/start/restart)
|
||||
/// on behalf of the hive when the root has crashed. Named capability
|
||||
/// for the existing manager privilege — future topology enforcement
|
||||
/// will gate this via the capability system instead of the hardcoded
|
||||
/// `container == MANAGER_CONTAINER` check.
|
||||
ManageRootAgent,
|
||||
/// Agent can read the full host journal via `GetHostJournal`.
|
||||
/// hive-c0re checks this capability before running journalctl.
|
||||
/// MCP tool `get_host_journal` is only registered in the harness
|
||||
/// when this capability is present.
|
||||
ReadHostJournal,
|
||||
/// Agent can query non-child agents via `GetLooseEnds`,
|
||||
/// `CountPendingReminders`, and `ReminderRollup` on the agent
|
||||
/// socket. Without this capability, targeting a non-child agent is
|
||||
/// rejected with an error (direct children are always accessible
|
||||
/// without any capability). The `"*"` hive-wide value is not
|
||||
/// available on the agent socket even with this capability — use the
|
||||
/// manager socket for swarm-wide scans.
|
||||
QueryAgentState,
|
||||
/// Agent can restart hive infrastructure containers (hive-ci,
|
||||
/// hive-gateway, hive-forge) via the `restart` MCP tool. hive-c0re
|
||||
/// checks this capability before routing the restart through
|
||||
/// hive-priv; the concrete service allowlist lives root-side in
|
||||
/// hive-priv. Deliberately generic ("infra admin") so future
|
||||
/// privileged infra ops can hang off the same grant.
|
||||
InfraAdmin,
|
||||
}
|
||||
|
||||
impl Capability {
|
||||
/// Every known capability in a stable order. Use this to enumerate
|
||||
/// columns in the permissions UI or validate incoming capability strings.
|
||||
pub const ALL: &'static [Self] = &[
|
||||
Self::ManageRootAgent,
|
||||
Self::ReadHostJournal,
|
||||
Self::QueryAgentState,
|
||||
Self::InfraAdmin,
|
||||
];
|
||||
|
||||
/// Canonical `snake_case` name for this capability (matches serde).
|
||||
#[must_use]
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::ManageRootAgent => "manage_root_agent",
|
||||
Self::ReadHostJournal => "read_host_journal",
|
||||
Self::QueryAgentState => "query_agent_state",
|
||||
Self::InfraAdmin => "infra_admin",
|
||||
}
|
||||
}
|
||||
|
||||
/// Short human-readable description suitable for a tooltip or help text.
|
||||
#[must_use]
|
||||
pub fn description(self) -> &'static str {
|
||||
match self {
|
||||
Self::ManageRootAgent => {
|
||||
"lifecycle-manage the root/manager agent on hive crash recovery"
|
||||
}
|
||||
Self::ReadHostJournal => "read host journald via get_host_journal MCP tool",
|
||||
Self::QueryAgentState => {
|
||||
"query non-child agents' loose ends and reminder state via get_loose_ends"
|
||||
}
|
||||
Self::InfraAdmin => {
|
||||
"restart hive infrastructure containers (hive-ci, hive-gateway, hive-forge) via the restart tool"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Schedule row shape on the wire — mirror of
|
||||
/// `scheduled_prompts::Schedule` but in the public crate so the
|
||||
/// dashboard and agent surfaces can deserialize without depending
|
||||
|
|
|
|||
272
hive-sh4re/src/permissions.rs
Normal file
272
hive-sh4re/src/permissions.rs
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
//! Per-agent authorization: named MCP-tool groups (`ToolGroup`) and
|
||||
//! system-level capability grants (`Capability`). Both are declared in
|
||||
//! per-agent config (`tool-groups.json` / `capabilities.json`), injected
|
||||
//! into the container as env vars, and read by the harness to decide
|
||||
//! which MCP tools claude actually sees.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Named group of MCP tools an agent may be granted. The harness reads
|
||||
/// `HIVE_TOOL_GROUPS` from the environment (a comma-separated list of
|
||||
/// `snake_case` group names written by the meta renderer from per-agent
|
||||
/// config) and expands it to the matching tool names for `--allowedTools`.
|
||||
/// When the env var is absent the harness falls back to `AGENT_DEFAULT`.
|
||||
/// See `docs/conventions.md::Tool groups`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ToolGroup {
|
||||
/// `send`, `recv`, `ask`, `answer`
|
||||
Messaging,
|
||||
/// `get_agent_meta` (`set_status` is always-on — see `ALWAYS_ON_TOOLS`)
|
||||
Meta,
|
||||
/// `get_loose_ends`, `cancel_loose_end`, `remind`
|
||||
Inbox,
|
||||
/// `kill`, `start`, `restart`, `update` - *(privileged)*
|
||||
Lifecycle,
|
||||
/// `request_init_config`, `request_update_meta_inputs` - *(privileged)*
|
||||
Approvals,
|
||||
/// `request_schedule_prompt`, `fire_schedule_now`, `cancel_schedule`,
|
||||
/// `edit_schedule`, `list_schedules` - *(privileged)*
|
||||
Scheduling,
|
||||
/// `get_logs` - *(privileged)*
|
||||
Diagnostics,
|
||||
/// `create_repo` — create git repos through hive-c0re (the only path
|
||||
/// now that agents can't create them directly). Opt-in per
|
||||
/// agent so the operator controls who can spin up repos.
|
||||
Forge,
|
||||
/// `run`, `status` (via `mcp__bash__*`)
|
||||
Execution,
|
||||
/// Claude built-in web egress tools: `WebFetch` (retrieve a URL) and
|
||||
/// `WebSearch` (search the web). Both are omitted from `--tools` by
|
||||
/// default; adding this group to an agent enables them in the session
|
||||
/// and in `--allowedTools` so they run without a confirmation prompt.
|
||||
/// Does not gate any MCP tools — `tools()` returns `&[]`.
|
||||
WebTools,
|
||||
}
|
||||
|
||||
impl ToolGroup {
|
||||
/// The MCP tool names (without the `mcp__hyperhive__` prefix) in this group.
|
||||
/// Returns `&[]` for `WebTools` — it enables Claude built-in tools,
|
||||
/// not MCP tools; see `builtin_tools()`.
|
||||
#[must_use]
|
||||
pub fn tools(self) -> &'static [&'static str] {
|
||||
match self {
|
||||
Self::Messaging => &["send", "recv", "ack_until", "ask", "answer"],
|
||||
Self::Meta => &["get_agent_meta"],
|
||||
Self::Inbox => &["get_loose_ends", "cancel_loose_end", "remind"],
|
||||
Self::Lifecycle => &["kill", "start", "restart", "update", "list_containers"],
|
||||
Self::Approvals => &["request_init_config", "request_update_meta_inputs"],
|
||||
Self::Scheduling => &[
|
||||
"request_schedule_prompt",
|
||||
"fire_schedule_now",
|
||||
"cancel_schedule",
|
||||
"edit_schedule",
|
||||
"list_schedules",
|
||||
],
|
||||
Self::Diagnostics => &["get_logs"],
|
||||
Self::Forge => &["create_repo"],
|
||||
Self::Execution => &["run", "status"],
|
||||
Self::WebTools => &[],
|
||||
}
|
||||
}
|
||||
|
||||
/// MCP tools that are always exposed regardless of which tool groups an
|
||||
/// agent is granted. `set_status` lives here because the operator
|
||||
/// dashboard depends on every agent being able to report its status
|
||||
/// chip — gating it behind a group would let a misconfigured agent go
|
||||
/// dark on the dashboard. The server-side `SetStatus` handler has no
|
||||
/// tool-group check either (only length validation), so listing it here
|
||||
/// keeps the `--allowedTools` list honest with that reality.
|
||||
///
|
||||
/// `compact` lives here too: it's pure self-management (no cross-agent
|
||||
/// effect, no privilege), gated server-side on context usage rather
|
||||
/// than on tool groups, and every agent should be able to reach for it
|
||||
/// regardless of which optional groups it's been granted — same
|
||||
/// reasoning as `set_status`.
|
||||
///
|
||||
/// `mark_todos_done` too (a critical bug every agent hit): it was
|
||||
/// declared as a `#[tool]` fn but never added to *any*
|
||||
/// group's [`tools`](Self::tools), including `Inbox`, so no agent could
|
||||
/// ever get it into `--allowedTools` and every call prompted for
|
||||
/// approval it can't get. Todos are pushed to an agent independent of
|
||||
/// whether it holds `Inbox` (that group only gates
|
||||
/// `get_loose_ends`/`cancel_loose_end`/`remind`), so an agent without
|
||||
/// `Inbox` could accumulate todos it can never clear — same
|
||||
/// "every agent needs this regardless of optional groups" shape as
|
||||
/// `set_status`/`compact`, not a narrower `Inbox`-only fix.
|
||||
pub const ALWAYS_ON_TOOLS: &'static [&'static str] =
|
||||
&["set_status", "compact", "mark_todos_done"];
|
||||
|
||||
/// The Claude built-in tool names enabled by this group. Only
|
||||
/// `WebTools` returns a non-empty slice; all other groups return `&[]`
|
||||
/// (they control MCP tools via `tools()` instead).
|
||||
#[must_use]
|
||||
pub fn builtin_tools(self) -> &'static [&'static str] {
|
||||
match self {
|
||||
Self::WebTools => &["WebFetch", "WebSearch"],
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
|
||||
/// Default tool groups for an agent harness. Used when `HIVE_TOOL_GROUPS` is unset.
|
||||
pub const AGENT_DEFAULT: &'static [Self] =
|
||||
&[Self::Messaging, Self::Meta, Self::Inbox, Self::Execution];
|
||||
|
||||
/// Convenience preset for a fully-privileged agent (all groups).
|
||||
/// Use this as a starting point in `tool-groups.json` for root/manager agents.
|
||||
pub const MANAGER_DEFAULT: &'static [Self] = &[
|
||||
Self::Messaging,
|
||||
Self::Meta,
|
||||
Self::Inbox,
|
||||
Self::Lifecycle,
|
||||
Self::Approvals,
|
||||
Self::Scheduling,
|
||||
Self::Diagnostics,
|
||||
Self::Execution,
|
||||
];
|
||||
|
||||
/// Every known tool group in a stable order. Use this to enumerate
|
||||
/// columns in the capabilities UI or any other place that needs the
|
||||
/// full list without hard-coding it at the call site.
|
||||
pub const ALL: &'static [Self] = &[
|
||||
Self::Messaging,
|
||||
Self::Meta,
|
||||
Self::Inbox,
|
||||
Self::Lifecycle,
|
||||
Self::Approvals,
|
||||
Self::Scheduling,
|
||||
Self::Diagnostics,
|
||||
Self::Forge,
|
||||
Self::Execution,
|
||||
Self::WebTools,
|
||||
];
|
||||
|
||||
/// The `snake_case` wire name for this group (matches `serde(rename_all =
|
||||
/// "snake_case")` serialisation).
|
||||
#[must_use]
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Messaging => "messaging",
|
||||
Self::Meta => "meta",
|
||||
Self::Inbox => "inbox",
|
||||
Self::Lifecycle => "lifecycle",
|
||||
Self::Approvals => "approvals",
|
||||
Self::Scheduling => "scheduling",
|
||||
Self::Diagnostics => "diagnostics",
|
||||
Self::Forge => "forge",
|
||||
Self::Execution => "execution",
|
||||
Self::WebTools => "web_tools",
|
||||
}
|
||||
}
|
||||
|
||||
/// Short human-readable description suitable for a tooltip or help text.
|
||||
#[must_use]
|
||||
pub fn description(self) -> &'static str {
|
||||
match self {
|
||||
Self::Messaging => "send, recv, ask, answer — core agent communication",
|
||||
Self::Meta => {
|
||||
"get_agent_meta — identity introspection (set_status is always available)"
|
||||
}
|
||||
Self::Inbox => "get_loose_ends, cancel_loose_end, remind — self-scheduling",
|
||||
Self::Lifecycle => {
|
||||
"kill, start, restart, update, list_containers — container lifecycle (privileged)"
|
||||
}
|
||||
Self::Approvals => {
|
||||
"request_init_config, request_update_meta_inputs — config change flow (privileged)"
|
||||
}
|
||||
Self::Scheduling => {
|
||||
"request_schedule_prompt and related — operator-visible scheduled prompts (privileged)"
|
||||
}
|
||||
Self::Diagnostics => {
|
||||
"get_logs — read a sub-agent container's systemd journal (privileged)"
|
||||
}
|
||||
Self::Forge => {
|
||||
"create_repo — create git repos through hive-c0re (operator-gated merge)"
|
||||
}
|
||||
Self::Execution => {
|
||||
"run, status — run shell commands via mcp__bash__run / mcp__bash__status"
|
||||
}
|
||||
Self::WebTools => "WebFetch, WebSearch — Claude built-in web egress; not MCP tools",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Per-agent capability grants. Stored in `meta/capabilities.json`
|
||||
/// (same shape as `tool-groups.json`: `{ "alice": ["read_host_journal"] }`).
|
||||
/// Capabilities control system-level access hive-c0re enforces at
|
||||
/// dispatch time; they are orthogonal to tool groups (which control
|
||||
/// which MCP tools the harness exposes to claude).
|
||||
///
|
||||
/// Injected into containers as `HIVE_CAPABILITIES` (comma-separated
|
||||
/// `snake_case`) via `meta::render_flake`. The harness reads this to
|
||||
/// conditionally register capability-gated MCP tools so claude only
|
||||
/// sees tools it can actually invoke. See `docs/conventions.md::Capabilities`.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Capability {
|
||||
/// Agent can lifecycle-manage the root agent (kill/start/restart)
|
||||
/// on behalf of the hive when the root has crashed. Named capability
|
||||
/// for the existing manager privilege — future topology enforcement
|
||||
/// will gate this via the capability system instead of the hardcoded
|
||||
/// `container == MANAGER_CONTAINER` check.
|
||||
ManageRootAgent,
|
||||
/// Agent can read the full host journal via `GetHostJournal`.
|
||||
/// hive-c0re checks this capability before running journalctl.
|
||||
/// MCP tool `get_host_journal` is only registered in the harness
|
||||
/// when this capability is present.
|
||||
ReadHostJournal,
|
||||
/// Agent can query non-child agents via `GetLooseEnds`,
|
||||
/// `CountPendingReminders`, and `ReminderRollup` on the agent
|
||||
/// socket. Without this capability, targeting a non-child agent is
|
||||
/// rejected with an error (direct children are always accessible
|
||||
/// without any capability). The `"*"` hive-wide value is not
|
||||
/// available on the agent socket even with this capability — use the
|
||||
/// manager socket for swarm-wide scans.
|
||||
QueryAgentState,
|
||||
/// Agent can restart hive infrastructure containers (hive-ci,
|
||||
/// hive-gateway, hive-forge) via the `restart` MCP tool. hive-c0re
|
||||
/// checks this capability before routing the restart through
|
||||
/// hive-priv; the concrete service allowlist lives root-side in
|
||||
/// hive-priv. Deliberately generic ("infra admin") so future
|
||||
/// privileged infra ops can hang off the same grant.
|
||||
InfraAdmin,
|
||||
}
|
||||
|
||||
impl Capability {
|
||||
/// Every known capability in a stable order. Use this to enumerate
|
||||
/// columns in the permissions UI or validate incoming capability strings.
|
||||
pub const ALL: &'static [Self] = &[
|
||||
Self::ManageRootAgent,
|
||||
Self::ReadHostJournal,
|
||||
Self::QueryAgentState,
|
||||
Self::InfraAdmin,
|
||||
];
|
||||
|
||||
/// Canonical `snake_case` name for this capability (matches serde).
|
||||
#[must_use]
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::ManageRootAgent => "manage_root_agent",
|
||||
Self::ReadHostJournal => "read_host_journal",
|
||||
Self::QueryAgentState => "query_agent_state",
|
||||
Self::InfraAdmin => "infra_admin",
|
||||
}
|
||||
}
|
||||
|
||||
/// Short human-readable description suitable for a tooltip or help text.
|
||||
#[must_use]
|
||||
pub fn description(self) -> &'static str {
|
||||
match self {
|
||||
Self::ManageRootAgent => {
|
||||
"lifecycle-manage the root/manager agent on hive crash recovery"
|
||||
}
|
||||
Self::ReadHostJournal => "read host journald via get_host_journal MCP tool",
|
||||
Self::QueryAgentState => {
|
||||
"query non-child agents' loose ends and reminder state via get_loose_ends"
|
||||
}
|
||||
Self::InfraAdmin => {
|
||||
"restart hive infrastructure containers (hive-ci, hive-gateway, hive-forge) via the restart tool"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue