mind-body control: Released carries a Grant only read_effective makes
The run cap and cadence leave the control reader only inside a Grant with private fields, so a cap cannot exist without a released brake and an in-bounds dial. effective() is private; its one caller is read_effective. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
8f41db55e0
commit
ed5fc195d2
2 changed files with 61 additions and 28 deletions
|
|
@ -1,5 +1,9 @@
|
|||
//! The operator's brake and burn-rate dial, and the one reader of them.
|
||||
//!
|
||||
//! `Released` carries a `Grant`, the only value that hands a run cap to the
|
||||
//! launch: it cannot be built outside this module, so a cap exists only as the
|
||||
//! reading of a released brake and an in-bounds dial.
|
||||
//!
|
||||
//! State is a CultCache single-file store at
|
||||
//! `/etc/gamecult/minds/<instance>/control.cc`, root-owned and world-readable.
|
||||
//! The operator's CLI replaces a document whole; every organ that acts calls
|
||||
|
|
@ -39,9 +43,7 @@ pub const DIAL_KEY: &str = "dial";
|
|||
/// `<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> {
|
||||
instance
|
||||
.validate_slug()
|
||||
.map_err(|refusal| anyhow::anyhow!("instance {:?} is not a slug: {refusal:?}", instance.0))?;
|
||||
instance.validate_slug().map_err(|refusal| anyhow::anyhow!("instance {:?} is not a slug: {refusal:?}", instance.0))?;
|
||||
Ok(root.join(&instance.0).join("control.cc"))
|
||||
}
|
||||
|
||||
|
|
@ -113,10 +115,36 @@ pub enum HeldReason {
|
|||
}
|
||||
|
||||
/// What the operator's store allows right now.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Effective {
|
||||
Held { reason: HeldReason },
|
||||
Released { cadence: Duration, run_cap_usd: Decimal },
|
||||
Released(Grant),
|
||||
}
|
||||
|
||||
/// The proof that the brake was released and the dial in bounds when read. Its
|
||||
/// fields are private and only `read_effective` makes one; `open_and_launch`
|
||||
/// takes it by value, so one reading opens at most one run.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Grant {
|
||||
cadence: Duration,
|
||||
run_cap_usd: Decimal,
|
||||
}
|
||||
|
||||
impl Grant {
|
||||
/// How often she may wake.
|
||||
pub fn cadence(&self) -> Duration {
|
||||
self.cadence
|
||||
}
|
||||
|
||||
/// The most one run may spend.
|
||||
pub fn run_cap_usd(&self) -> Decimal {
|
||||
self.run_cap_usd
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn for_test(cadence: Duration, run_cap_usd: Decimal) -> Self {
|
||||
Self { cadence, run_cap_usd }
|
||||
}
|
||||
}
|
||||
|
||||
/// The store's contents, decoded and not yet judged.
|
||||
|
|
@ -129,18 +157,22 @@ pub struct ControlState {
|
|||
impl ControlState {
|
||||
/// The one derivation. Order: brake first, then dial; each failing rung is
|
||||
/// its own reason.
|
||||
pub fn effective(&self) -> Effective {
|
||||
fn effective(&self) -> Effective {
|
||||
let held = |reason| Effective::Held { reason };
|
||||
let Some(brake) = &self.brake else { return held(HeldReason::BrakeAbsent) };
|
||||
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) };
|
||||
let Some(dial) = &self.dial else {
|
||||
return held(HeldReason::DialAbsent);
|
||||
};
|
||||
if !dial.in_bounds() {
|
||||
return held(HeldReason::DialOutOfBounds);
|
||||
}
|
||||
match (dial.cadence(), dial.run_cap_usd()) {
|
||||
(Some(cadence), Some(run_cap_usd)) => Effective::Released { cadence, run_cap_usd },
|
||||
(Some(cadence), Some(run_cap_usd)) => Effective::Released(Grant { cadence, run_cap_usd }),
|
||||
_ => held(HeldReason::DialOutOfBounds),
|
||||
}
|
||||
}
|
||||
|
|
@ -213,8 +245,7 @@ const FILE_MODE: u32 = 0o644;
|
|||
#[cfg(unix)]
|
||||
fn set_mode(path: &Path, mode: u32) -> Result<()> {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
std::fs::set_permissions(path, std::fs::Permissions::from_mode(mode))
|
||||
.with_context(|| format!("failed to set the mode of {}", path.display()))
|
||||
std::fs::set_permissions(path, std::fs::Permissions::from_mode(mode)).with_context(|| format!("failed to set the mode of {}", path.display()))
|
||||
}
|
||||
|
||||
/// A control path the writer will not write through: it is not root's, or
|
||||
|
|
@ -326,11 +357,7 @@ impl ControlWriter {
|
|||
/// never holds one that the CLI put there.
|
||||
pub fn set_dial(&mut self, dial: BurnRate) -> Result<()> {
|
||||
if !dial.in_bounds() {
|
||||
anyhow::bail!(
|
||||
"dial refused: heat must be within {}..={}, base_cooldown_s above 0 and base_run_usd above 0",
|
||||
heat_min(),
|
||||
heat_max()
|
||||
);
|
||||
anyhow::bail!("dial refused: heat must be within {}..={}, base_cooldown_s above 0 and base_run_usd above 0", heat_min(), heat_max());
|
||||
}
|
||||
self.prepare()?;
|
||||
self.cache.put(DIAL_KEY, &dial)?;
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ fn an_undecodable_store_reads_as_held() {
|
|||
|
||||
// The untouched store is still released, so the two reads above were the damage.
|
||||
std::fs::write(&path, &good).unwrap();
|
||||
assert!(matches!(effective(&path), Effective::Released { .. }));
|
||||
assert!(matches!(effective(&path), Effective::Released(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -180,7 +180,7 @@ fn dial_bounds_are_inclusive_at_both_ends() {
|
|||
let path = released_store_unchecked(dir.path(), dial(heat, 3600, "1"));
|
||||
let got = effective(&path);
|
||||
if expected_in {
|
||||
assert!(matches!(got, Effective::Released { .. }), "heat {heat}: {got:?}");
|
||||
assert!(matches!(got, Effective::Released(_)), "heat {heat}: {got:?}");
|
||||
} else {
|
||||
assert_eq!(got, held(HeldReason::DialOutOfBounds), "heat {heat}");
|
||||
}
|
||||
|
|
@ -196,7 +196,7 @@ fn a_zero_base_cooldown_or_a_non_positive_run_cost_is_out_of_bounds() {
|
|||
}
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let smallest = released_store_unchecked(dir.path(), dial("1", 1, "0.000001"));
|
||||
assert!(matches!(effective(&smallest), Effective::Released { .. }));
|
||||
assert!(matches!(effective(&smallest), Effective::Released(_)));
|
||||
}
|
||||
|
||||
fn released_store_unchecked(dir: &Path, bad: BurnRate) -> PathBuf {
|
||||
|
|
@ -218,7 +218,9 @@ fn cadence_and_run_cap_are_the_dial_divided_and_multiplied_by_heat() {
|
|||
] {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = released_store(dir.path(), dial(heat, cooldown, run_usd));
|
||||
assert_eq!(effective(&path), Effective::Released { cadence, run_cap_usd: cap }, "heat {heat} cooldown {cooldown} run {run_usd}");
|
||||
let Effective::Released(grant) = effective(&path) else { panic!("heat {heat} cooldown {cooldown} run {run_usd}: not released") };
|
||||
assert_eq!(grant.cadence(), cadence, "heat {heat} cooldown {cooldown} run {run_usd}");
|
||||
assert_eq!(grant.run_cap_usd(), cap, "heat {heat} cooldown {cooldown} run {run_usd}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -226,7 +228,7 @@ fn cadence_and_run_cap_are_the_dial_divided_and_multiplied_by_heat() {
|
|||
fn the_operators_last_write_is_what_is_in_force_and_provenance_round_trips() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = released_store(dir.path(), dial("1", 3600, "1"));
|
||||
assert!(matches!(effective(&path), Effective::Released { .. }));
|
||||
assert!(matches!(effective(&path), Effective::Released(_)));
|
||||
|
||||
ControlWriter::open(&path).unwrap().set_brake(false, at(60), "alice").unwrap();
|
||||
assert_eq!(effective(&path), held(HeldReason::BrakeHeld));
|
||||
|
|
@ -237,7 +239,7 @@ fn the_operators_last_write_is_what_is_in_force_and_provenance_round_trips() {
|
|||
assert_eq!(state.dial.unwrap().set_by, "op");
|
||||
|
||||
ControlWriter::open(&path).unwrap().set_brake(true, at(120), "bob").unwrap();
|
||||
assert!(matches!(effective(&path), Effective::Released { .. }));
|
||||
assert!(matches!(effective(&path), Effective::Released(_)));
|
||||
let brake = load_state(&FileSource::new(&path)).unwrap().brake.unwrap();
|
||||
assert_eq!((brake.released, brake.set_at, brake.set_by.as_str()), (true, at(120), "bob"));
|
||||
}
|
||||
|
|
@ -283,7 +285,7 @@ fn reading_writes_nothing_and_takes_no_lock_file() {
|
|||
}
|
||||
}
|
||||
let before = (listing(dir.path()), std::fs::read(&path).unwrap());
|
||||
assert!(matches!(effective(&path), Effective::Released { .. }));
|
||||
assert!(matches!(effective(&path), Effective::Released(_)));
|
||||
assert_eq!((listing(dir.path()), std::fs::read(&path).unwrap()), before);
|
||||
}
|
||||
|
||||
|
|
@ -348,7 +350,9 @@ mod modes {
|
|||
/// nothing, so a plain run of the suite passes it vacuously.
|
||||
#[test]
|
||||
fn mode_child() {
|
||||
let Ok(role) = std::env::var(CHILD) else { return };
|
||||
let Ok(role) = std::env::var(CHILD) else {
|
||||
return;
|
||||
};
|
||||
let instance = std::env::var("MIND_BODY_INSTANCE").unwrap_or_else(|_| "eureka".into());
|
||||
let path = Path::new(&std::env::var(ROOT).unwrap()).join(instance).join("control.cc");
|
||||
match role.as_str() {
|
||||
|
|
@ -386,12 +390,14 @@ mod modes {
|
|||
}
|
||||
"intruder" => {
|
||||
assert!(ControlWriter::open(&path).and_then(|mut writer| writer.set_brake(false, at(1), "intruder")).is_err());
|
||||
assert!(ControlWriter::open(Path::new(&std::env::var(ROOT).unwrap()).join("ghost").join("control.cc"))
|
||||
.and_then(|mut writer| writer.set_brake(true, at(1), "intruder"))
|
||||
.is_err());
|
||||
assert!(
|
||||
ControlWriter::open(Path::new(&std::env::var(ROOT).unwrap()).join("ghost").join("control.cc"))
|
||||
.and_then(|mut writer| writer.set_brake(true, at(1), "intruder"))
|
||||
.is_err()
|
||||
);
|
||||
assert!(std::fs::OpenOptions::new().write(true).open(&path).is_err());
|
||||
assert!(std::fs::OpenOptions::new().write(true).open(path.with_file_name("control.cc.lock")).is_err());
|
||||
assert!(matches!(effective(&path), Effective::Released { .. }), "a non-root user can still read the store");
|
||||
assert!(matches!(effective(&path), Effective::Released(_)), "a non-root user can still read the store");
|
||||
}
|
||||
other => panic!("unknown role {other}"),
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue