remove the get_host_journal MCP tool and its capability

swarm-logs covers every host-tier unit this tool could reach, so the
second, capability-gated path into host journald earns nothing and is
removed outright rather than disabled behind a flag.

Removed end to end: the MCP tool definition + handler, the
GetHostJournal/HostJournal wire variants, hive-c0re's
dispatch_host_journal handler, the ReadHostJournal capability, and the
harness-side capability->--allowedTools gate. get_host_journal was the
only capability that mapped to an MCP tool, so allowed_capability_tools
could only ever return an empty vec; it goes too rather than linger as a
function that provably does nothing.

capabilities::has_cap/caps_for stay: #4624 gave ManageRootAgent's
bind-mount enforcement (hive-c0re/src/lifecycle/host_config.rs) a
second caller of has_cap, so they're no longer callerless once this
lands on top of it.

hive-sh4re's journal module (JournalPriority) had no consumer outside
this tool and is deleted.

An existing capabilities.json still naming read_host_journal does not
error: capabilities::prune_unknown drops unrecognised names with a
warn!, and an agent left with no capabilities has its entry removed. No
migration step is needed.

Untouched: hive-c0re/src/dashboard/journal.rs's
read_host_journal_response, which matches the name but is the private
helper behind the operator-only GET /api/journal-host dashboard route
and carries no capability check.
This commit is contained in:
atlas 2026-09-21 19:20:05 +02:00
commit 11097ed336
18 changed files with 21 additions and 401 deletions

View file

@ -399,7 +399,7 @@ Contents:
`tool_groups::set_groups`; injected as `HIVE_TOOL_GROUPS` env
var into each agent's container.
- `capabilities.json` — per-agent capability grants
(`{ "atlas": ["read_host_journal"] }`). Written by
(`{ "ruth": ["manage_root_agent"] }`). Written by
`capabilities::set_caps`; injected as `HIVE_CAPABILITIES` env
var. Absent agents have no extra capabilities.
- `resource-limits.json` — per-agent container resource overrides

View file

@ -363,11 +363,10 @@ that allows the underlying resource access.
| Capability | Effect |
|---|---|
| `manage_root_agent` | may manage *any* agent: bind-mounts every agent's state (rw) + config (ro) into the holder's container, plus `/applied` and `/meta` (ro). Takes effect on the holder's next container rebuild/restart |
| `read_host_journal` | registers the `get_host_journal` MCP tool + serves `GET /journal-host` requests |
**Config storage** — per-agent capabilities live in
`/var/lib/hyperhive/meta/capabilities.json` alongside `tool-groups.json`.
Format: `{ "atlas": ["read_host_journal"], "ruth": ["manage_root_agent"] }`.
Format: `{ "ruth": ["manage_root_agent"] }`.
An absent entry means "no extra capabilities." `render_flake` in `meta.rs`
reads this file and injects `HIVE_CAPABILITIES` (comma-separated
`snake_case` names) into each agent's systemd service env; absent entries emit

View file

@ -44,4 +44,4 @@ debug agent behavior.
(`mcp__matrix__*`) for agents with a matrix account, multiple
accounts per agent, and declaring extra MCP servers generally.
- **[scheduling](scheduling.md)** — scheduled prompts (operator
approval required) and the `get_host_journal` diagnostics tool.
approval required).

View file

@ -66,31 +66,6 @@ body, per-target `last_fired_at` and `last_result`,
`next_fire_at_unix`, `interval_seconds`. Use to look up an id before
cancelling, or to audit upcoming wake-ups across the hive.
## `read_host_journal` capability
Capability-gated (not a tool group) — the operator enables it in the
P3RM1SS10NS C4P4B1L1T13S section. Unlike tool groups this isn't
configurable from `agent.nix`.
### `get_host_journal(unit?, container?, lines?, priority?, grep?, since?, until?)`
Fetch recent lines from the **host** journal (requires
`read_host_journal` capability). Useful when you need visibility
outside your own container — infrastructure services, hive-c0re
lifecycle events, or another container's boot log.
- `unit` — filter to a systemd unit (for example `hive-c0re.service`).
- `container` — nspawn machine name verbatim. Agent containers use
the `h-<name>` prefix (for example `h-iris`); infrastructure containers
use their full name (for example `hive-ci`, `hive-forge`, `hive-matrix`).
Omit for the host journal. The gateway has no machine — its nginx
runs on the host, so read it with `unit: nginx.service` and no
`container`.
- `lines` — how many lines to return (default 30, max 100).
- `priority` — minimum syslog level (`emerg``debug`).
- `grep` — regex matched against log message fields (`journalctl --grep`).
- `since` / `until` — time bounds (for example `-1h`, `2024-01-01 12:00:00`).
## See also
- `remind` (no-approval self-wake path) — documented in

View file

@ -150,11 +150,10 @@ hive_name?, swarm_name?, matrix_accounts? }`. `matrix_accounts` is a
and `WebSearch` tools (not MCP tools; added directly to the
`--allowedTools` list). Off by default; add the group in the
P3RM1SS10NS tab and rebuild to enable.
- **Capability-gated**`get_host_journal` (requires
`read_host_journal` capability set via the P3RM1SS10NS tab;
orthogonal to tool groups). Full list of capabilities and their
effects in [`docs/process/conventions.md#capabilities`](../process/conventions.md).
Also documented in [`docs/tools/scheduling.md`](../tools/scheduling.md).
- **Capabilities** — orthogonal to tool groups, set via the P3RM1SS10NS
tab. No capability registers an MCP tool today; the full list and their
effects are in
[`docs/process/conventions.md#capabilities`](../process/conventions.md).
- **Matrix MCP + extra servers**`mcp__matrix__*` tools and
per-agent extra MCP config. See
[`docs/tools/matrix.md`](../tools/matrix.md).

View file

@ -441,7 +441,6 @@ The current capabilities are:
| Name | Effect |
|------|--------|
| `manage_root_agent` | mounts every agent's state (rw) + config (ro) into this agent's container so it can manage/recover any agent |
| `read_host_journal` | unlocks `get_host_journal` to read journald from inside a container |
Each row is one agent. Columns are the capability names returned by
`GET /api/capabilities` as `caps: Vec<String>`. Checking or unchecking

View file

@ -216,33 +216,3 @@ pub struct EditScheduleArgs {
#[serde(default)]
pub targets_remove: Option<Vec<String>>,
}
/// Arguments for `get_host_journal` (capability-gated: `read_host_journal`).
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct GetHostJournalArgs {
/// Systemd unit to filter (e.g. `hive-c0re.service`). Omit for all units.
#[serde(default)]
pub unit: Option<String>,
/// nspawn machine name verbatim (e.g. `h-iris`). Omit for host journal.
/// Agent containers use the `h-` prefix (e.g. `h-iris`); infrastructure
/// containers use their full name (e.g. `hive-ci`, `hive-forge`,
/// `hive-matrix`). The gateway has no machine — its nginx runs on the
/// host, so read it with `unit: nginx.service` and no `container`.
#[serde(default)]
pub container: Option<String>,
/// Number of lines to return (default 30, max 100).
#[serde(default)]
pub lines: Option<u32>,
/// Minimum syslog priority level.
#[serde(default)]
pub priority: Option<hive_sh4re::journal::JournalPriority>,
/// Regex to match against log message fields (journalctl --grep).
#[serde(default)]
pub grep: Option<String>,
/// Show entries on or newer than this timestamp (e.g. `-1h`).
#[serde(default)]
pub since: Option<String>,
/// Show entries on or older than this timestamp.
#[serde(default)]
pub until: Option<String>,
}

View file

@ -25,8 +25,8 @@ mod render;
pub use args::{
AckUntilArgs, CancelLooseEndArgs, CancelScheduleArgs, CompactArgs, CreateRepoArgs,
EditScheduleArgs, FireScheduleNowArgs, GetAgentMetaArgs, GetHostJournalArgs, MarkTodosDoneArgs,
RecvArgs, RemindArgs, RequestSchedulePromptArgs, SendArgs, SetStatusArgs,
EditScheduleArgs, FireScheduleNowArgs, GetAgentMetaArgs, MarkTodosDoneArgs, RecvArgs,
RemindArgs, RequestSchedulePromptArgs, SendArgs, SetStatusArgs,
};
pub use render::{annotate_retries, format_ack, format_agent_meta, format_recv};
@ -547,48 +547,6 @@ impl AgentServer {
.await
}
// IMPORTANT: this tool is capability-gated (`read_host_journal`).
// It is added to `--allowedTools` by `allowed_capability_tools` only
// when `HIVE_CAPABILITIES` contains `read_host_journal`. hive-c0re
// performs a second capability check server-side before running journalctl.
#[tool(
description = "Fetch recent lines from the host journal (requires `read_host_journal` \
capability). All filters are optional - omit to get the last N host journal lines. \
`unit`: filter to a systemd unit (e.g. `hive-c0re.service`). \
`container`: nspawn machine name verbatim (e.g. `h-iris`). Omit for host journal. \
Agent containers use the `h-` prefix (e.g. `h-iris`, `h-atlas`); \
infrastructure containers use their full name (e.g. `hive-ci`, `hive-forge`, \
`hive-matrix`). The gateway is not a container: its nginx logs are in \
the host journal, so omit `container` and pass `unit: nginx.service`. \
`lines`: how many lines (default 30, max 100). \
`priority`: minimum syslog level enum. \
`grep`: regex matched against log message fields (journalctl --grep). \
`since`: show entries on or newer than this (e.g. `-1h`, `2024-01-01 12:00:00`). \
`until`: show entries on or older than this."
)]
async fn get_host_journal(&self, Parameters(args): Parameters<GetHostJournalArgs>) -> String {
let log = format!("{args:?}");
run_tool_envelope("get_host_journal", log, async move {
let (resp, retries) = self
.dispatch(hive_core_agent_sock::Request::GetHostJournal {
unit: args.unit,
container: args.container,
lines: args.lines,
priority: args.priority,
grep: args.grep,
since: args.since,
until: args.until,
})
.await;
let result = match resp {
Ok(hive_core_agent_sock::Response::HostJournal { content }) => content,
other => reply_err(other, "get_host_journal"),
};
annotate_retries(result, retries)
})
.await
}
#[tool(
description = "Queue an approval to add a scheduled prompt — one body delivered to \
N agent inboxes at a target time, optionally recurring every `interval_seconds`. \

View file

@ -5,7 +5,6 @@ Tools (hyperhive surface). Full signature + behavior for each comes from the too
- **Inbox / messaging** (always available): `mcp__hyperhive__recv`, `mcp__hyperhive__ack_until`, `mcp__hyperhive__send`, `mcp__hyperhive__get_loose_ends`, `mcp__hyperhive__cancel_loose_end`, `mcp__hyperhive__mark_todos_done`, `mcp__hyperhive__remind`, `mcp__hyperhive__set_status`, `mcp__hyperhive__get_agent_meta`. One habit worth internalizing beyond the tool descriptions themselves: prefer ending the turn over repeatedly polling `recv` when idle (only turn-boundaries observe in-container todo wakes — bash-task completions, matrix unread, forge activity — and ending the turn is also your checkpoint). For a large todo backlog (`get_loose_ends` caps at 40 rows), clear reviewed ids in bulk with `mark_todos_done` rather than cancelling one at a time — there's no blind range-clear, only ids you've actually looked at.
- **Extra MCP tools** (some agents only): `mcp__<server>__<tool>` — agent-specific (matrix client, scraper, db connector, etc.) declared in your `agent.nix` under `hyperhive.extraMcpServers`. First-class tools, already operator-approved at deploy time.
- **Scheduling** (_requires `scheduling` tool group_): `request_schedule_prompt` (queues an approval), `cancel_schedule`, `fire_schedule_now`, `edit_schedule`, `list_schedules` (these four don't need approval — you can manage any schedule, whoever owns it).
- **Diagnostics**: `get_host_journal` (_requires `read_host_journal` capability_).
Need new packages, env vars, or other NixOS config for yourself? You can't edit your own config directly — open a PR on your `agent-configs/<name>` repo, or contact the operator directly. Config repos live at `/agents/{label}/config/` (read-only inside your container). All changes flow through operator-approved commits.

View file

@ -30,37 +30,6 @@ pub const DEFAULT_MCP_HTTP_PORT: u16 = 8790;
/// drift.
pub use hive_sh4re::permissions::{builtin_tools_arg, effective_tool_groups};
/// `HIVE_CAPABILITIES` env var injected by `meta::render_flake` when the
/// operator grants capabilities to this agent. Comma-separated
/// `hive_sh4re::permissions::Capability` `snake_case` names. Absent = no extra capabilities.
const CAPABILITIES_ENV: &str = "HIVE_CAPABILITIES";
/// Returns the MCP tool names (without `mcp__hyperhive__` prefix) that are
/// unlocked by the agent's current capability set. These are added to the
/// `--allowedTools` list so claude can call them without prompting, and
/// hive-c0re performs a second server-side capability check before executing.
fn allowed_capability_tools() -> Vec<String> {
let raw = match std::env::var(CAPABILITIES_ENV) {
Ok(v) if !v.trim().is_empty() => v,
_ => return vec![],
};
let mut tools = Vec::new();
for token in raw.split(',') {
let t = token.trim().to_ascii_lowercase();
match t.as_str() {
"read_host_journal" => tools.push("get_host_journal".to_owned()),
// manage_root_agent exposes no MCP tool at all: it is a
// host-side grant, enforced by hive-c0re when it computes the
// container's bind mounts. Nothing for the harness to register.
"manage_root_agent" => {}
unknown => {
tracing::warn!(capability = %unknown, "unrecognised capability in HIVE_CAPABILITIES — skipped");
}
}
}
tools
}
/// Tool group an extra (out-of-process) MCP server is gated behind, if any.
///
/// Most `services.hyperhive.agent.extraMcpServers` entries are ungated — available whenever
@ -161,13 +130,6 @@ pub fn allowed_tools_arg() -> String {
.map(ToOwned::to_owned)
.collect();
all.extend(allowed_mcp_tools(&groups));
// Capability-gated MCP tools: added to --allowedTools when HIVE_CAPABILITIES
// includes the corresponding capability. hive-c0re performs a second
// server-side check, so this is a usability gate (no annoying prompts),
// not the security boundary.
for tool in allowed_capability_tools() {
all.push(format!("mcp__{SERVER_NAME}__{tool}"));
}
all.join(",")
}

View file

@ -501,7 +501,6 @@ fn tool_icon(name: &str) -> &'static str {
"mcp__matrix__list_rooms"
| "mcp__matrix__list_room_members"
| "mcp__matrix__list_invites" => "📋",
"mcp__hyperhive__get_host_journal" => "📜",
"mcp__matrix__read_room" | "Read" => "📖",
"mcp__matrix__mark_read" => "👁️",
"mcp__bash__kill" => "🛑",
@ -645,25 +644,6 @@ fn fmt_hyperhive_tool(name: &str, short: &str, input: &Value) -> String {
.map_or_else(|| "?".to_owned(), |n| n.to_string());
format!("{short}{up_to}")
}
"mcp__hyperhive__get_host_journal" => {
let mut parts = Vec::new();
if let Some(c) = input.get("container").and_then(Value::as_str) {
parts.push(c.to_owned());
} else if let Some(u) = input.get("unit").and_then(Value::as_str) {
parts.push(u.to_owned());
}
if let Some(g) = input.get("grep").and_then(Value::as_str) {
parts.push(format!("/{g}/"));
}
if let Some(l) = input.get("lines").and_then(Value::as_u64) {
parts.push(format!("{l}L"));
}
if parts.is_empty() {
format!("{short}()")
} else {
format!("{short} {}", parts.join(" · "))
}
}
"mcp__hyperhive__remind" => fmt_hyperhive_remind(short, input),
"mcp__hyperhive__list_schedules"
| "mcp__hyperhive__cancel_schedule"

View file

@ -7,7 +7,7 @@
//!
//! ```json
//! {
//! "atlas": ["read_host_journal"]
//! "atlas": ["manage_root_agent"]
//! }
//! ```
//!
@ -167,9 +167,9 @@ mod tests {
#[test]
fn prune_unknown_keeps_known_name() {
let mut map = BTreeMap::from([("atlas".to_owned(), vec!["read_host_journal".to_owned()])]);
let mut map = BTreeMap::from([("atlas".to_owned(), vec!["manage_root_agent".to_owned()])]);
assert!(!prune_unknown(&mut map));
assert_eq!(map["atlas"], vec!["read_host_journal".to_owned()]);
assert_eq!(map["atlas"], vec!["manage_root_agent".to_owned()]);
}
#[test]
@ -183,23 +183,16 @@ mod tests {
fn prune_unknown_keeps_known_and_drops_unknown_in_same_entry() {
let mut map = BTreeMap::from([(
"atlas".to_owned(),
vec!["read_host_journal".to_owned(), "fly_to_the_moon".to_owned()],
vec!["manage_root_agent".to_owned(), "fly_to_the_moon".to_owned()],
)]);
assert!(prune_unknown(&mut map));
assert_eq!(map["atlas"], vec!["read_host_journal".to_owned()]);
}
#[test]
fn manage_root_agent_is_still_a_known_name() {
let mut map = BTreeMap::from([("atlas".to_owned(), vec!["manage_root_agent".to_owned()])]);
assert!(!prune_unknown(&mut map));
assert_eq!(map["atlas"], vec!["manage_root_agent".to_owned()]);
}
#[test]
fn apply_known_caps_removes_entry_on_empty_input() {
let mut current =
BTreeMap::from([("atlas".to_owned(), vec!["read_host_journal".to_owned()])]);
BTreeMap::from([("atlas".to_owned(), vec!["manage_root_agent".to_owned()])]);
apply_known_caps(&mut current, "atlas", &[]);
assert!(!current.contains_key("atlas"));
}
@ -207,7 +200,7 @@ mod tests {
#[test]
fn apply_known_caps_removes_entry_when_only_unknown_names_given() {
let mut current =
BTreeMap::from([("atlas".to_owned(), vec!["read_host_journal".to_owned()])]);
BTreeMap::from([("atlas".to_owned(), vec!["manage_root_agent".to_owned()])]);
apply_known_caps(&mut current, "atlas", &["fly_to_the_moon".to_owned()]);
assert!(!current.contains_key("atlas"));
}
@ -218,8 +211,8 @@ mod tests {
apply_known_caps(
&mut current,
"atlas",
&["read_host_journal".to_owned(), "fly_to_the_moon".to_owned()],
&["manage_root_agent".to_owned(), "fly_to_the_moon".to_owned()],
);
assert_eq!(current["atlas"], vec!["read_host_journal".to_owned()]);
assert_eq!(current["atlas"], vec!["manage_root_agent".to_owned()]);
}
}

View file

@ -231,29 +231,6 @@ pub(crate) async fn dispatch_shared(
coord.clear_pause_pending(agent);
hive_core_agent_sock::Response::Ok
}
hive_core_agent_sock::Request::GetHostJournal {
unit,
container,
lines,
priority,
grep,
since,
until,
} => {
dispatch_host_journal(
agent,
HostJournalArgs {
unit,
container,
lines,
priority,
grep,
since,
until,
},
)
.await
}
// Not a shared variant.
_ => return None,
})
@ -735,116 +712,6 @@ fn check_can_cancel_approval(canceller: &str) -> Result<(), String> {
}
}
/// Field-named journal-query knobs for [`dispatch_host_journal`].
/// Borrows straight from the matched `GetHostJournal` request variant.
pub struct HostJournalArgs<'a> {
pub unit: &'a Option<String>,
pub container: &'a Option<String>,
pub lines: &'a Option<u32>,
pub priority: &'a Option<hive_sh4re::journal::JournalPriority>,
pub grep: &'a Option<String>,
pub since: &'a Option<String>,
pub until: &'a Option<String>,
}
/// Handle `GetHostJournal` from both the agent and manager sockets.
/// Capability-gated: the calling agent must hold `read_host_journal` in
/// `meta/capabilities.json`. Runs `journalctl` host-side and returns
/// the output as a `HostJournal` response.
///
/// The manager is not exempt - grant `read_host_journal` in
/// `meta/capabilities.json` to enable it for any agent including the manager.
pub async fn dispatch_host_journal(agent: &str, args: HostJournalArgs<'_>) -> Response {
let HostJournalArgs {
unit,
container,
lines,
priority,
grep,
since,
until,
} = args;
if !crate::capabilities::has_cap(agent, hive_sh4re::permissions::Capability::ReadHostJournal) {
return Response::Err {
message: "agent does not have the read_host_journal capability".to_owned(),
};
}
let n = lines.unwrap_or(30).min(100);
// A container (`-M`) read enters the container namespace and needs
// root, so it's delegated to hive-priv. A host read (no container)
// the unprivileged hive-core user can do directly via its
// systemd-journal group membership.
if let Some(c) = container {
tracing::info!(%agent, machine = %c, %n, "get_host_journal (container)");
return match crate::priv_client::read_container_journal(
c,
hive_priv_sock::JournalQuery {
lines: n,
unit: unit.clone(),
priority: priority.as_ref().map(|p| p.as_journald_str().to_owned()),
grep: grep.clone(),
since: since.clone(),
until: until.clone(),
..Default::default()
},
)
.await
{
Ok((stdout, stderr)) => {
let content = if stdout.is_empty() { stderr } else { stdout };
Response::HostJournal { content }
}
Err(e) => Response::Err {
message: format!("journal read: {e:#}"),
},
};
}
let mut args: Vec<String> = vec![
"--no-pager".to_owned(),
"--output=short".to_owned(),
"-n".to_owned(),
n.to_string(),
];
if let Some(u) = unit {
args.push("-u".to_owned());
args.push(u.clone());
}
if let Some(p) = priority {
args.push("-p".to_owned());
args.push(p.as_journald_str().to_owned());
}
if let Some(g) = grep {
args.push(format!("--grep={g}"));
}
if let Some(s) = since {
args.push(format!("--since={s}"));
}
if let Some(u) = until {
args.push(format!("--until={u}"));
}
tracing::info!(%agent, ?args, "get_host_journal");
match tokio::process::Command::new("journalctl")
.args(&args)
.output()
.await
{
Ok(out) => {
let content = if out.status.success() || !out.stdout.is_empty() {
String::from_utf8_lossy(&out.stdout).into_owned()
} else {
let stderr = String::from_utf8_lossy(&out.stderr);
format!("journalctl exited {}: {stderr}", out.status)
};
Response::HostJournal { content }
}
Err(e) => Response::Err {
message: format!("journalctl spawn failed: {e:#}"),
},
}
}
/// 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).

View file

@ -9,7 +9,6 @@
use hive_sh4re::container::MatrixIdentity;
use hive_sh4re::inbox::{CancelLooseEndKind, DeliveredMessage, InboxRow, LooseEnd};
use hive_sh4re::journal::JournalPriority;
use hive_sh4re::manager::SchedulePromptPayload;
use hive_sh4re::schedule::WireSchedule;
use hive_types::Ident;
@ -119,34 +118,6 @@ pub enum Request {
/// DAG's drain node resolve immediately instead of waiting out its
/// timeout fallback — same shape as `GracefulStopComplete`.
PauseAcknowledged,
/// *(capability-gated: `read_host_journal`)* Fetch recent lines
/// from the host journal. Filters are all optional; omitting all
/// returns the last `lines` entries from the global journal.
GetHostJournal {
/// Filter to a specific systemd unit (e.g. `hive-c0re.service`).
#[serde(default, skip_serializing_if = "Option::is_none")]
unit: Option<String>,
/// Machine name to pass to journalctl `-M` verbatim (e.g. `h-iris`).
/// The caller is responsible for the correct nspawn machine name.
#[serde(default, skip_serializing_if = "Option::is_none")]
container: Option<String>,
/// Number of journal lines to return (default 30, max 100).
#[serde(default, skip_serializing_if = "Option::is_none")]
lines: Option<u32>,
/// Minimum syslog priority level.
#[serde(default, skip_serializing_if = "Option::is_none")]
priority: Option<JournalPriority>,
/// Regex to match against log message fields (journalctl `--grep`).
#[serde(default, skip_serializing_if = "Option::is_none")]
grep: Option<String>,
/// Show entries on or newer than this timestamp (journalctl `--since`).
/// ISO 8601 or journalctl-accepted relative strings (e.g. `"-1h"`).
#[serde(default, skip_serializing_if = "Option::is_none")]
since: Option<String>,
/// Show entries on or older than this timestamp (journalctl `--until`).
#[serde(default, skip_serializing_if = "Option::is_none")]
until: Option<String>,
},
// ---- privileged (manager socket only for now) ---------------------------
/// *(privileged)* Queue an approval to add a scheduled prompt.
@ -247,10 +218,6 @@ pub enum Response {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
matrix_accounts: Vec<MatrixIdentity>,
},
/// `GetHostJournal` result: host journal lines matching the
/// requested filters. Returned on the agent socket when the agent
/// holds the `read_host_journal` capability.
HostJournal { content: String },
/// `ListSchedules` result. Snapshot of the schedules the requester is
/// authorized to see — see `ListSchedules`'s own doc comment.
/// Returned on the manager socket only.

View file

@ -2259,8 +2259,8 @@ fn validate_forge_admin_arg(arg: &str) -> Result<()> {
/// ⚠️ This deliberately over-matches. A nix store hash is also a long
/// opaque run and will redact its line. That is the correct direction to
/// be wrong in: the cost of a false positive is one less log line, and
/// the cost of a false negative is a live credential in a journal that
/// any `read_host_journal` holder can read.
/// the cost of a false negative is a live credential sitting in the host
/// journal, readable by anything with access to it.
fn redact_secret_line(line: &str) -> std::borrow::Cow<'_, str> {
if line.to_ascii_lowercase().contains("password") {
return std::borrow::Cow::Borrowed("[redacted: line mentions a password]");

View file

@ -1,41 +0,0 @@
//! Journal-priority encoding for `GetHostJournal` (`read_host_journal`
//! capability). One small enum, own topic module rather than a
//! catch-all — see `hive-sh4re/README.md`'s module list.
use serde::{Deserialize, Serialize};
/// Syslog priority levels for `GetHostJournal`. Serialised as lowercase
/// strings matching journalctl `-p` accepted values.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, schemars::JsonSchema)]
#[serde(rename_all = "lowercase")]
pub enum JournalPriority {
Emerg,
Alert,
Crit,
Err,
Warning,
Notice,
Info,
Debug,
}
impl JournalPriority {
/// Returns the lowercase string journalctl expects for `-p`. Named
/// `as_journald_str` (not `as_str`) because this is a specific
/// external-tool encoding, not the enum's general wire/db string —
/// distinct call sites shouldn't reach for this by accident when they
/// actually want the serde wire representation.
#[must_use]
pub fn as_journald_str(self) -> &'static str {
match self {
Self::Emerg => "emerg",
Self::Alert => "alert",
Self::Crit => "crit",
Self::Err => "err",
Self::Warning => "warning",
Self::Notice => "notice",
Self::Info => "info",
Self::Debug => "debug",
}
}
}

View file

@ -5,7 +5,6 @@ pub mod assets;
pub mod bash_task;
pub mod container;
pub mod inbox;
pub mod journal;
pub mod manager;
pub mod paths;
pub mod permissions;

View file

@ -354,7 +354,7 @@ impl ToolGroup {
}
/// Per-agent capability grants. Stored in `meta/capabilities.json`
/// (same shape as `tool-groups.json`: `{ "alice": ["read_host_journal"] }`).
/// (same shape as `tool-groups.json`: `{ "alice": ["manage_root_agent"] }`).
/// 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).
@ -382,17 +382,12 @@ pub enum Capability {
/// into an unrecognised name that `capabilities::prune_unknown`
/// silently drops.
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,
}
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];
pub const ALL: &'static [Self] = &[Self::ManageRootAgent];
/// Short human-readable description suitable for a tooltip or help text.
#[must_use]
@ -401,7 +396,6 @@ impl Capability {
Self::ManageRootAgent => {
"manage any agent: every agent's state (rw) and config (ro) mounted for recovery"
}
Self::ReadHostJournal => "read host journald via get_host_journal MCP tool",
}
}
}