swarm-dial: one swarm envelope (brake, heat, rate, run cap, k, shares) in a root-only store; read_envelope fails closed; Grant narrowing never widens (squash of eureka-body/swarm-dial 6963b75..46f7dd6; Soul s1)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Eureka 2026-10-29 20:28:57 +01:00
commit 49bfe17c18
6 changed files with 713 additions and 39 deletions

View file

@ -8,8 +8,15 @@
//! mind-control --instance <slug> brake release
//! mind-control --instance <slug> dial set --heat H --base-cooldown-s S --base-run-usd U
//! mind-control [--root <dir>] --instance <slug> check
//! mind-control [--root <dir>] swarm show
//! mind-control swarm brake hold | release
//! mind-control swarm dial set --heat H --base-rate R --base-run U --k K
//! mind-control swarm share set <instance> <weight>
//! ```
//!
//! The `swarm` verbs write `<root>/swarm.cc` and stop no unit: the swarm's
//! brake and heat 0 are read by the organs that act on the swarm's behalf.
//!
//! `check` exits 0 only when the brake is released and the dial in bounds; a
//! systemd unit runs it as its `ExecCondition`. It writes nothing.
//!
@ -27,11 +34,13 @@ use chrono::{DateTime, Utc};
use eureka_pipeline::{RunTurn, Slug};
use rust_decimal::Decimal;
use crate::control::{BurnRate, ControlWriter, Effective, FileSource, control_path, derive, load_state};
use crate::control::{
BurnRate, ControlWriter, Effective, FileSource, Share, SwarmDial, SwarmWriter, control_path, derive, derive_swarm, load_state, load_swarm_state, swarm_path,
};
use crate::launch::unit;
const USAGE: &str =
"usage: mind-control [--root <dir>] --instance <slug> show | check | brake hold | brake release | dial set --heat H --base-cooldown-s S --base-run-usd U";
"usage: mind-control [--root <dir>] --instance <slug> show | check | brake hold | brake release | dial set --heat H --base-cooldown-s S --base-run-usd U | mind-control [--root <dir>] swarm show | brake hold | brake release | dial set --heat H --base-rate R --base-run U --k K | share set <instance> <weight>";
/// The arguments every organ binary shares: `--instance <slug>` and an optional
/// `--endpoint rudp://<ip>:<port>`, which `env_endpoint` (the binary's reading of
@ -86,6 +95,11 @@ pub fn run(
[flag, dir, rest @ ..] if flag == "--root" => (Path::new(dir), rest),
_ => (root, args),
};
if let [word, rest @ ..] = args
&& word == "swarm"
{
return swarm(root, rest, set_by, now, out);
}
let (instance, command) = match args {
[flag, instance, command @ ..] if flag == "--instance" => (Slug(instance.clone()), command),
_ => bail!("{USAGE}"),
@ -117,6 +131,87 @@ pub fn run(
}
}
/// The `swarm` verbs over `<root>/swarm.cc`. Every refusal happens before the
/// writer is opened, so a refused command creates nothing.
fn swarm(root: &Path, args: &[String], set_by: &str, now: DateTime<Utc>, out: &mut impl Write) -> Result<()> {
let path = swarm_path(root);
let words: Vec<&str> = args.iter().map(String::as_str).collect();
match words.as_slice() {
["show"] => show_swarm(&path, out),
["brake", "hold"] => SwarmWriter::open(&path)?.set_brake(false, now, set_by),
["brake", "release"] => SwarmWriter::open(&path)?.set_brake(true, now, set_by),
["dial", "set", flags @ ..] => {
let dial = parse_swarm_dial(flags, now, set_by)?;
SwarmWriter::open(&path)?.set_dial(dial)
}
["share", "set", instance, weight] => {
let share = Share { instance: (*instance).to_string(), weight: Decimal::from_str(weight).context("the weight is not a decimal")? };
SwarmWriter::open(&path)?.set_share(share)
}
_ => bail!("{USAGE}"),
}
}
fn parse_swarm_dial(flags: &[&str], now: DateTime<Utc>, set_by: &str) -> Result<SwarmDial> {
let (mut heat, mut rate, mut run_usd, mut k) = (None, None, None, None);
let mut rest = flags.iter();
while let Some(flag) = rest.next() {
let value = rest.next().ok_or_else(|| anyhow!("{flag} needs a value"))?;
let slot = match *flag {
"--heat" => &mut heat,
"--base-rate" => &mut rate,
"--base-run" => &mut run_usd,
"--k" => &mut k,
other => bail!("unknown flag {other:?}"),
};
if slot.replace(*value).is_some() {
bail!("{flag} given twice");
}
}
Ok(SwarmDial {
heat: Decimal::from_str(need(heat, "--heat")?).context("--heat is not a decimal")?,
base_rate_usd_per_h: Decimal::from_str(need(rate, "--base-rate")?).context("--base-rate is not a decimal")?,
base_run_usd: Decimal::from_str(need(run_usd, "--base-run")?).context("--base-run is not a decimal")?,
k: need(k, "--k")?.parse().context("--k is not a whole number")?,
set_at: now,
set_by: set_by.to_string(),
})
}
fn show_swarm(path: &Path, out: &mut impl Write) -> Result<()> {
let source = FileSource::new(path);
writeln!(out, "swarm: {}", path.display())?;
match load_swarm_state(&source) {
Ok(state) => {
match &state.brake {
Some(brake) => writeln!(out, "brake: {} (set_at {}, set_by {})", if brake.released { "released" } else { "held" }, brake.set_at.to_rfc3339(), brake.set_by)?,
None => writeln!(out, "brake: absent")?,
}
match &state.dial {
Some(dial) => writeln!(
out,
"dial: heat {} base_rate_usd_per_h {} base_run_usd {} k {} (set_at {}, set_by {})",
dial.heat,
dial.base_rate_usd_per_h,
dial.base_run_usd,
dial.k,
dial.set_at.to_rfc3339(),
dial.set_by
)?,
None => writeln!(out, "dial: absent")?,
}
let mut shares: Vec<_> = state.shares.iter().collect();
shares.sort_by(|a, b| a.instance.cmp(&b.instance));
for share in shares {
writeln!(out, "share: {} {}", share.instance, share.weight)?;
}
}
Err(error) => writeln!(out, "store: undecodable ({error:#})")?,
}
writeln!(out, "effective: {:?}", derive_swarm(&source))?;
Ok(())
}
fn parse_dial(flags: &[&str], now: DateTime<Utc>, set_by: &str) -> Result<BurnRate> {
let (mut heat, mut cooldown, mut run_usd) = (None, None, None);
let mut rest = flags.iter();

View file

@ -26,6 +26,13 @@
//! the filesystem is the authority. The writer only refuses to write through an
//! existing directory, lock or store that is not root's or that group or world
//! can write (`UnsafeStore`); it checks no caller identity.
//!
//! The swarm has one more store of the same kind, `CONTROL_ROOT/swarm.cc`: a
//! file beside the instance directories, so no instance name reaches it. It
//! holds the swarm's brake, the swarm dial (heat, the spend rate, the per-run
//! cap and the concurrency) and each instance's share. `read_envelope` is its
//! one reader, fail closed like `read_effective`; `Envelope::grant_for` is the
//! only way to narrow the envelope into a `Grant`, and it never widens one.
use std::path::{Path, PathBuf};
use std::time::Duration;
@ -45,6 +52,14 @@ pub const CONTROL_ROOT: &str = "/etc/gamecult/minds";
pub const BRAKE_KEY: &str = "brake";
pub const DIAL_KEY: &str = "dial";
/// The swarm's store, a file directly under `CONTROL_ROOT`.
pub const SWARM_FILE: &str = "swarm.cc";
/// `<root>/swarm.cc`.
pub fn swarm_path(root: &Path) -> PathBuf {
root.join(SWARM_FILE)
}
/// `<root>/<instance>/control.cc`, after the leaf's slug grammar has passed, so
/// an instance name cannot walk out of the root.
pub fn control_path(root: &Path, instance: &Slug) -> Result<PathBuf> {
@ -116,6 +131,10 @@ pub enum HeldReason {
BrakeHeld,
DialAbsent,
DialOutOfBounds,
/// The swarm dial's heat is 0: the operator stopped everything.
HeatZero,
/// A share in the swarm store is out of bounds or not keyed by its instance.
ShareOutOfBounds,
Undecodable,
}
@ -258,6 +277,14 @@ fn registered_cache() -> Result<CultCache> {
Ok(cache)
}
fn swarm_cache() -> Result<CultCache> {
let mut cache = CultCache::new();
cache.register_entry_type::<Brake>()?;
cache.register_entry_type::<SwarmDial>()?;
cache.register_entry_type::<Share>()?;
Ok(cache)
}
/// Decodes the store into its two documents. Any envelope the cache does not
/// know, or any payload that does not decode, is an error: a store the reader
/// cannot fully read is not one it may act on.
@ -408,6 +435,44 @@ fn set_mode(_path: &Path, _mode: u32) -> Result<()> {
Ok(())
}
/// Before the first write: the control directory exists at 0755 and the
/// lock file at 0644, set explicitly because creation takes the umask.
/// A directory, lock or store that already exists must be root's and not
/// group- or world-writable, else the write is refused (`UnsafeStore`): the
/// operator fixes the install. Called after a setter's own refusals, so a
/// refused write creates nothing.
fn prepare_store(path: &Path) -> Result<()> {
let directory = path.parent().context("control path has no directory")?;
let mut lock_name = path.file_name().context("control path has no file name")?.to_os_string();
lock_name.push(".lock");
let lock = path.with_file_name(lock_name);
for existing in [directory, lock.as_path(), path] {
if existing.exists() {
require_root_only_writable(existing)?;
}
}
// Every directory this write creates is 0755, ancestors included: a
// restrictive process umask would otherwise leave /etc/gamecult/minds
// untraversable by the units that only read.
let missing: Vec<&Path> = directory.ancestors().take_while(|ancestor| !ancestor.exists()).collect();
if !missing.is_empty() {
std::fs::create_dir_all(directory).with_context(|| format!("failed to create {}", directory.display()))?;
for created in missing {
set_mode(created, DIR_MODE)?;
}
}
if !lock.exists() {
std::fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(false)
.open(&lock)
.with_context(|| format!("failed to open {}", lock.display()))?;
set_mode(&lock, FILE_MODE)?;
}
Ok(())
}
impl ControlWriter {
pub fn open(path: impl Into<PathBuf>) -> Result<Self> {
let path = path.into();
@ -417,42 +482,8 @@ impl ControlWriter {
Ok(Self { cache, path })
}
/// Before the first write: the control directory exists at 0755 and the
/// lock file at 0644, set explicitly because creation takes the umask.
/// A directory, lock or store that already exists must be root's and not
/// group- or world-writable, else the write is refused (`UnsafeStore`): the
/// operator fixes the install. Called after a setter's own refusals, so a
/// refused write creates nothing.
fn prepare(&self) -> Result<()> {
let directory = self.path.parent().context("control path has no directory")?;
let mut lock_name = self.path.file_name().context("control path has no file name")?.to_os_string();
lock_name.push(".lock");
let lock = self.path.with_file_name(lock_name);
for existing in [directory, lock.as_path(), self.path.as_path()] {
if existing.exists() {
require_root_only_writable(existing)?;
}
}
// Every directory this write creates is 0755, ancestors included: a
// restrictive process umask would otherwise leave /etc/gamecult/minds
// untraversable by the units that only read.
let missing: Vec<&Path> = directory.ancestors().take_while(|ancestor| !ancestor.exists()).collect();
if !missing.is_empty() {
std::fs::create_dir_all(directory).with_context(|| format!("failed to create {}", directory.display()))?;
for created in missing {
set_mode(created, DIR_MODE)?;
}
}
if !lock.exists() {
std::fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(false)
.open(&lock)
.with_context(|| format!("failed to open {}", lock.display()))?;
set_mode(&lock, FILE_MODE)?;
}
Ok(())
prepare_store(&self.path)
}
/// After a write: the store file the atomic rename put in place takes the
@ -480,3 +511,234 @@ impl ControlWriter {
self.settle()
}
}
// ---------------------------------------------------------------------------
// The swarm store: `CONTROL_ROOT/swarm.cc`.
// ---------------------------------------------------------------------------
/// The swarm dial: `heat` scales the whole swarm's activity, 0 stops it. The
/// base values say what heat 1.0 means: the total spend rate, the most one run
/// may spend, and `k`, how many turns may run at once. The envelope derives
/// rate and run cap from it, never stored.
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(type = "eureka.control.swarm_dial.v1", schema = "ControlSwarmDial")]
pub struct SwarmDial {
#[cultcache(key = 0)]
pub heat: Decimal,
#[cultcache(key = 1)]
pub base_rate_usd_per_h: Decimal,
#[cultcache(key = 2)]
pub base_run_usd: Decimal,
#[cultcache(key = 3)]
pub k: u32,
#[cultcache(key = 4)]
pub set_at: DateTime<Utc>,
#[cultcache(key = 5)]
pub set_by: String,
}
impl SwarmDial {
/// The one bounds rule for the swarm dial: the reader holds on it and the
/// CLI refuses on it. Heat 0 is in bounds (it is the stop); the reader
/// answers it as held.
pub fn in_bounds(&self) -> bool {
self.heat >= Decimal::ZERO && self.heat <= heat_max() && self.base_rate_usd_per_h > Decimal::ZERO && self.base_run_usd > Decimal::ZERO && self.k >= 1
}
}
/// One instance's share of the swarm, keyed in the store by the instance name.
/// An instance with no share has weight 1.
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
#[cultcache(type = "eureka.control.share.v1", schema = "ControlShare")]
pub struct Share {
#[cultcache(key = 0)]
pub instance: String,
#[cultcache(key = 1)]
pub weight: Decimal,
}
impl Share {
/// A share is a slug-named instance with a positive weight.
pub fn in_bounds(&self) -> bool {
Slug(self.instance.clone()).validate_slug().is_ok() && self.weight > Decimal::ZERO
}
}
/// The swarm store's contents, decoded and not yet judged.
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct SwarmState {
pub brake: Option<Brake>,
pub dial: Option<SwarmDial>,
pub shares: Vec<Share>,
}
/// What the swarm store allows right now.
#[derive(Debug, PartialEq, Eq)]
pub enum SwarmEffective {
Held(HeldReason),
Released(Envelope),
}
/// The swarm's budget while the brake is released and heat is above 0. Its
/// fields are private and only `read_envelope` makes one, so a run cap derived
/// from it exists only as the reading of the root-owned swarm store.
#[derive(Debug, PartialEq, Eq)]
pub struct Envelope {
rate_usd_per_h: Decimal,
run_cap_usd: Decimal,
k: u32,
shares: std::collections::BTreeMap<String, Decimal>,
}
impl Envelope {
/// The swarm's total spend rate: `base_rate_usd_per_h x heat`.
pub fn rate_usd_per_h(&self) -> Decimal {
self.rate_usd_per_h
}
/// The most any one run may spend: `base_run_usd x heat`.
pub fn run_cap_usd(&self) -> Decimal {
self.run_cap_usd
}
/// How many turns may run at once.
pub fn k(&self) -> u32 {
self.k
}
/// The instance's weight; 1 when the store holds no share for it.
pub fn share(&self, instance: &Slug) -> Decimal {
self.shares.get(&instance.0).copied().unwrap_or(Decimal::ONE)
}
/// Narrows this envelope and the instance's own reading into the Grant for
/// one run of `budget_usd`. It is the only new way to build a Grant and it
/// never widens: `None` unless the instance's own reading is released for
/// this instance and `0 < budget_usd` is within both the swarm's run cap and
/// the instance's own. The Grant's cap is the budget.
pub fn grant_for(&self, instance: &Slug, instance_effective: &Effective, budget_usd: Decimal) -> Option<Grant> {
let Effective::Released(own) = instance_effective else {
return None;
};
if own.instance != *instance || budget_usd <= Decimal::ZERO || budget_usd > self.run_cap_usd || budget_usd > own.run_cap_usd {
return None;
}
Some(Grant { instance: instance.clone(), cadence: own.cadence, run_cap_usd: budget_usd })
}
}
impl SwarmState {
/// The one derivation. Order: brake, dial bounds, heat, shares.
pub(crate) fn effective(&self) -> SwarmEffective {
let held = SwarmEffective::Held;
let Some(brake) = &self.brake else {
return held(HeldReason::BrakeAbsent);
};
if !brake.released {
return held(HeldReason::BrakeHeld);
}
let Some(dial) = &self.dial else {
return held(HeldReason::DialAbsent);
};
if !dial.in_bounds() {
return held(HeldReason::DialOutOfBounds);
}
if dial.heat.is_zero() {
return held(HeldReason::HeatZero);
}
if !self.shares.iter().all(Share::in_bounds) {
return held(HeldReason::ShareOutOfBounds);
}
match (dial.base_rate_usd_per_h.checked_mul(dial.heat), dial.base_run_usd.checked_mul(dial.heat)) {
(Some(rate_usd_per_h), Some(run_cap_usd)) => SwarmEffective::Released(Envelope {
rate_usd_per_h,
run_cap_usd,
k: dial.k,
shares: self.shares.iter().map(|share| (share.instance.clone(), share.weight)).collect(),
}),
_ => held(HeldReason::DialOutOfBounds),
}
}
}
/// Decodes the swarm store. An envelope the cache does not know, a payload that
/// does not decode or a share not keyed by its own instance is an error.
pub fn load_swarm_state(source: &impl ControlSource) -> Result<SwarmState> {
let mut cache = swarm_cache()?;
for envelope in source.snapshot().context("swarm store cannot be read")? {
cache.put_raw_envelope(envelope)?;
}
let mut shares = Vec::new();
for (key, share) in cache.get_all_with_keys::<Share>()? {
anyhow::ensure!(key == share.instance, "a share is not keyed by its instance");
shares.push(share);
}
Ok(SwarmState { brake: cache.get::<Brake>(BRAKE_KEY)?, dial: cache.get::<SwarmDial>(DIAL_KEY)?, shares })
}
/// The one reader of the swarm store, at `CONTROL_ROOT/swarm.cc`; the caller
/// supplies neither a root, a path nor a source. Anything but a decodable store
/// holding a released brake, an in-bounds dial with heat above 0 and in-bounds
/// shares is `Held`, with the reason.
pub fn read_envelope() -> SwarmEffective {
derive_swarm(&FileSource::new(swarm_path(&control_root())))
}
/// The derivation over any source; the CLI's `show` is its other caller.
pub(crate) fn derive_swarm(source: &impl ControlSource) -> SwarmEffective {
match load_swarm_state(source) {
Ok(state) => state.effective(),
Err(_) => SwarmEffective::Held(HeldReason::Undecodable),
}
}
/// The operator's writer over the swarm file, with the same root-only refusals
/// as `ControlWriter`. Each setter replaces its document whole.
pub struct SwarmWriter {
cache: CultCache,
path: PathBuf,
}
impl SwarmWriter {
pub fn open(path: impl Into<PathBuf>) -> Result<Self> {
let path = path.into();
let mut cache = swarm_cache()?;
cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&path))?;
cache.pull_all_backing_stores()?;
Ok(Self { cache, path })
}
fn settle(&self) -> Result<()> {
set_mode(&self.path, FILE_MODE)
}
pub fn set_brake(&mut self, released: bool, set_at: DateTime<Utc>, set_by: &str) -> Result<()> {
let brake = Brake { released, set_at, set_by: set_by.to_string() };
prepare_store(&self.path)?;
self.cache.put(BRAKE_KEY, &brake)?;
self.settle()
}
/// Refuses an out-of-bounds dial before anything is written.
pub fn set_dial(&mut self, dial: SwarmDial) -> Result<()> {
if !dial.in_bounds() {
anyhow::bail!(
"swarm dial refused: heat must be within 0..={}, base_rate_usd_per_h and base_run_usd above 0, k at least 1",
heat_max()
);
}
prepare_store(&self.path)?;
self.cache.put(DIAL_KEY, &dial)?;
self.settle()
}
/// Refuses a share for a non-slug instance or a weight at or below 0.
pub fn set_share(&mut self, share: Share) -> Result<()> {
if !share.in_bounds() {
anyhow::bail!("share refused: the instance must be a slug and the weight above 0");
}
prepare_store(&self.path)?;
self.cache.put(share.instance.clone(), &share)?;
self.settle()
}
}

View file

@ -425,3 +425,221 @@ mod grant {
assert!(format!("{error:#}").contains("asked the mind"), "{error:#}");
}
}
/// The swarm store: `<root>/swarm.cc`, read by `read_envelope`.
mod swarm {
use super::*;
fn swarm_file(dir: &Path) -> PathBuf {
swarm_path(dir)
}
fn swarm_dial(heat: &str, rate: &str, run_usd: &str, k: u32) -> SwarmDial {
SwarmDial { heat: dec(heat), base_rate_usd_per_h: dec(rate), base_run_usd: dec(run_usd), k, set_at: at(0), set_by: "op".into() }
}
fn share(instance: &str, weight: &str) -> Share {
Share { instance: instance.into(), weight: dec(weight) }
}
/// A released swarm store with the given dial, through the real writer.
fn released_swarm(dir: &Path, dial: SwarmDial) -> PathBuf {
let path = swarm_file(dir);
let mut writer = SwarmWriter::open(&path).unwrap();
writer.set_brake(true, at(0), "op").unwrap();
writer.set_dial(dial).unwrap();
path
}
fn envelope_at(dir: &Path) -> SwarmEffective {
with_root(dir, read_envelope)
}
fn released(dir: &Path) -> Envelope {
match envelope_at(dir) {
SwarmEffective::Released(envelope) => envelope,
other => panic!("expected a released envelope, got {other:?}"),
}
}
/// A swarm dial written around the writer's bounds check.
fn write_unchecked(path: &Path, dial: &SwarmDial) {
let mut cache = swarm_cache().unwrap();
cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(path)).unwrap();
cache.pull_all_backing_stores().unwrap();
cache.put(DIAL_KEY, dial).unwrap();
}
fn write_unchecked_share(path: &Path, key: &str, share: &Share) {
let mut cache = swarm_cache().unwrap();
cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(path)).unwrap();
cache.pull_all_backing_stores().unwrap();
cache.put(key, share).unwrap();
}
fn slug(name: &str) -> Slug {
Slug(name.into())
}
#[test]
fn missing_or_corrupt_swarm_store_is_held() {
let dir = tempfile::tempdir().unwrap();
// No file at all.
assert_eq!(envelope_at(dir.path()), SwarmEffective::Held(HeldReason::BrakeAbsent));
// Bytes that are not a store.
std::fs::write(swarm_file(dir.path()), b"not a cultcache store").unwrap();
assert_eq!(envelope_at(dir.path()), SwarmEffective::Held(HeldReason::Undecodable));
std::fs::remove_file(swarm_file(dir.path())).unwrap();
// A brake and no dial.
let path = swarm_file(dir.path());
SwarmWriter::open(&path).unwrap().set_brake(true, at(0), "op").unwrap();
assert_eq!(envelope_at(dir.path()), SwarmEffective::Held(HeldReason::DialAbsent));
// Every out-of-bounds dial, written around the writer.
for (what, bad) in [
("negative heat", swarm_dial("-0.1", "5", "2", 2)),
("heat above the maximum", swarm_dial("2.1", "5", "2", 2)),
("zero rate", swarm_dial("1", "0", "2", 2)),
("zero run cap", swarm_dial("1", "5", "0", 2)),
("zero k", swarm_dial("1", "5", "2", 0)),
] {
write_unchecked(&path, &bad);
assert_eq!(envelope_at(dir.path()), SwarmEffective::Held(HeldReason::DialOutOfBounds), "{what}");
}
// Heat 0 is in bounds and stops everything.
write_unchecked(&path, &swarm_dial("0", "5", "2", 2));
assert_eq!(envelope_at(dir.path()), SwarmEffective::Held(HeldReason::HeatZero));
}
#[test]
fn a_held_swarm_brake_stops_everything_whatever_the_dial_says() {
let dir = tempfile::tempdir().unwrap();
let path = released_swarm(dir.path(), swarm_dial("1", "5", "2", 2));
let _ = released(dir.path());
SwarmWriter::open(&path).unwrap().set_brake(false, at(60), "alice").unwrap();
assert_eq!(envelope_at(dir.path()), SwarmEffective::Held(HeldReason::BrakeHeld));
SwarmWriter::open(&path).unwrap().set_brake(true, at(120), "bob").unwrap();
let _ = released(dir.path());
// Heat 0 stops it again, with the brake released.
SwarmWriter::open(&path).unwrap().set_dial(swarm_dial("0", "5", "2", 2)).unwrap();
assert_eq!(envelope_at(dir.path()), SwarmEffective::Held(HeldReason::HeatZero));
// The dial survived the brake writes, provenance included.
let state = load_swarm_state(&FileSource::new(&path)).unwrap();
assert_eq!(state.brake.unwrap().set_by, "bob");
assert_eq!(state.dial.unwrap().heat, dec("0"));
}
#[test]
fn a_share_that_is_out_of_bounds_or_not_keyed_by_its_instance_holds_the_swarm() {
let dir = tempfile::tempdir().unwrap();
let path = released_swarm(dir.path(), swarm_dial("1", "5", "2", 2));
write_unchecked_share(&path, "weksa", &share("weksa", "0"));
assert_eq!(envelope_at(dir.path()), SwarmEffective::Held(HeldReason::ShareOutOfBounds));
write_unchecked_share(&path, "weksa", &share("weksa", "-1"));
assert_eq!(envelope_at(dir.path()), SwarmEffective::Held(HeldReason::ShareOutOfBounds));
write_unchecked_share(&path, "weksa", &share("weksa", "1"));
let _ = released(dir.path());
write_unchecked_share(&path, "mallory", &share("weksa", "9"));
assert_eq!(envelope_at(dir.path()), SwarmEffective::Held(HeldReason::Undecodable));
}
#[test]
fn envelope_scales_by_heat() {
for (heat, rate, run_cap) in [("0.5", "2.5", "1.25"), ("2", "10", "5")] {
let dir = tempfile::tempdir().unwrap();
released_swarm(dir.path(), swarm_dial(heat, "5", "2.5", 3));
let envelope = released(dir.path());
assert_eq!(envelope.rate_usd_per_h(), dec(rate), "rate at heat {heat}");
assert_eq!(envelope.run_cap_usd(), dec(run_cap), "run cap at heat {heat}");
assert_eq!(envelope.k(), 3);
}
}
#[test]
fn a_missing_share_is_weight_one_and_a_set_share_is_read_back() {
let dir = tempfile::tempdir().unwrap();
let path = released_swarm(dir.path(), swarm_dial("1", "5", "2", 2));
SwarmWriter::open(&path).unwrap().set_share(share("weksa", "3")).unwrap();
let envelope = released(dir.path());
assert_eq!(envelope.share(&slug("weksa")), dec("3"));
assert_eq!(envelope.share(&slug("eureka")), Decimal::ONE);
}
/// An instance's own released reading, with a run cap of `run_usd`.
fn instance_reading(instance: &str, run_usd: &str) -> Effective {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join(instance).join("control.cc");
let mut writer = ControlWriter::open(&path).unwrap();
writer.set_brake(true, at(0), "op").unwrap();
writer.set_dial(dial("1", 600, run_usd)).unwrap();
with_root(dir.path(), || read_effective(&slug(instance)))
}
#[test]
fn grant_for_never_exceeds_cap() {
let dir = tempfile::tempdir().unwrap();
released_swarm(dir.path(), swarm_dial("1", "5", "2", 2));
let envelope = released(dir.path());
let eureka = slug("eureka");
let own = instance_reading("eureka", "10");
for (budget, expected) in [("2.01", None), ("100", None), ("0", None), ("-1", None), ("2", Some("2")), ("0.5", Some("0.5")), ("0.01", Some("0.01"))] {
let grant = envelope.grant_for(&eureka, &own, dec(budget));
assert_eq!(grant.map(|g| g.run_cap_usd()), expected.map(dec), "budget {budget}");
}
let grant = envelope.grant_for(&eureka, &own, dec("1")).unwrap();
assert_eq!(grant.instance(), &eureka);
assert_eq!(grant.cadence(), Duration::from_secs(600), "the instance cadence is carried, not invented");
}
#[test]
fn grant_for_does_not_widen_the_instance_own_cap() {
let dir = tempfile::tempdir().unwrap();
released_swarm(dir.path(), swarm_dial("1", "5", "2", 2));
let envelope = released(dir.path());
let eureka = slug("eureka");
let own = instance_reading("eureka", "0.5");
assert!(envelope.grant_for(&eureka, &own, dec("0.51")).is_none());
assert_eq!(envelope.grant_for(&eureka, &own, dec("0.5")).unwrap().run_cap_usd(), dec("0.5"));
}
#[test]
fn grant_for_refuses_held_instance() {
let dir = tempfile::tempdir().unwrap();
released_swarm(dir.path(), swarm_dial("1", "5", "2", 2));
let envelope = released(dir.path());
let eureka = slug("eureka");
assert!(envelope.grant_for(&eureka, &Effective::Held { reason: HeldReason::BrakeHeld }, dec("1")).is_none());
// Another instance released reading is no proof for this one.
let weksa_reading = instance_reading("weksa", "10");
assert!(envelope.grant_for(&eureka, &weksa_reading, dec("1")).is_none());
assert!(envelope.grant_for(&slug("weksa"), &weksa_reading, dec("1")).is_some());
}
#[cfg(unix)]
#[test]
fn the_swarm_store_is_written_by_root_only() {
use std::os::unix::fs::PermissionsExt;
let dir = tempfile::tempdir().unwrap();
let path = released_swarm(dir.path(), swarm_dial("1", "5", "2", 2));
let before = std::fs::read(&path).unwrap();
for mode in [0o666, 0o664] {
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(mode)).unwrap();
let error = SwarmWriter::open(&path).unwrap().set_dial(swarm_dial("0", "5", "2", 2)).unwrap_err();
assert!(error.downcast_ref::<UnsafeStore>().is_some(), "mode {mode:o}: {error:#}");
}
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o644)).unwrap();
assert_eq!(std::fs::read(&path).unwrap(), before, "a refused write changed the store");
}
#[test]
fn the_swarm_writer_refuses_out_of_bounds_values_and_writes_nothing() {
let dir = tempfile::tempdir().unwrap();
let path = swarm_file(dir.path());
let mut writer = SwarmWriter::open(&path).unwrap();
assert!(writer.set_dial(swarm_dial("2.5", "5", "2", 2)).is_err());
assert!(writer.set_dial(swarm_dial("1", "5", "2", 0)).is_err());
assert!(writer.set_share(share("weksa", "0")).is_err());
assert!(writer.set_share(share("Not A Slug", "1")).is_err());
assert!(!path.exists(), "a refused write created the store");
assert!(std::fs::read_dir(dir.path()).unwrap().next().is_none(), "a refused write created a sibling");
}
}

View file

@ -454,3 +454,65 @@ mod shared_arguments {
}
}
}
fn swarm_show(root: &std::path::Path) -> String {
let (result, out) = go(root, &["swarm", "show"], "op", 0);
result.unwrap();
out
}
#[test]
fn swarm_verbs_round_trip() {
let root = tempfile::tempdir().unwrap();
let shown = swarm_show(root.path());
assert!(shown.contains("brake: absent") && shown.contains("dial: absent") && shown.contains("BrakeAbsent"), "{shown}");
for args in [
&["swarm", "brake", "release"][..],
&["swarm", "dial", "set", "--heat", "1.5", "--base-rate", "4", "--base-run", "2", "--k", "3"],
&["swarm", "share", "set", "weksa", "2.5"],
] {
go(root.path(), args, "alice", 5).0.unwrap();
}
let shown = swarm_show(root.path());
assert!(shown.contains("brake: released") && shown.contains("set_by alice"), "{shown}");
assert!(shown.contains("dial: heat 1.5 base_rate_usd_per_h 4 base_run_usd 2 k 3"), "{shown}");
assert!(shown.contains("share: weksa 2.5"), "{shown}");
assert!(shown.contains("Released") && shown.contains("rate_usd_per_h: 6.0") && shown.contains("run_cap_usd: 3.0"), "{shown}");
// Heat 0 stops the swarm and is one command away; the hold stops it too.
go(root.path(), &["swarm", "dial", "set", "--heat", "0", "--base-rate", "4", "--base-run", "2", "--k", "3"], "alice", 6).0.unwrap();
assert!(swarm_show(root.path()).contains("HeatZero"));
go(root.path(), &["swarm", "brake", "hold"], "alice", 7).0.unwrap();
assert!(swarm_show(root.path()).contains("BrakeHeld"));
}
#[test]
fn swarm_verbs_write_only_the_swarm_file_and_never_call_the_stopper() {
let root = tempfile::tempdir().unwrap();
let (result, _, calls) = go_stopping(root.path(), &["swarm", "brake", "hold"], "op", 0, |_| Ok(()));
result.unwrap();
assert!(calls.is_empty(), "the swarm hold has no instance to stop: {calls:?}");
let names = listing(root.path());
assert!(names.iter().all(|name| name.starts_with("swarm.cc")), "{names:?}");
}
#[test]
fn swarm_commands_refuse_bad_input_and_write_nothing() {
let root = tempfile::tempdir().unwrap();
for args in [
&["swarm"][..],
&["swarm", "dial", "set", "--heat", "1"],
&["swarm", "dial", "set", "--heat", "3", "--base-rate", "4", "--base-run", "2", "--k", "3"],
&["swarm", "dial", "set", "--heat", "1", "--base-rate", "4", "--base-run", "2", "--k", "0"],
&["swarm", "dial", "set", "--heat", "1", "--heat", "1", "--base-rate", "4", "--base-run", "2", "--k", "3"],
&["swarm", "share", "set", "weksa", "0"],
&["swarm", "share", "set", "weksa", "heavy"],
&["swarm", "share", "set", "Not A Slug", "1"],
&["swarm", "share", "set", "weksa"],
&["swarm", "brake", "release", "now"],
] {
assert!(go(root.path(), args, "op", 0).0.is_err(), "{args:?} was accepted");
}
assert!(listing(root.path()).is_empty(), "a refused command wrote something");
}

View file

@ -19,7 +19,7 @@ use std::time::Duration;
use chrono::{DateTime, TimeZone, Utc};
use eureka_pipeline::Slug;
use mind_body::control::{BurnRate, ControlWriter, FileSource, control_path, load_state};
use mind_body::control::{BurnRate, ControlWriter, FileSource, SwarmWriter, control_path, load_state, swarm_path};
use rust_decimal::Decimal;
use std::str::FromStr;
@ -112,6 +112,9 @@ mod modes {
"brake" => {
ControlWriter::open(&path).unwrap().set_brake(true, at(0), "op").unwrap();
}
"swarm_brake" => {
SwarmWriter::open(swarm_path(Path::new(&std::env::var(ROOT).unwrap()))).unwrap().set_brake(true, at(0), "op").unwrap();
}
"attacker" => {
// Spins on an open-for-write of the live store; any success is a hit.
let stop = std::env::var("MIND_BODY_STOP").unwrap();
@ -182,6 +185,14 @@ mod modes {
}
}
#[test]
fn the_swarm_store_is_created_0644_whatever_the_operators_umask() {
for umask in ["000", "022", "077"] {
let (dir, _) = written_under("swarm_brake", umask);
assert_eq!(mode(&swarm_path(dir.path())), 0o644, "swarm store under umask {umask}");
}
}
fn running_as_root() -> bool {
Command::new("id").arg("-u").output().map(|out| String::from_utf8_lossy(&out.stdout).trim() == "0").unwrap_or(false)
}

View file

@ -7,16 +7,21 @@ mind_body: mod wake
mind_body::cli: fn instance_and_endpoint (args : & [String] , env_endpoint : Option < String > , usage : & str) -> Result < (Slug , SocketAddr) >
mind_body::cli: fn operator_name (sudo_user : Option < String > , user : Option < String >) -> String
mind_body::cli: fn run (args : & [String] , root : & Path , set_by : & str , now : DateTime < Utc > , out : & mut impl Write , stop_units : & mut dyn FnMut (& Slug) -> Result < () > ,) -> Result < () >
mind_body::control: # [derive (Clone , Copy , Debug , PartialEq , Eq)] pub enum HeldReason { BrakeAbsent , BrakeHeld , DialAbsent , DialOutOfBounds , Undecodable , }
mind_body::control: # [derive (Clone , Copy , Debug , PartialEq , Eq)] pub enum HeldReason { BrakeAbsent , BrakeHeld , DialAbsent , DialOutOfBounds , HeatZero , ShareOutOfBounds , Undecodable , }
mind_body::control: # [derive (Debug , PartialEq , Eq)] pub enum Effective { Held { reason : HeldReason } , Released (Grant) , }
mind_body::control: # [derive (Debug , PartialEq , Eq)] pub enum SwarmEffective { Held (HeldReason) , Released (Envelope) , }
mind_body::control: const BRAKE_KEY : & str
mind_body::control: const CONTROL_ROOT : & str
mind_body::control: const DIAL_KEY : & str
mind_body::control: const SWARM_FILE : & str
mind_body::control: fn control_path (root : & Path , instance : & Slug) -> Result < PathBuf >
mind_body::control: fn heat_max () -> Decimal
mind_body::control: fn heat_min () -> Decimal
mind_body::control: fn load_state (source : & impl ControlSource) -> Result < ControlState >
mind_body::control: fn load_swarm_state (source : & impl ControlSource) -> Result < SwarmState >
mind_body::control: fn read_effective (instance : & Slug) -> Effective
mind_body::control: fn read_envelope () -> SwarmEffective
mind_body::control: fn swarm_path (root : & Path) -> PathBuf
mind_body::control: impl BurnRate
mind_body::control: impl BurnRate: fn in_bounds (& self) -> bool
mind_body::control: impl ControlSource for FileSource
@ -25,12 +30,28 @@ mind_body::control: impl ControlWriter
mind_body::control: impl ControlWriter: fn open (path : impl Into < PathBuf >) -> Result < Self >
mind_body::control: impl ControlWriter: fn set_brake (& mut self , released : bool , set_at : DateTime < Utc > , set_by : & str) -> Result < () >
mind_body::control: impl ControlWriter: fn set_dial (& mut self , dial : BurnRate) -> Result < () >
mind_body::control: impl Envelope
mind_body::control: impl Envelope: fn grant_for (& self , instance : & Slug , instance_effective : & Effective , budget_usd : Decimal) -> Option < Grant >
mind_body::control: impl Envelope: fn k (& self) -> u32
mind_body::control: impl Envelope: fn rate_usd_per_h (& self) -> Decimal
mind_body::control: impl Envelope: fn run_cap_usd (& self) -> Decimal
mind_body::control: impl Envelope: fn share (& self , instance : & Slug) -> Decimal
mind_body::control: impl FileSource
mind_body::control: impl FileSource: fn new (path : impl Into < PathBuf >) -> Self
mind_body::control: impl Grant
mind_body::control: impl Grant: fn cadence (& self) -> Duration
mind_body::control: impl Grant: fn instance (& self) -> & Slug
mind_body::control: impl Grant: fn run_cap_usd (& self) -> Decimal
mind_body::control: impl Share
mind_body::control: impl Share: fn in_bounds (& self) -> bool
mind_body::control: impl SwarmDial
mind_body::control: impl SwarmDial: fn in_bounds (& self) -> bool
mind_body::control: impl SwarmState
mind_body::control: impl SwarmWriter
mind_body::control: impl SwarmWriter: fn open (path : impl Into < PathBuf >) -> Result < Self >
mind_body::control: impl SwarmWriter: fn set_brake (& mut self , released : bool , set_at : DateTime < Utc > , set_by : & str) -> Result < () >
mind_body::control: impl SwarmWriter: fn set_dial (& mut self , dial : SwarmDial) -> Result < () >
mind_body::control: impl SwarmWriter: fn set_share (& mut self , share : Share) -> Result < () >
mind_body::control: impl std :: error :: Error for UnsafeStore
mind_body::control: impl std :: fmt :: Display for UnsafeStore
mind_body::control: pub trait ControlSource { fn snapshot (& self) -> Result < Vec < CultCacheEnvelope > > ; }
@ -38,8 +59,13 @@ mind_body::control: struct Brake # [derive (Clone , Debug , PartialEq , Eq , Dat
mind_body::control: struct BurnRate # [derive (Clone , Debug , PartialEq , Eq , DatabaseEntry)] pub-fields[heat: Decimal, base_cooldown_s: u32, base_run_usd: Decimal, set_at: DateTime < Utc >, set_by: String]
mind_body::control: struct ControlState # [derive (Clone , Debug , PartialEq , Eq , Default)] pub-fields[brake: Option < Brake >, dial: Option < BurnRate >]
mind_body::control: struct ControlWriter pub-fields[]
mind_body::control: struct Envelope # [derive (Debug , PartialEq , Eq)] pub-fields[]
mind_body::control: struct FileSource pub-fields[]
mind_body::control: struct Grant # [derive (Debug , PartialEq , Eq)] pub-fields[]
mind_body::control: struct Share # [derive (Clone , Debug , PartialEq , Eq , DatabaseEntry)] pub-fields[instance: String, weight: Decimal]
mind_body::control: struct SwarmDial # [derive (Clone , Debug , PartialEq , Eq , DatabaseEntry)] pub-fields[heat: Decimal, base_rate_usd_per_h: Decimal, base_run_usd: Decimal, k: u32, set_at: DateTime < Utc >, set_by: String]
mind_body::control: struct SwarmState # [derive (Clone , Debug , PartialEq , Eq , Default)] pub-fields[brake: Option < Brake >, dial: Option < SwarmDial >, shares: Vec < Share >]
mind_body::control: struct SwarmWriter pub-fields[]
mind_body::control: struct UnsafeStore # [derive (Debug)] pub-fields[path: PathBuf, mode: u32, uid: u32]
mind_body::launch: # [derive (Clone , Debug , PartialEq , Eq)] pub enum Declined { Admission (MindRefusal) , Conflict , AlreadyAdmitted , StartFailed , GrantForOtherInstance , PersonaRunTakesNoClaims , }
mind_body::launch: # [derive (Clone , Debug , PartialEq , Eq)] pub enum LaunchOutcome { Launched { run : PipelineRef , } , NotQueued { item : PipelineRef , } , Tripped { item : PipelineRef , question : PipelineRef , } , Busy { run : PipelineRef , } , Refused (Declined) , }