hive-c0re: scrub attribution cookies across background-task modules (#715 batch 6)

This commit is contained in:
damocles 2026-05-31 17:36:41 +02:00 committed by mara
commit d62a9518c0
4 changed files with 25 additions and 28 deletions

View file

@ -10,7 +10,7 @@
//! on a fresh spawn / purge). //! on a fresh spawn / purge).
//! //!
//! `NeedsUpdate` events are now fired from the apply-commit path //! `NeedsUpdate` events are now fired from the apply-commit path
//! directly rather than via rev-marker polling (issue #179 cleanup). //! directly rather than via rev-marker polling.
//! //!
//! D-Bus subscription would be lower-latency for the first axis, //! D-Bus subscription would be lower-latency for the first axis,
//! but polling is simpler and a 10s detection delay is fine. //! but polling is simpler and a 10s detection delay is fine.
@ -29,7 +29,7 @@ const POLL_INTERVAL: Duration = Duration::from_secs(10);
/// for the purpose of suppressing crash events. Three full /// for the purpose of suppressing crash events. Three full
/// `POLL_INTERVAL`s gives the post-lifecycle path comfortable /// `POLL_INTERVAL`s gives the post-lifecycle path comfortable
/// breathing room — the watcher will have polled at least twice /// breathing room — the watcher will have polled at least twice
/// inside the window even with worst-case timer skew (#425). /// inside the window even with worst-case timer skew.
const RECENT_TRANSIENT_GRACE: Duration = Duration::from_secs(30); const RECENT_TRANSIENT_GRACE: Duration = Duration::from_secs(30);
pub fn spawn(coord: Arc<Coordinator>) { pub fn spawn(coord: Arc<Coordinator>) {
@ -101,7 +101,7 @@ fn emit_crash_transitions(coord: &Coordinator, prev: &HashSet<String>, current:
let transients = coord.transient_snapshot(); let transients = coord.transient_snapshot();
// Operator actions whose RAII guard already cleared but only just; // Operator actions whose RAII guard already cleared but only just;
// suppresses the race where `lifecycle::kill` returns + drops the // suppresses the race where `lifecycle::kill` returns + drops the
// guard between two crash-watch polls (closes #425). // guard between two crash-watch polls.
let recent = coord.recent_transient_within(RECENT_TRANSIENT_GRACE); let recent = coord.recent_transient_within(RECENT_TRANSIENT_GRACE);
for stopped in prev.difference(current) { for stopped in prev.difference(current) {
let active = transients.get(stopped).map(|st| st.kind); let active = transients.get(stopped).map(|st| st.kind);
@ -118,8 +118,8 @@ fn emit_crash_transitions(coord: &Coordinator, prev: &HashSet<String>, current:
} }
/// Pure classifier: did the operator stop / restart / destroy / /// Pure classifier: did the operator stop / restart / destroy /
/// rebuild this container, or did it crash? Splits the matcher out so /// rebuild this container, or did it crash? Splits the matcher out
/// it has a focused unit test (#425) without needing a Coordinator /// so it has a focused unit test without needing a Coordinator
/// fixture. `active` is the currently-set transient (if any), /// fixture. `active` is the currently-set transient (if any),
/// `recently_cleared` is one whose RAII guard dropped within the /// `recently_cleared` is one whose RAII guard dropped within the
/// grace window. /// grace window.
@ -197,8 +197,8 @@ mod tests {
#[test] #[test]
fn deliberate_when_recent_transient_is_operator_kind() { fn deliberate_when_recent_transient_is_operator_kind() {
// Race the #425 bug repros: lifecycle action completes + drops // Race repros: lifecycle action completes + drops the guard
// the guard between two polls. recent_transient catches it. // between two polls. recent_transient catches it.
for kind in [ for kind in [
TransientKind::Stopping, TransientKind::Stopping,
TransientKind::Restarting, TransientKind::Restarting,

View file

@ -82,17 +82,17 @@ enum Cmd {
Approve { id: i64 }, Approve { id: i64 },
/// Deny a pending request by id. /// Deny a pending request by id.
Deny { id: i64 }, Deny { id: i64 },
/// Move an agent in the topology tree (#486). Set `--parent` to /// Move an agent in the topology tree. Set `--parent` to a new
/// a new parent agent name; pass `--root` to promote the agent /// parent agent name; pass `--root` to promote the agent to root
/// to root (no parent). Refuses cycles, unknown agents, and /// (no parent). Refuses cycles, unknown agents, and any attempt
/// any attempt to reparent the manager. /// to reparent the manager.
SetParent { SetParent {
child: String, child: String,
/// New parent agent name. Mutually exclusive with `--root`. /// New parent agent name. Mutually exclusive with `--root`.
/// Exactly one of `--parent` / `--root` is required — clap /// Exactly one of `--parent` / `--root` is required — clap
/// rejects both-absent calls so a fat-fingered /// rejects both-absent calls so a fat-fingered
/// `hive-c0re set-parent alice` doesn't silently promote /// `hive-c0re set-parent alice` doesn't silently promote
/// alice to root (argus flag on PR #492). /// alice to root.
#[arg(long, conflicts_with = "root", required_unless_present = "root")] #[arg(long, conflicts_with = "root", required_unless_present = "root")]
parent: Option<String>, parent: Option<String>,
/// Promote `child` to root (no parent). /// Promote `child` to root (no parent).
@ -213,9 +213,9 @@ async fn cmd_serve(
}); });
// Matrix user sweep: same shape — ensure every container has // Matrix user sweep: same shape — ensure every container has
// an account on the local matrix-tuwunel homeserver with an // an account on the local matrix-tuwunel homeserver with an
// access_token persisted to `<state>/matrix-token` (#548). No-op // access_token persisted to `<state>/matrix-token`. No-op when
// when the hive-matrix container isn't running. Backgrounded // the hive-matrix container isn't running. Backgrounded because
// because UIAA is a two-roundtrip dance per agent. // UIAA is a two-roundtrip dance per agent.
tokio::spawn(async move { tokio::spawn(async move {
matrix::ensure_all().await; matrix::ensure_all().await;
}); });
@ -259,15 +259,14 @@ async fn cmd_serve(
// rebuilding, then having its harness bind the socket) without // rebuilding, then having its harness bind the socket) without
// needing an explicit hook on each container start. write() is // needing an explicit hook on each container start. write() is
// idempotent so steady-state cost is one stat per agent per // idempotent so steady-state cost is one stat per agent per
// tick. closes atlas's #813 concern that agents in the JSON // tick. See `docs/gateway.md::Per-agent unix-socket upstream`.
// would 502 the gateway until they actually opt in.
agent_sockets::spawn_poll(); agent_sockets::spawn_poll();
// Reminder scheduler: drains due reminders + handles // Reminder scheduler: drains due reminders + handles
// file_path payload persistence. See reminder_scheduler.rs. // file_path payload persistence. See reminder_scheduler.rs.
reminder_scheduler::spawn(coord.clone()); reminder_scheduler::spawn(coord.clone());
// Scheduled-prompts worker: drains due scheduled_prompts rows // Scheduled-prompts worker: drains due scheduled_prompts rows
// and fans the body out to each active target's inbox. See // and fans the body out to each active target's inbox. See
// scheduled_prompts_worker.rs (#444). // scheduled_prompts_worker.rs.
scheduled_prompts_worker::spawn(coord.clone()); scheduled_prompts_worker::spawn(coord.clone());
// Rebuild-queue worker: drains the global rebuild/meta-update/ // Rebuild-queue worker: drains the global rebuild/meta-update/
// spawn queue FIFO so hive-c0re never runs two heavyweight // spawn queue FIFO so hive-c0re never runs two heavyweight

View file

@ -199,8 +199,7 @@ pub fn write_payload(agent: &str, host_path: &Path, message: &str) -> Result<(),
/// under. Every agent sees its state at `/agents/<container>/state/` /// under. Every agent sees its state at `/agents/<container>/state/`
/// (see `lifecycle::set_nspawn_flags`). Auto-file paths use the same /// (see `lifecycle::set_nspawn_flags`). Auto-file paths use the same
/// prefix so the round-trip is symmetric. The manager logical name /// prefix so the round-trip is symmetric. The manager logical name
/// maps to its container name (`hm1nd`) per `lifecycle::MANAGER_NAME` — /// maps to its container name (`hm1nd`) per `lifecycle::MANAGER_NAME`.
/// the pre-#604 legacy `/state/` alias is gone.
#[must_use] #[must_use]
pub fn container_state_prefix(agent: &str) -> String { pub fn container_state_prefix(agent: &str) -> String {
if agent == hive_sh4re::MANAGER_AGENT { if agent == hive_sh4re::MANAGER_AGENT {
@ -277,16 +276,15 @@ mod tests {
#[test] #[test]
fn manager_uses_container_name_prefix() { fn manager_uses_container_name_prefix() {
// Post-#604: manager's container view of its state is at // Manager's container view of its state is at
// `/agents/<MANAGER_NAME>/state/` (= `/agents/hm1nd/state/`), // `/agents/<MANAGER_NAME>/state/` (= `/agents/hm1nd/state/`),
// same as every other agent — the legacy bare `/state/` mount // same as every other agent.
// was dropped from lifecycle::set_nspawn_flags.
assert_eq!(container_state_prefix("manager"), "/agents/hm1nd/state/"); assert_eq!(container_state_prefix("manager"), "/agents/hm1nd/state/");
let p = resolve_host_path("manager", "/agents/hm1nd/state/reminders/x.md").unwrap(); let p = resolve_host_path("manager", "/agents/hm1nd/state/reminders/x.md").unwrap();
// NB: the host path still resolves under `agents/manager/` // NB: the host path still resolves under `agents/manager/`
// (Coordinator::agent_notes_dir takes the broker LOGICAL name). // (Coordinator::agent_notes_dir takes the broker LOGICAL name).
// That's a pre-existing manager-logical-vs-container-name // That's a pre-existing manager-logical-vs-container-name
// discrepancy tracked separately in #162; out of scope here. // discrepancy tracked separately; out of scope here.
assert_eq!( assert_eq!(
p, p,
PathBuf::from("/var/lib/hyperhive/agents/manager/state/reminders/x.md") PathBuf::from("/var/lib/hyperhive/agents/manager/state/reminders/x.md")

View file

@ -1,6 +1,6 @@
//! Background loop that drains due `scheduled_prompts` rows //! Background loop that drains due `scheduled_prompts` rows
//! (#444) and fans the body out as inbox `Message`s to each //! and fans the body out as inbox `Message`s to each active
//! active target. Mirrors `reminder_scheduler::spawn` shape: //! target. Mirrors `reminder_scheduler::spawn` shape:
//! single `spawn(coord)` entry, 5s poll cadence, shutdown-aware. //! single `spawn(coord)` entry, 5s poll cadence, shutdown-aware.
//! //!
//! ## Catch-up semantics //! ## Catch-up semantics
@ -264,8 +264,8 @@ pub struct FireNowReport {
pub one_shot_consumed: bool, pub one_shot_consumed: bool,
} }
/// Manual / out-of-band fire of a scheduled prompt (#467 "fire /// Manual / out-of-band fire of a scheduled prompt ("fire now"
/// now" button). Mirrors the per-target fan-out of `fire_schedule` /// dashboard button). Mirrors the per-target fan-out of `fire_schedule`
/// but skips the rearm step entirely — manual fires don't disturb /// but skips the rearm step entirely — manual fires don't disturb
/// a recurring schedule's rhythm. For one-shots, a manual fire /// a recurring schedule's rhythm. For one-shots, a manual fire
/// **consumes** the schedule (operator intent: "send this now, /// **consumes** the schedule (operator intent: "send this now,