Watch
0
0
Fork
You've already forked hyperhive
0

Compare commits

...
Author SHA1 Message Date
atlas
6d7565a30d scheduling.md: drop the list_schedules prose, keep the approval-scope fix
mara's call on this PR was "list schedules not being scoped at all is a
bug - dont document it, file the bug and fix it". The bug is fixed in
damocles's separate PR, which also rewrites this page's
`list_schedules()` section.

So both of my paragraphs about scoping go: the "not scoped at all"
sentence in the intro (documenting the bug, which is what she
objected to) and the follow-up in the `list_schedules()` section. That
section is now byte-identical to main again, leaving it entirely to the
PR that changes the behaviour — the two PRs no longer touch a common
hunk in this file.

What stays is the claim this PR was actually filed for: the page said
"All scheduling ops go through the operator approval queue", and only
creating one does. The intro now splits creating from the other four
verbs and states the one authorization rule that covers all of them,
which the scoping fix makes true.
2026-09-11 18:43:10 +02:00
atlas
c28da210b4 scheduling: clamp get_logs host-side, and fix two authorization claims
`docs/tools/scheduling.md` said three things about who may do what. Two
were wrong prose; the third was the code.

"All scheduling ops go through the operator approval queue" — one of the
five does. Cancel, edit, list and fire are `require_group("scheduling")`
and nothing else (socket_server/mod.rs:594-643), which is what the MCP
tool descriptions already tell an agent. The page pushed in the cautious
direction: someone watching a runaway recurring schedule would wait for
an operator rather than cancel it themselves.

The authorization sentence covered "read/cancel/edit". Cancel, edit and
fire really do check `cancel_authorized` (self, operator, or subtree) from
three call sites. `handle_list_schedules` takes no requester at all and
returns every row — now stated, along with the part that matters: the
snapshot includes other agents' schedule bodies.

`lines` was documented as "host-capped at 500" and the 500 was in the
agent's own MCP layer, not the host; `handle_get_logs` passed any u32
straight into JournalQuery. A limit in the caller is not a limit, so the
host clamps instead of the sentence changing. That also makes args.rs's
arg doc and the tool description agents read correct, untouched. The
sibling `get_host_journal` already clamps host-side at 100, which is both
the precedent and the control that the missing clamp here was real.

Closes #4230.
2026-09-11 18:43:10 +02:00
2 changed files with 15 additions and 6 deletions

View file

@ -3,11 +3,17 @@
## `scheduling` tool group ## `scheduling` tool group
Scheduled prompts fan a message body out to one or more agent inboxes Scheduled prompts fan a message body out to one or more agent inboxes
at a future time, optionally recurring. All scheduling ops go through at a future time, optionally recurring.
the operator approval queue (even self-targeted schedules — use
`remind` for unapproved self-wake). Authorization for read/cancel/edit **Creating** one goes through the operator approval queue, even when it
ops: you can act on schedules you own or any owned by a sub-agent in targets only yourself — use `remind` for an unapproved self-wake. The
your topology subtree. other four verbs need no approval: holding the `scheduling` tool group
is the whole gate.
Authorization is one rule for all four verbs: you reach schedules you own
and any owned by an agent in your topology subtree. `list_schedules`
applies it too, so the snapshot only ever shows schedules you could also
cancel.
### `request_schedule_prompt(targets, body, first_fire_at_unix, interval_seconds?, description?)` ### `request_schedule_prompt(targets, body, first_fire_at_unix, interval_seconds?, description?)`

View file

@ -1121,7 +1121,10 @@ pub(crate) fn handle_send(
/// `-M` read needs root). `journalctl -M` wants the `h-<name>` machine /// `-M` read needs root). `journalctl -M` wants the `h-<name>` machine
/// name, which `container_name` derives. /// name, which `container_name` derives.
async fn handle_get_logs(agent: &str, lines: Option<u32>) -> Response { async fn handle_get_logs(agent: &str, lines: Option<u32>) -> Response {
let n = lines.unwrap_or(50); // Clamped here and not only in the MCP layer that also clamps it: a
// caller-side limit is not a limit, and anything speaking this socket
// sets `lines` itself. Mirrors `handle_get_host_journal`'s own cap.
let n = lines.unwrap_or(50).min(500);
let machine = crate::lifecycle::container_name(agent); let machine = crate::lifecycle::container_name(agent);
tracing::info!(%agent, %machine, %n, "manager: get_logs"); tracing::info!(%agent, %machine, %n, "manager: get_logs");
match crate::priv_client::read_container_journal( match crate::priv_client::read_container_journal(