Skip to main content

holger_fleet/
exec.rs

1//! **The order executor**: one verified order → one of holger's own verbs → one ack.
2//!
3//! The host hands a [`HolgerBox`] (holger-server-lib's `fleet_box`, a test's fake). Every
4//! verb defaults to a refusal BY NAME, so a box that cannot do one says why instead of
5//! the guardian waiting. The money is checked HERE as well as in the box: an entitlement,
6//! a snapshot and a growth step are verified under the monetize key before the box is
7//! asked to move anything.
8
9use std::collections::BTreeMap;
10
11use monetize_embed::fleet::{GrowGoAhead, OrderAck};
12use monetize_product::pull::{AddPrincipal, GrowRoom, PurgeAnswer, PurgeTenant};
13use monetize_product::EntitlementFact;
14
15use crate::order::Order;
16
17pub use crate::order::Recharge as RechargeAsk;
18
19/// 512-byte sectors in a GiB.
20pub const SECTORS_PER_GIB: u64 = 2_097_152;
21
22/// What an entitlement did on the box.
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub enum Applied {
25    /// Stored: the words say what.
26    New(String),
27    /// The box already holds this fact or a newer one: nothing moved.
28    Held(String),
29}
30
31/// Where the grow machine stands, as a pulled growth reads it.
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum GrowState {
34    Running(String),
35    /// `/data` is off; the machine waits for the members to come back larger.
36    WaitingMembers(String),
37    Done(String),
38    Failed(String),
39}
40
41/// **`Fleet.Ask{grow_set}`**: monetize sizes THIS box's own data disks.
42pub trait GrowSet {
43    fn grow_set(&mut self, gib: u64) -> Result<String, String>;
44}
45
46/// A caller with no channel to ask on.
47pub struct NoAsk;
48impl GrowSet for NoAsk {
49    fn grow_set(&mut self, _: u64) -> Result<String, String> {
50        Err("this box has no pull channel to ask monetize for its disks".into())
51    }
52}
53
54/// **Holger's verbs, as an order reaches them.** Sync: the executor runs on a jera job's
55/// blocking thread.
56pub trait HolgerBox {
57    /// This box serves as the primary (not a twin, not awaiting its recharge).
58    fn serving(&self) -> bool;
59    /// The box's health words for `Fleet.Report` (`serving`, `data`, `serves_at`, `twin`, `epoch`).
60    fn health(&self) -> BTreeMap<String, String> {
61        BTreeMap::new()
62    }
63    /// `monetize_product::pull::ProductReport` as JSON — the main that serves sends it.
64    fn product_report(&self) -> Option<String> {
65        None
66    }
67    /// A twin reviews itself on every report (G12): its review as JSON.
68    fn own_review(&self) -> Option<String> {
69        None
70    }
71    /// Store one VERIFIED fact, newer wins.
72    fn entitle(&self, fact: &EntitlementFact) -> Result<Applied, String> {
73        Err(format!(
74            "this box holds no entitlements, so the fact for {} is not applied",
75            fact.tenant.0
76        ))
77    }
78    /// Purge one repository (or answer its purge's state).
79    fn purge(&self, p: &PurgeTenant) -> Result<PurgeAnswer, String> {
80        Err(format!(
81            "this box purges no repositories, so {} is not purged",
82            p.tenant
83        ))
84    }
85    /// **A sold estate's resend sign-in link** (Gunnar's S10): mint a fresh first-login invite
86    /// for the order's account and mail it to the order's address. Default: this box mints none.
87    fn sign_in_again(&self, s: &crate::order::SignInAgain) -> Result<String, String> {
88        let _ = (&s.to, &s.host, s.ttl_secs);
89        Err(format!(
90            "this box mints no sign-in links, so it cannot run sign_in_again for {}",
91            s.account
92        ))
93    }
94    /// **Keep the estate keys fed again** (monetize MAIL-RELAY + NIGHT-PUBREPO 2026-10-28): kept
95    /// beside the feed the box was laid with, which they go over. Default: this box keeps none.
96    fn keep_refeed(&self, r: &crate::order::Refeed) -> Result<String, String> {
97        Err(format!(
98            "this box keeps no estate keys fed again, so {} is not kept",
99            r.keys.keys().cloned().collect::<Vec<_>>().join(", ")
100        ))
101    }
102    /// A paid signup's repository (`a.username`), bound to `a.signup` (idempotent); answers the
103    /// namespace made. A name bound to another signup is refused starting `taken`.
104    fn add_principal(&self, a: &AddPrincipal) -> Result<String, String> {
105        Err(format!(
106            "this box makes no repositories for a signup, so {} is not made",
107            a.username
108        ))
109    }
110    /// Keep the room wanted past the set and tell the operator.
111    fn want_room(&self, g: &GrowRoom) -> Result<String, String> {
112        Err(format!(
113            "this box grows no data set, so {} bytes wanted cannot be kept",
114            g.need_bytes
115        ))
116    }
117    /// Do the box's data disks already carry `data_gib` a disk?
118    fn grow_carried(&self, data_gib: u64) -> Result<bool, String> {
119        Err(format!(
120            "this box grows no data set, so it cannot grow to {data_gib} GiB"
121        ))
122    }
123    /// The twin first (a main only): Ok once the twin holds main's whole set.
124    fn twin_holds(&self, data_gib: u64) -> Result<String, String> {
125        Err(format!(
126            "this box cannot see whether its twin holds {data_gib} GiB a disk"
127        ))
128    }
129    /// Start the growth to `data_gib` a disk under a one-time `nonce` (the step was verified).
130    fn grow_start(&self, data_gib: u64, nonce: &str) -> Result<GrowState, String> {
131        let _ = (data_gib, nonce);
132        Err("this box grows no data set".into())
133    }
134    /// Wait for the grow machine to stop moving (past `waiting-members` when told).
135    fn grow_settle(&self, past_waiting: bool) -> GrowState {
136        let _ = past_waiting;
137        GrowState::Failed("this box grows no data set".into())
138    }
139    /// Look at the members again (`give_up`: mount back what is there).
140    fn grow_look_again(&self, give_up: bool) -> Result<(), String> {
141        let _ = give_up;
142        Err("this box grows no data set".into())
143    }
144    /// Fill `r.main` from `r.twin` (holger's recharge lane).
145    fn recharge(&self, r: &RechargeAsk) -> Result<String, String> {
146        Err(format!(
147            "this box runs no recharge lane, so {} is not filled from {}",
148            r.main, r.twin
149        ))
150    }
151    /// Fence the old primary at `primary` (this box itself when it names it): it refuses
152    /// every write from now on. Refused by name by default.
153    fn fence(&self, primary: &str, epoch: u64) -> Result<String, String> {
154        let _ = epoch;
155        Err(format!("this box cannot fence the primary at {primary}: the guardian fences it at the cloud (the recharge's `fenced` proof)"))
156    }
157    /// Certify the twin at `twin` for this primary.
158    fn adopt(&self, twin: &str, principal: &str) -> Result<String, String> {
159        let _ = principal;
160        Err(format!(
161            "this box cannot certify the twin at {twin}: the front's certification does"
162        ))
163    }
164    /// Take the twin role: follow `primary` (a FRESH box only, exactly once).
165    fn become_twin(&self, primary: &str) -> Result<String, String> {
166        Err(format!("this box cannot become the twin of {primary}"))
167    }
168    /// Review the twin (on a twin: itself), as JSON.
169    fn review(&self) -> Result<serde_json::Value, String> {
170        Err("this box cannot review a twin".into())
171    }
172}
173
174/// What the executor knows about the order's context: the shared pump's own (P4a).
175pub use monetize_estate_side::pump::Ctx;
176
177fn said(seq: u64, r: Result<String, String>) -> OrderAck {
178    match r {
179        Ok(detail) => OrderAck {
180            seq,
181            ok: true,
182            detail,
183            receipt_json: String::new(),
184        },
185        Err(detail) => OrderAck {
186            seq,
187            ok: false,
188            detail,
189            receipt_json: String::new(),
190        },
191    }
192}
193
194/// **Run one verified order** and say what came of it — the ack the next Report carries.
195/// Never panics, never retries: the guardian reads the ack.
196pub fn execute<B: HolgerBox + ?Sized>(
197    b: &B,
198    ctx: &Ctx,
199    seq: u64,
200    order: &Order,
201    ask: &mut dyn GrowSet,
202) -> OrderAck {
203    let not_serving = || {
204        Err::<String, String>("this box does not serve as the primary (a twin, or awaiting its recharge): nothing applied".into())
205    };
206    match order {
207        Order::Entitlement(fact) => {
208            if !b.serving() {
209                return said(seq, not_serving());
210            }
211            if let Err(e) = monetize_embed::signing::verify_fact(fact, ctx.key) {
212                return said(seq, Err(format!("the fact for {} does not verify under this box's monetize key ({e}): nothing moved", fact.tenant.0)));
213            }
214            said(
215                seq,
216                b.entitle(fact).map(|a| match a {
217                    Applied::New(w) | Applied::Held(w) => w,
218                }),
219            )
220        }
221        Order::RestateEntitlements(snap) => {
222            if !b.serving() {
223                return said(seq, not_serving());
224            }
225            if let Err(e) = monetize_embed::signing::verify_snapshot(snap, ctx.key) {
226                return said(seq, Err(format!("the snapshot does not verify under this box's monetize key ({e}): nothing restated")));
227            }
228            let (mut new, mut held) = (0usize, 0usize);
229            for fact in &snap.facts {
230                match b.entitle(fact) {
231                    Ok(Applied::New(_)) => new += 1,
232                    Ok(Applied::Held(_)) => held += 1,
233                    Err(e) => {
234                        return said(
235                            seq,
236                            Err(format!(
237                                "restated {new} before {} was refused: {e}",
238                                fact.tenant.0
239                            )),
240                        )
241                    }
242                }
243            }
244            said(seq, Ok(format!("restated {new} entitlement fact(s) ({held} already held as new or newer); nothing deleted")))
245        }
246        Order::PurgeTenant(p) => {
247            if !b.serving() {
248                return said(seq, not_serving());
249            }
250            match b.purge(p) {
251                Ok(answer) => OrderAck {
252                    seq,
253                    ok: true,
254                    detail: serde_json::to_string(&answer).unwrap_or_default(),
255                    receipt_json: String::new(),
256                },
257                Err(e) => said(seq, Err(e)),
258            }
259        }
260        Order::GrowRoom(g) => said(seq, b.want_room(g)),
261        Order::AddPrincipal(a) => {
262            if !b.serving() {
263                return said(seq, not_serving());
264            }
265            said(seq, b.add_principal(a))
266        }
267        Order::SignInAgain(s) => {
268            if !b.serving() {
269                return said(seq, not_serving());
270            }
271            said(seq, b.sign_in_again(s))
272        }
273        // Any box the order is for keeps it (the pump checked the role): a twin that serves later
274        // mails through it too.
275        Order::Refeed(r) => said(seq, b.keep_refeed(r)),
276        Order::GrowGoAhead(g) => grow(b, ctx, seq, g, ask),
277        Order::RechargeFromTwin(r) => {
278            // Fence before recharge (Gunnar's order): the old primary the order names is
279            // fenced first, and only a fence that held lets the recharge run beside it. A
280            // guardian that fenced it AT THE CLOUD sends its proof (`fenced`) instead. With
281            // neither, the box's recharge lane refuses `not_fenced` (A7 row 4: the shared
282            // `nordisk_twin::fence::fence_gate`), so a bare order never makes two writers.
283            let fenced = match r.fence.as_deref().map(str::trim) {
284                Some(old) if old == r.main.trim() => {
285                    return said(seq, Err(format!("order {seq} names the main {} as the primary to fence: a recharge never fences the box it fills", r.main)));
286                }
287                Some(old) => match b.fence(old, r.epoch) {
288                    Ok(_) => Some(old.to_string()),
289                    Err(e) => return said(seq, Err(format!("order {seq} fences {old} first and that fence did not hold ({e}): nothing was recharged"))),
290                },
291                None => None,
292            };
293            let prefix = match (&fenced, r.fenced.as_deref()) {
294                (Some(f), _) => format!("fenced {f}; "),
295                (None, Some(p)) => format!("cloud-fenced ({p}); "),
296                (None, None) => String::new(),
297            };
298            said(
299                seq,
300                b.recharge(r)
301                    .map(|w| format!("{prefix}{w}"))
302                    .map_err(|e| format!("{prefix}{e}")),
303            )
304        }
305        Order::FencePrimary { primary, epoch } => said(seq, b.fence(primary, *epoch)),
306        Order::AdoptTwin { twin, principal } => said(seq, b.adopt(twin, principal)),
307        Order::BecomeTwin { primary } => said(seq, b.become_twin(primary)),
308        Order::ReviewTwin => match b.review() {
309            Ok(v) => OrderAck {
310                seq,
311                ok: true,
312                detail: v
313                    .get("verdict")
314                    .and_then(|w| w.as_str())
315                    .unwrap_or("reviewed")
316                    .to_string(),
317                receipt_json: v.to_string(),
318            },
319            Err(e) => said(seq, Err(e)),
320        },
321    }
322}
323
324/// **A pulled `grow_go_ahead`** (G16-d): verify monetize's step for THIS estate and role,
325/// the twin first, drain and unmount, ask monetize to size this box's own disks, look
326/// again, remount. A growth already at size is acked ok with nothing moved.
327fn grow<B: HolgerBox + ?Sized>(
328    b: &B,
329    ctx: &Ctx,
330    seq: u64,
331    g: &GrowGoAhead,
332    ask: &mut dyn GrowSet,
333) -> OrderAck {
334    let fail = |w: String| said(seq, Err(w));
335    let step = match monetize_embed::signing::verify_estate_growth(g.go_ahead.as_bytes(), ctx.key) {
336        Ok(s) => s,
337        Err(e) => {
338            return fail(format!(
339                "the paid step does not verify under this box's monetize key ({e}): nothing moved"
340            ))
341        }
342    };
343    if step.estate != ctx.me.instance {
344        return fail(format!(
345            "the paid step is estate {}'s; this box pulls as {}: nothing moved",
346            step.estate, ctx.me.instance
347        ));
348    }
349    if step.until_unix_ms <= ctx.now_unix_ms as i64 {
350        return fail(format!(
351            "the paid step was good until {} (unix ms): its period is over, nothing moved",
352            step.until_unix_ms
353        ));
354    }
355    let twin = ctx.me.role == "twin";
356    let main_gib = step.main_data_gib_per_member;
357    if !twin && g.data_gib != main_gib {
358        return fail(format!("the order sizes main's disks to {} GiB, the paid step to {main_gib} GiB: nothing moved", g.data_gib));
359    }
360    if twin && g.data_gib < main_gib {
361        return fail(format!("the order sizes the twin's disk to {} GiB, under main's {main_gib} GiB a disk: nothing moved", g.data_gib));
362    }
363    match b.grow_carried(g.data_gib) {
364        Ok(true) => {
365            return said(
366                seq,
367                Ok(format!(
368                    "already {} GiB a data disk: nothing moved",
369                    g.data_gib
370                )),
371            )
372        }
373        Ok(false) => {}
374        Err(e) => return fail(e),
375    }
376    if !twin {
377        if let Err(e) = b.twin_holds(g.data_gib) {
378            return fail(e);
379        }
380    }
381    match b.grow_start(g.data_gib, &format!("pull-{seq}-{}", step.reference)) {
382        Ok(GrowState::Done(d)) => return said(seq, Ok(d)),
383        Ok(GrowState::Failed(f)) => return fail(f),
384        Ok(_) => {}
385        Err(e) => return fail(e),
386    }
387    match b.grow_settle(false) {
388        GrowState::WaitingMembers(_) => {}
389        GrowState::Done(d) => return said(seq, Ok(d)),
390        GrowState::Failed(f) | GrowState::Running(f) => return fail(f),
391    }
392    let asked = ask.grow_set(g.data_gib);
393    if let Err(e) = b.grow_look_again(asked.is_err()) {
394        return fail(format!("the grow machine would not look again: {e}"));
395    }
396    let end = b.grow_settle(true);
397    match (asked, end) {
398        (Ok(words), GrowState::Done(d)) => said(seq, Ok(format!("{d} (monetize: {words})"))),
399        (
400            Ok(words),
401            GrowState::Failed(f) | GrowState::Running(f) | GrowState::WaitingMembers(f),
402        ) => fail(format!("{f} (monetize: {words})")),
403        (
404            Err(e),
405            GrowState::Done(s)
406            | GrowState::Failed(s)
407            | GrowState::Running(s)
408            | GrowState::WaitingMembers(s),
409        ) => fail(format!(
410            "monetize did not size this box's disks ({e}); /data: {s}"
411        )),
412    }
413}
414
415#[cfg(test)]
416pub(crate) mod tests {
417    use super::*;
418    use crate::order::tests::{me, monetize_key, MONETIZE_SEED};
419    use ed25519_dalek::Signer as _;
420    use monetize_product::{State, TenantId};
421    use std::cell::RefCell;
422
423    /// A Holger box in memory: what it holds and every verb it was asked.
424    #[derive(Default)]
425    pub struct Fake {
426        pub twin: bool,
427        pub facts: RefCell<BTreeMap<String, EntitlementFact>>,
428        pub did: RefCell<Vec<String>>,
429        pub carried: bool,
430        pub settle: RefCell<Vec<GrowState>>,
431    }
432
433    impl HolgerBox for Fake {
434        fn serving(&self) -> bool {
435            !self.twin
436        }
437        fn entitle(&self, fact: &EntitlementFact) -> Result<Applied, String> {
438            let mut f = self.facts.borrow_mut();
439            if f.get(&fact.tenant.0)
440                .is_some_and(|h| h.issued_unix_ms >= fact.issued_unix_ms)
441            {
442                return Ok(Applied::Held(format!("{}: held", fact.tenant.0)));
443            }
444            f.insert(fact.tenant.0.clone(), fact.clone());
445            Ok(Applied::New(format!(
446                "{} is {:?}",
447                fact.tenant.0, fact.state
448            )))
449        }
450        fn purge(&self, p: &PurgeTenant) -> Result<PurgeAnswer, String> {
451            self.did.borrow_mut().push(format!("purge {}", p.tenant));
452            Ok(PurgeAnswer {
453                job: format!("purge:{}", p.tenant),
454                detail: "3 artifacts".into(),
455                ..Default::default()
456            })
457        }
458        fn want_room(&self, g: &GrowRoom) -> Result<String, String> {
459            self.did.borrow_mut().push(format!("room {}", g.need_bytes));
460            Ok("kept".into())
461        }
462        fn sign_in_again(&self, s: &crate::order::SignInAgain) -> Result<String, String> {
463            self.did.borrow_mut().push(format!(
464                "sign-in {} {} {} {}",
465                s.account, s.to, s.host, s.ttl_secs
466            ));
467            Ok(format!(
468                "a new sign-in link for {} was mailed to {}",
469                s.account, s.to
470            ))
471        }
472        fn keep_refeed(&self, r: &crate::order::Refeed) -> Result<String, String> {
473            self.did.borrow_mut().push(format!(
474                "refeed {}",
475                r.keys.keys().cloned().collect::<Vec<_>>().join(",")
476            ));
477            Ok(format!(
478                "kept {}",
479                r.keys.keys().cloned().collect::<Vec<_>>().join(", ")
480            ))
481        }
482        fn add_principal(&self, a: &AddPrincipal) -> Result<String, String> {
483            self.did
484                .borrow_mut()
485                .push(format!("signup {} {}", a.username, a.signup));
486            if a.username == "theirs" {
487                Err("taken: theirs is another signup's".into())
488            } else {
489                Ok(a.username.clone())
490            }
491        }
492        fn grow_carried(&self, _: u64) -> Result<bool, String> {
493            Ok(self.carried)
494        }
495        fn twin_holds(&self, gib: u64) -> Result<String, String> {
496            self.did.borrow_mut().push(format!("twin_holds {gib}"));
497            Ok("the twin holds it".into())
498        }
499        fn grow_start(&self, gib: u64, nonce: &str) -> Result<GrowState, String> {
500            self.did.borrow_mut().push(format!("start {gib} {nonce}"));
501            Ok(GrowState::Running("draining".into()))
502        }
503        fn grow_settle(&self, past: bool) -> GrowState {
504            self.did.borrow_mut().push(format!("settle {past}"));
505            let mut s = self.settle.borrow_mut();
506            if s.is_empty() {
507                if past {
508                    GrowState::Done("grown".into())
509                } else {
510                    GrowState::WaitingMembers("waiting-members".into())
511                }
512            } else {
513                s.remove(0)
514            }
515        }
516        fn grow_look_again(&self, give_up: bool) -> Result<(), String> {
517            self.did.borrow_mut().push(format!("look_again {give_up}"));
518            Ok(())
519        }
520        fn recharge(&self, r: &RechargeAsk) -> Result<String, String> {
521            self.did.borrow_mut().push(format!(
522                "recharge {} from {} lost={}",
523                r.main, r.twin, r.accept_lost
524            ));
525            Ok("recharged".into())
526        }
527        fn fence(&self, primary: &str, epoch: u64) -> Result<String, String> {
528            self.did
529                .borrow_mut()
530                .push(format!("fence {primary} {epoch}"));
531            if primary.starts_with("stuck") {
532                Err(format!("{primary} answers nothing"))
533            } else {
534                Ok(format!("{primary} is fenced at epoch {epoch}"))
535            }
536        }
537        fn adopt(&self, twin: &str, principal: &str) -> Result<String, String> {
538            self.did
539                .borrow_mut()
540                .push(format!("adopt {twin} {principal}"));
541            Ok("follows".into())
542        }
543        fn review(&self) -> Result<serde_json::Value, String> {
544            Ok(serde_json::json!({"verdict": "the twin is whole", "complete": true}))
545        }
546        fn become_twin(&self, primary: &str) -> Result<String, String> {
547            self.did.borrow_mut().push(format!("become_twin {primary}"));
548            Ok("elected".into())
549        }
550    }
551
552    pub fn fact(repo: &str, issued: u64, signer: [u8; 32]) -> EntitlementFact {
553        let mut f = EntitlementFact {
554            tenant: TenantId(repo.into()),
555            plan: "o-1".into(),
556            state: State::Paid,
557            paid_until_unix_ms: Some(99_000_000),
558            caps: Default::default(),
559            source: "payment:stripe:o-1".into(),
560            signature: Vec::new(),
561            issued_unix_ms: (issued != 0).then_some(issued),
562            issued_signature: Vec::new(),
563        };
564        let key = ed25519_dalek::SigningKey::from_bytes(&signer);
565        f.signature = key
566            .sign(&monetize_embed::signing::fact_message(&f))
567            .to_bytes()
568            .to_vec();
569        if issued != 0 {
570            f.issued_signature = key
571                .sign(&monetize_embed::signing::fact_message_issued(&f))
572                .to_bytes()
573                .to_vec();
574        }
575        f
576    }
577
578    fn run(b: &dyn HolgerBox, role: &str, o: Order) -> OrderAck {
579        let (m, k) = (me(role), monetize_key());
580        execute(
581            b,
582            &Ctx {
583                me: &m,
584                key: &k,
585                now_unix_ms: 10_000,
586            },
587            5,
588            &o,
589            &mut NoAsk,
590        )
591    }
592
593    struct Asked(Vec<u64>, bool);
594    impl GrowSet for Asked {
595        fn grow_set(&mut self, gib: u64) -> Result<String, String> {
596            self.0.push(gib);
597            if self.1 {
598                Ok("resized".into())
599            } else {
600                Err("no answer".into())
601            }
602        }
603    }
604
605    fn growth(estate: &str, gib: u64, until: i64) -> String {
606        let mut g = monetize_embed::signing::EstateGrowth {
607            v: 1,
608            signer: "shop".into(),
609            estate: estate.into(),
610            main_data_gib_per_member: gib,
611            until_unix_ms: until,
612            reference: "o-7".into(),
613            issued_unix_ms: 1,
614            signature: String::new(),
615        };
616        let sig = ed25519_dalek::SigningKey::from_bytes(&MONETIZE_SEED)
617            .sign(&monetize_embed::signing::estate_growth_message(&g));
618        g.signature = monetize_embed::signing::base64_encode(&sig.to_bytes());
619        serde_json::to_string(&g).unwrap()
620    }
621
622    #[test]
623    fn entitlement_is_applied_newer_wins_and_a_forgery_moves_nothing() {
624        let b = Fake::default();
625        let a = run(
626            &b,
627            "main",
628            Order::Entitlement(fact("libs", 5, MONETIZE_SEED)),
629        );
630        assert!(a.ok, "{a:?}");
631        assert!(b.facts.borrow().contains_key("libs"));
632        let again = run(
633            &b,
634            "main",
635            Order::Entitlement(fact("libs", 4, MONETIZE_SEED)),
636        );
637        assert!(
638            again.ok && again.detail.contains("held"),
639            "an older fact is held, not refused: {again:?}"
640        );
641        let forged = run(&b, "main", Order::Entitlement(fact("other", 6, [9u8; 32])));
642        assert!(
643            !forged.ok && forged.detail.contains("does not verify"),
644            "{forged:?}"
645        );
646        assert!(!b.facts.borrow().contains_key("other"));
647        let twin = Fake {
648            twin: true,
649            ..Default::default()
650        };
651        let refused = run(
652            &twin,
653            "twin",
654            Order::Entitlement(fact("libs", 5, MONETIZE_SEED)),
655        );
656        assert!(
657            !refused.ok && refused.detail.contains("does not serve"),
658            "{refused:?}"
659        );
660    }
661
662    /// ★ **A resend sign-in link reaches the box that serves** (A16, Gunnar's S10): the order's
663    /// words go to the host as they are; a twin mints nothing; a box that cannot mint says so.
664    #[test]
665    fn a_sign_in_again_reaches_the_serving_box_and_a_twin_mints_nothing() {
666        let s = crate::order::SignInAgain {
667            account: "acme".into(),
668            to: "buyer@mock.invalid".into(),
669            host: "acme.holger.mock.invalid".into(),
670            ttl_secs: 86_400,
671        };
672        let b = Fake::default();
673        let a = run(&b, "main", Order::SignInAgain(s.clone()));
674        assert!(
675            a.ok && a.detail.contains("mailed to buyer@mock.invalid"),
676            "{a:?}"
677        );
678        assert_eq!(
679            b.did.borrow().as_slice(),
680            ["sign-in acme buyer@mock.invalid acme.holger.mock.invalid 86400"]
681        );
682        let twin = Fake {
683            twin: true,
684            ..Default::default()
685        };
686        let refused = run(&twin, "twin", Order::SignInAgain(s.clone()));
687        assert!(
688            !refused.ok
689                && refused.detail.contains("does not serve")
690                && twin.did.borrow().is_empty(),
691            "{refused:?}"
692        );
693        struct Bare;
694        impl HolgerBox for Bare {
695            fn serving(&self) -> bool {
696                true
697            }
698        }
699        let none = run(&Bare, "main", Order::SignInAgain(s));
700        assert!(
701            !none.ok && none.detail.contains("mints no sign-in link"),
702            "{none:?}"
703        );
704    }
705
706    /// ★ **MAIL-RELAY: a refeed reaches the box's keep**; a box that keeps none says so.
707    #[test]
708    fn a_refeed_reaches_the_boxes_keep_and_a_box_that_keeps_none_refuses() {
709        let r = crate::order::Refeed {
710            keys: [
711                ("secret.smtp_url", "smtps://u:tok@relay.mock.invalid:465"),
712                ("secret.smtp_from", "holger@mock.invalid"),
713                ("public_repositories", "crates"),
714            ]
715            .iter()
716            .map(|(k, v)| (k.to_string(), v.to_string()))
717            .collect(),
718        };
719        let b = Fake::default();
720        let a = run(&b, "main", Order::Refeed(r.clone()));
721        assert!(
722            a.ok && a.detail.contains("public_repositories") && !a.detail.contains("tok@"),
723            "{a:?}"
724        );
725        assert_eq!(
726            b.did.borrow().as_slice(),
727            ["refeed public_repositories,secret.smtp_from,secret.smtp_url"]
728        );
729        struct Bare;
730        impl HolgerBox for Bare {
731            fn serving(&self) -> bool {
732                true
733            }
734        }
735        let none = run(&Bare, "main", Order::Refeed(r));
736        assert!(
737            !none.ok && none.detail.contains("keeps no estate keys"),
738            "{none:?}"
739        );
740    }
741
742    #[test]
743    fn restate_entitlements_verifies_the_snapshot_and_counts_new_and_held() {
744        let b = Fake::default();
745        run(&b, "main", Order::Entitlement(fact("a", 5, MONETIZE_SEED)));
746        let facts = vec![fact("a", 5, MONETIZE_SEED), fact("b", 5, MONETIZE_SEED)];
747        let mut snap = monetize_embed::Snapshot {
748            issued_unix_ms: 7,
749            facts,
750            signature: Vec::new(),
751        };
752        snap.signature = ed25519_dalek::SigningKey::from_bytes(&MONETIZE_SEED)
753            .sign(&monetize_embed::signing::snapshot_message(
754                snap.issued_unix_ms,
755                &snap.facts,
756            ))
757            .to_bytes()
758            .to_vec();
759        let a = run(&b, "main", Order::RestateEntitlements(snap.clone()));
760        assert!(
761            a.ok && a.detail.contains("restated 1") && a.detail.contains("1 already held"),
762            "{a:?}"
763        );
764        snap.signature = vec![0; 64];
765        let bad = run(&b, "main", Order::RestateEntitlements(snap));
766        assert!(!bad.ok && bad.detail.contains("does not verify"), "{bad:?}");
767    }
768
769    #[test]
770    fn purge_tenant_answers_monetizes_purge_answer() {
771        let b = Fake::default();
772        let a = run(
773            &b,
774            "main",
775            Order::PurgeTenant(PurgeTenant {
776                tenant: "libs".into(),
777                reason: "nonpay".into(),
778                job: String::new(),
779            }),
780        );
781        assert!(a.ok, "{a:?}");
782        let answer: PurgeAnswer = serde_json::from_str(&a.detail).unwrap();
783        assert_eq!(answer.job, "purge:libs");
784        assert_eq!(b.did.borrow().as_slice(), ["purge libs"]);
785    }
786
787    #[test]
788    fn add_principal_makes_a_repository_on_a_plan_and_says_taken_by_name() {
789        let b = Fake::default();
790        let a = run(
791            &b,
792            "main",
793            Order::AddPrincipal(AddPrincipal {
794                username: "crates".into(),
795                kind: "org".into(),
796                signup: "s-1".into(),
797            }),
798        );
799        assert!(
800            a.ok && a.detail == "crates",
801            "the namespace made is the repository: {a:?}"
802        );
803        let t = run(
804            &b,
805            "main",
806            Order::AddPrincipal(AddPrincipal {
807                username: "theirs".into(),
808                kind: "org".into(),
809                signup: "s-2".into(),
810            }),
811        );
812        assert!(!t.ok && t.detail.starts_with("taken"), "{t:?}");
813        let twin = Fake {
814            twin: true,
815            ..Default::default()
816        };
817        assert!(
818            !run(
819                &twin,
820                "twin",
821                Order::AddPrincipal(AddPrincipal {
822                    username: "crates".into(),
823                    kind: "org".into(),
824                    signup: "s-1".into()
825                })
826            )
827            .ok,
828            "only the main that serves"
829        );
830    }
831
832    #[test]
833    fn grow_room_is_kept_for_the_operator() {
834        let b = Fake::default();
835        let a = run(
836            &b,
837            "main",
838            Order::GrowRoom(GrowRoom {
839                need_bytes: 1 << 30,
840                reason: "libs".into(),
841            }),
842        );
843        assert!(a.ok, "{a:?}");
844        assert_eq!(b.did.borrow().as_slice(), ["room 1073741824"]);
845    }
846
847    #[test]
848    fn grow_go_ahead_runs_twin_first_asks_monetize_and_remounts() {
849        let b = Fake::default();
850        let (m, k) = (me("main"), monetize_key());
851        let mut ask = Asked(Vec::new(), true);
852        let o = Order::GrowGoAhead(GrowGoAhead {
853            go_ahead: growth("acme", 120, 99_000),
854            data_gib: 120,
855        });
856        let a = execute(
857            &b,
858            &Ctx {
859                me: &m,
860                key: &k,
861                now_unix_ms: 10_000,
862            },
863            6,
864            &o,
865            &mut ask,
866        );
867        assert!(a.ok, "{a:?}");
868        assert_eq!(
869            b.did.borrow().as_slice(),
870            [
871                "twin_holds 120",
872                "start 120 pull-6-o-7",
873                "settle false",
874                "look_again false",
875                "settle true"
876            ]
877        );
878        assert_eq!(ask.0, [120]);
879
880        // Monetize does not answer: /data is mounted back and the ack says why.
881        let b = Fake::default();
882        let mut deaf = Asked(Vec::new(), false);
883        b.settle.borrow_mut().extend([
884            GrowState::WaitingMembers("w".into()),
885            GrowState::Failed("mounted back".into()),
886        ]);
887        let a = execute(
888            &b,
889            &Ctx {
890                me: &m,
891                key: &k,
892                now_unix_ms: 10_000,
893            },
894            7,
895            &o,
896            &mut deaf,
897        );
898        assert!(
899            !a.ok && a.detail.contains("no answer") && a.detail.contains("mounted back"),
900            "{a:?}"
901        );
902        assert!(b.did.borrow().contains(&"look_again true".to_string()));
903    }
904
905    #[test]
906    fn grow_go_ahead_refuses_another_estate_a_lapsed_step_and_a_forgery_and_skips_a_done_one() {
907        let b = Fake::default();
908        for (go, why) in [
909            (growth("other", 120, 99_000), "estate other"),
910            (growth("acme", 120, 5_000), "period is over"),
911            (
912                growth("acme", 120, 99_000).replace("\"o-7\"", "\"o-8\""),
913                "does not verify",
914            ),
915        ] {
916            let a = run(
917                &b,
918                "main",
919                Order::GrowGoAhead(GrowGoAhead {
920                    go_ahead: go,
921                    data_gib: 120,
922                }),
923            );
924            assert!(!a.ok && a.detail.contains(why), "{why}: {a:?}");
925        }
926        assert!(b.did.borrow().is_empty(), "nothing moved");
927        let done = Fake {
928            carried: true,
929            ..Default::default()
930        };
931        let a = run(
932            &done,
933            "main",
934            Order::GrowGoAhead(GrowGoAhead {
935                go_ahead: growth("acme", 120, 99_000),
936                data_gib: 120,
937            }),
938        );
939        assert!(a.ok && a.detail.contains("nothing moved"), "{a:?}");
940        let twin = Fake {
941            twin: true,
942            ..Default::default()
943        };
944        let a = run(
945            &twin,
946            "twin",
947            Order::GrowGoAhead(GrowGoAhead {
948                go_ahead: growth("acme", 120, 99_000),
949                data_gib: 240,
950            }),
951        );
952        assert!(a.detail.contains("no pull channel"), "{a:?}");
953        assert!(
954            !twin
955                .did
956                .borrow()
957                .iter()
958                .any(|d| d.starts_with("twin_holds")),
959            "the twin grows first, asking no twin: {a:?}"
960        );
961    }
962
963    #[test]
964    fn recharge_from_twin_runs_the_lane_on_a_cloud_fence_proof() {
965        let b = Fake::default();
966        let r = RechargeAsk {
967            main: "main:1".into(),
968            twin: "twin:1".into(),
969            accept_lost: true,
970            fenced: Some("upcloud: gone".into()),
971            ..Default::default()
972        };
973        let a = run(&b, "twin", Order::RechargeFromTwin(r));
974        assert!(a.ok && a.detail.starts_with("cloud-fenced"), "{a:?}");
975        assert_eq!(
976            b.did.borrow().as_slice(),
977            ["recharge main:1 from twin:1 lost=true"]
978        );
979    }
980
981    /// ★ **Fence before recharge, by the pulled order** (M2, Gunnar's pattern): the old
982    /// primary the order names is fenced first, and only a fence that held lets the
983    /// recharge run; a fence that did not hold runs nothing; the main it fills is never
984    /// the box it fences.
985    #[test]
986    fn recharge_from_twin_fences_the_old_primary_first() {
987        let b = Fake::default();
988        let r = RechargeAsk {
989            main: "main:2".into(),
990            twin: "twin:1".into(),
991            accept_lost: false,
992            epoch: 4,
993            fence: Some("old:1".into()),
994            ..Default::default()
995        };
996        let a = run(&b, "twin", Order::RechargeFromTwin(r.clone()));
997        assert!(a.ok && a.detail.starts_with("fenced old:1; "), "{a:?}");
998        assert_eq!(
999            b.did.borrow().as_slice(),
1000            ["fence old:1 4", "recharge main:2 from twin:1 lost=false"]
1001        );
1002
1003        let stuck = Fake::default();
1004        let a = run(
1005            &stuck,
1006            "twin",
1007            Order::RechargeFromTwin(RechargeAsk {
1008                fence: Some("stuck:1".into()),
1009                ..r.clone()
1010            }),
1011        );
1012        assert!(
1013            !a.ok
1014                && a.detail.contains("stuck:1 answers nothing")
1015                && a.detail.contains("nothing was recharged"),
1016            "{a:?}"
1017        );
1018        assert_eq!(
1019            stuck.did.borrow().as_slice(),
1020            ["fence stuck:1 4"],
1021            "no recharge beside an unfenced primary"
1022        );
1023
1024        let same = Fake::default();
1025        let a = run(
1026            &same,
1027            "twin",
1028            Order::RechargeFromTwin(RechargeAsk {
1029                fence: Some("main:2".into()),
1030                ..r
1031            }),
1032        );
1033        assert!(
1034            !a.ok && a.detail.contains("never fences the box it fills"),
1035            "{a:?}"
1036        );
1037        assert!(same.did.borrow().is_empty(), "nothing ran");
1038    }
1039
1040    #[test]
1041    fn become_twin_runs_holgers_election() {
1042        let b = Fake {
1043            twin: true,
1044            ..Default::default()
1045        };
1046        assert!(
1047            run(
1048                &b,
1049                "twin",
1050                Order::BecomeTwin {
1051                    primary: "main:1".into()
1052                }
1053            )
1054            .ok
1055        );
1056        assert_eq!(b.did.borrow().as_slice(), ["become_twin main:1"]);
1057        struct Bare;
1058        impl HolgerBox for Bare {
1059            fn serving(&self) -> bool {
1060                false
1061            }
1062        }
1063        let none = run(
1064            &Bare,
1065            "twin",
1066            Order::BecomeTwin {
1067                primary: "main:1".into(),
1068            },
1069        );
1070        assert!(
1071            !none.ok && none.detail.contains("cannot become"),
1072            "the default refuses by name: {none:?}"
1073        );
1074    }
1075
1076    #[test]
1077    fn fence_primary_runs_the_boxes_fence_adopt_and_review_run() {
1078        let b = Fake::default();
1079        let a = run(
1080            &b,
1081            "twin",
1082            Order::FencePrimary {
1083                primary: "old:1".into(),
1084                epoch: 3,
1085            },
1086        );
1087        assert!(
1088            a.ok && b.did.borrow().contains(&"fence old:1 3".to_string()),
1089            "{a:?}"
1090        );
1091        struct Bare;
1092        impl HolgerBox for Bare {
1093            fn serving(&self) -> bool {
1094                false
1095            }
1096        }
1097        let none = run(
1098            &Bare,
1099            "twin",
1100            Order::FencePrimary {
1101                primary: "old:1".into(),
1102                epoch: 3,
1103            },
1104        );
1105        assert!(
1106            !none.ok && none.detail.contains("cannot fence"),
1107            "the default refuses by name: {none:?}"
1108        );
1109        let a = run(
1110            &b,
1111            "main",
1112            Order::AdoptTwin {
1113                twin: "twin:1".into(),
1114                principal: "twin".into(),
1115            },
1116        );
1117        assert!(
1118            a.ok && b.did.borrow().contains(&"adopt twin:1 twin".to_string()),
1119            "{a:?}"
1120        );
1121        let a = run(&b, "twin", Order::ReviewTwin);
1122        assert!(
1123            a.ok && a.detail == "the twin is whole" && a.receipt_json.contains("complete"),
1124            "{a:?}"
1125        );
1126    }
1127}