crates/monetize-guardian/src/fleet.rs
//! **The pull channel's front door** (G1-pull): `Fleet.Report`, `Fleet.Orders`, `Fleet.Ask`.
//!
//! Every call is a [`FleetCall`] signed by the calling box's own Ed25519 key, registered for
//! `(instance, role)` at install. Anyone else is refused in one sentence that names nothing.
//! Monetize answers; it never calls back.
use monetize_embed::fleet::{call, verify_fleet_call, Ask, FleetCall, OrdersAfter, Report, SignedOrder, LONG_POLL_MAX_MS};
use monetize_product::guardian::{Finding, Remedy};
use crate::runner::Guardian;
/// Why a box's call is refused.
#[derive(Clone, PartialEq, Eq, Debug, thiserror::Error)]
pub enum FleetRefusal {
/// Unknown estate or box, wrong product, bad signature, stale: all said the same way.
#[error("the call is not signed by a box this monetize knows")]
Unauthenticated,
#[error("{0}")]
Malformed(String),
#[error("{0}")]
Unavailable(String),
/// Heard and refused by name: what the box asked is not its to have (a disk that is not
/// its own, more than is paid).
#[error("{0}")]
Refused(String),
}
/// What `Fleet.Ask` answers at once; anything that changes the estate comes later as orders.
#[derive(Clone, PartialEq, Eq, Debug, Default)]
pub struct AskAnswer {
pub case_id: String,
pub words: String,
}
/// The remedies a box may ask for.
fn asked_remedy(kind: &str) -> Option<Remedy> {
Remedy::parse(kind)
}
impl Guardian {
/// The caller is the box it says it is, and the call is the one it says.
pub fn authenticate(&self, c: &FleetCall, signature: &[u8], want: &str, now_unix_ms: u64) -> Result<(), FleetRefusal> {
if c.call != want {
return Err(FleetRefusal::Malformed(format!("a {:?} call on the {want} door", c.call)));
}
if self.store.product_of(&c.instance).as_deref() != Some(c.product.as_str()) {
return Err(FleetRefusal::Unauthenticated);
}
let key = self.store.key_of(&c.instance, &c.role).ok_or(FleetRefusal::Unauthenticated)?;
verify_fleet_call(c, signature, &key, now_unix_ms).map_err(|_| FleetRefusal::Unauthenticated)
}
/// **A box's first call (TOFU)**: when no key is registered for `(instance, role)` and a
/// pin window is open for it (a sold estate's install, a case that just laid that role),
/// `box_key` is pinned if the call verifies under it, and the window closes. `Ok(false)`:
/// the key presented is the one registered (nothing to pin). Outside a window: refused.
///
/// ★ PIN-1 (prod audit 2026-10-12): a window open while a key IS registered — the
/// estate's owner said its boxes were laid AGAIN — lets a box presenting a NEW key
/// replace the old one, once (the window closes on the re-pin): a reinstalled box mints
/// a new fleet key on its new data disk and was refused for ever.
pub fn first_call(&self, c: &FleetCall, signature: &[u8], box_key: &[u8], now_unix_ms: u64) -> Result<bool, FleetRefusal> {
if self.store.product_of(&c.instance).as_deref() != Some(c.product.as_str()) {
return Err(FleetRefusal::Unauthenticated);
}
let open = self.store.pin_window(&c.instance, &c.role) >= now_unix_ms;
if let Some(pinned) = self.store.key_of(&c.instance, &c.role) {
if pinned.as_bytes().as_slice() == box_key {
return Ok(false);
}
if !open {
return Err(FleetRefusal::Unauthenticated);
}
}
if !open {
return Err(FleetRefusal::Unauthenticated);
}
let raw: [u8; 32] = box_key.try_into().map_err(|_| FleetRefusal::Unauthenticated)?;
let key = ed25519_dalek::VerifyingKey::from_bytes(&raw).map_err(|_| FleetRefusal::Unauthenticated)?;
verify_fleet_call(c, signature, &key, now_unix_ms).map_err(|_| FleetRefusal::Unauthenticated)?;
let b64 = monetize_embed::signing::base64_encode(&raw);
self.store.register_key(&c.instance, &c.role, &b64).map_err(|e| FleetRefusal::Unavailable(e.to_string()))?;
self.store.open_pin(&c.instance, &c.role, 0).map_err(|e| FleetRefusal::Unavailable(e.to_string()))?;
if let Some(mut case) = self.store.open_case(&c.instance) {
case.say(now_unix_ms, format!("the new {} called home: its key pinned on its first call", c.role));
// The key is pinned whatever the case's log says: the box is not refused for it.
if let Err(err) = self.store.put_case(&case) {
eprintln!("monetize-guardian: ALERT guardian.case-unsaved {}: the call home of the new {} is not in its log ({err})", case.id, c.role);
}
}
Ok(true)
}
/// **`Fleet.Report`**: the box's heartbeat and its acks. Answers the seqs it acked.
pub fn report(&self, c: &FleetCall, signature: &[u8], now_unix_ms: u64) -> Result<Vec<u64>, FleetRefusal> {
self.authenticate(c, signature, call::REPORT, now_unix_ms)?;
let report: Report = if c.body.trim().is_empty() { Report::default() } else { serde_json::from_str(&c.body).map_err(|e| FleetRefusal::Malformed(format!("the report is not a report: {e}")))? };
self.store.record_report(&c.instance, &c.role, now_unix_ms, &report).map_err(|e| FleetRefusal::Unavailable(e.to_string()))
}
/// **`Fleet.Orders`**, once: the signed orders queued for the calling box after its
/// `after`, and how long the caller may hold the call open waiting for one.
pub fn orders(&self, c: &FleetCall, signature: &[u8], now_unix_ms: u64) -> Result<(Vec<SignedOrder>, u64), FleetRefusal> {
self.authenticate(c, signature, call::ORDERS, now_unix_ms)?;
let ask: OrdersAfter = if c.body.trim().is_empty() { OrdersAfter::default() } else { serde_json::from_str(&c.body).map_err(|e| FleetRefusal::Malformed(format!("not an orders request: {e}")))? };
Ok((self.store.orders_for(&c.instance, &c.role, ask.after), ask.wait_ms.min(LONG_POLL_MAX_MS)))
}
/// **`Fleet.Ask`**: the estate asks for a remedy (`new_twin`, `new_server`,
/// `recharge_from_twin`, `review_twin`). It opens a case — approved only by a human or
/// the estate's own auto-fix opt-in, never by the asking box.
pub fn ask(&self, c: &FleetCall, signature: &[u8], now_unix_ms: u64) -> Result<AskAnswer, FleetRefusal> {
self.authenticate(c, signature, call::ASK, now_unix_ms)?;
let ask: Ask = serde_json::from_str(&c.body).map_err(|e| FleetRefusal::Malformed(format!("not an ask: {e}")))?;
let remedy = asked_remedy(&ask.kind).ok_or_else(|| FleetRefusal::Malformed(format!("{:?} is nothing an estate may ask for: new_twin, new_server, recharge_from_twin or review_twin", ask.kind)))?;
if let Some(open) = self.store.open_case(&c.instance) {
return Ok(AskAnswer { case_id: open.id.clone(), words: format!("case {} is already {}", open.id, open.state.as_str()) });
}
let finding = Finding {
code: format!("asked:{}", remedy.as_str()),
role: c.role.clone(),
words: format!("the {} asked for {}", c.role, remedy.words().to_lowercase()),
evidence: vec![format!("Fleet.Ask from {}: {}", c.role, ask.body)],
remedies: vec![remedy],
auto: Some(remedy),
};
let case = self.open(&c.instance, &c.product, finding, now_unix_ms).map_err(|e| FleetRefusal::Unavailable(e.to_string()))?;
Ok(AskAnswer { case_id: case.id.clone(), words: format!("case {} opened; a human (or the estate's auto-fix) decides", case.id) })
}
}