crates/monetize-server/src/estate_order/tests/paired_material.rs
//! **F8 (2026-10-21): a paired estate's front takes its installer material.** test3-holger-rs
//! was adopted into `paired` (F5) and F7 let it take the update to r10 — which failed on its
//! twin: "its live door did not answer: no installer material is kept in holger's primary …
//! repository monetize-backup yet". The material reaches the primary only through the front's
//! `SoldEstate.TakeMaterial`, and that was refused until the estate was captured; a paired
//! estate is captured only once its main reports, and its main could report only after the
//! update. A circle.
//!
//! * **Every box PAIRED takes the material**: an estate in `paired` (its installer's PASS, or
//! adopted on a re-check) takes its own installer material, the way a delivered one does —
//! the material is the estate's, its payment is held. The hand-off still forgets it only
//! once the estate is delivered.
//! * **An install still running is refused it**, and so is every phase before `paired`.
//! * Gunnar's and Holger's alike: both fronts ask as soon as their primary answers.
use super::paired_update::{
call, finish_every_box, front_key, runs_older, site_release, take_as, SITE,
};
use super::recheck::{answers, install_failed_on};
use super::*;
const MINUTE: u64 = 60_000;
const HOUR: u64 = 60 * MINUTE;
/// The primary's `monetize-backup` repository as the front keeps the material there: the
/// front takes it from the shop, keeps it, and calls `done` (holger-estate-front
/// `shop_door::keep_material`, gunnar-front `recharge::take_kept`).
#[derive(Default)]
struct PrimarySlot(Mutex<Option<Vec<u8>>>);
impl PrimarySlot {
fn keep(
&self,
state: &AppState,
clock: &AtomicU64,
key: &monetize::Signer,
company: &str,
) -> Result<String, sold::SoldRefusal> {
let take = |done: bool| {
let (at, sig) = call(
clock,
key,
company,
sold::CALL_TAKE_MATERIAL,
&[],
u64::from(done),
"",
);
sold::take_material(state, company, done, at, &sig)
};
let taken = take(false)?;
assert!(!taken.bundle.is_empty(), "{}", taken.words);
*self.0.lock().unwrap() = Some(taken.bundle);
Ok(take(true)?.words)
}
/// The twin's live door: it answers only once the primary keeps the material.
fn twin_door(&self) -> Result<(), String> {
self.0.lock().unwrap().as_ref().map(|_| ()).ok_or_else(|| {
"no installer material is kept in the primary's repository monetize-backup yet"
.to_string()
})
}
}
/// The operator site whose install was stopped while its installer ran, then adopted on a
/// re-check (F7's test3-holger-rs), its pairing's late bundle sealed into the custody with the
/// rest (the shop's pairing writes it on its late PASS; the take-back seals it whatever the
/// outcome).
fn adopted(name: &str) -> (Arc<AppState>, Arc<MockCloud>, Arc<FakeKit>, Arc<AtomicU64>) {
let (state, cloud, kit, _mails, clock) = install_failed_on(name, true, site_release("r10"));
let custody = state.estates.custody.as_ref().unwrap();
let dir = state.estates.run_dir(SITE).unwrap().join("late-pass");
let h = custody.hand_off(SITE, &dir).unwrap();
let back = h.dir().join(crate::custody::handoff::RETURN_DIR);
std::fs::create_dir_all(&back).unwrap();
std::fs::write(
back.join(crate::custody::handoff::INSTALLER_MATERIAL),
"{\"v\":\"installer-material/1\"}",
)
.unwrap();
custody.take_back(SITE, &h).unwrap();
drop(h);
// Its boxes run r6, whose main never reports (its report-when-fed fix is in r10).
runs_older(&state, SITE);
clock.fetch_add(22 * HOUR + 29 * MINUTE, Ordering::SeqCst);
answers(&kit, true);
assert_eq!(
settle(&state, "gunnar-rs", RECHECK).expect("adopted").phase,
phase::PAIRED
);
crate::estate_order::drive(&state, SITE);
let run = state.estates.get(SITE).unwrap();
assert_eq!(
run.phase,
phase::PAIRED,
"its main never reports on r6: {:#?}",
run.history
);
(state, cloud, kit, clock)
}
/// ★ **An adopted paired estate's front takes its material, the primary keeps it, and the
/// update to the current release then goes through all three boxes** — the custody keeps its
/// copy until the estate is delivered.
#[test]
fn an_adopted_paired_estate_takes_its_material_then_its_update_goes_through_every_box() {
let (state, cloud, kit, clock) = adopted("f8-adopted");
let key = front_key(&kit);
let slot = PrimarySlot::default();
assert!(slot.twin_door().is_err());
let said = slot
.keep(&state, &clock, &key, SITE)
.expect("a paired estate's front takes its material");
assert!(said.contains("took its installer material"), "{said}");
assert_eq!(
slot.0.lock().unwrap().as_deref(),
Some(&b"{\"v\":\"installer-material/1\"}"[..])
);
let run = state.estates.get(SITE).unwrap();
assert!(run.material_taken_at_ms > 0, "{:#?}", run.history);
assert_eq!(
run.phase,
phase::PAIRED,
"taking the material is no delivery"
);
assert!(
state.estates.custody.as_ref().unwrap().holds(SITE),
"the shop forgets it only at the hand-off of a delivered estate"
);
assert!(
!take_as(&state, &clock, &key, SITE).open,
"the front has asked before: nothing open"
);
let keep_before = cloud.keep_booted.lock().unwrap().len();
let u =
update::start(&state, "gunnar-rs", "", "operator").expect("the operator's update is taken");
assert_eq!(u.release, "r10-gunnar", "{}", u.line());
slot.twin_door()
.expect("the twin's live door answers: the primary keeps the material");
finish_every_box(&state, &clock, &key, SITE, "gunnar");
assert_eq!(
cloud.keep_booted.lock().unwrap().len(),
keep_before + 1,
"the main, from its keep medium"
);
let run = state.estates.get(SITE).unwrap();
assert_eq!(
run.updates.last().unwrap().state,
update::state::DONE,
"{}",
run.updates.last().unwrap().line()
);
assert!(
["front", "main", "twin"].iter().all(|r| run
.release_of
.get(*r)
.is_some_and(|x| x.starts_with("r10-"))),
"{:?}",
run.release_of
);
}
/// ★ **Holger, one lifecycle**: a Holger estate paired by its installer's PASS, no box reported
/// yet, takes its material into its primary and then its update through all three boxes.
#[test]
fn a_paired_holger_estate_takes_its_material_then_its_update() {
let mut c = EstateOrderConfig {
lifecycle: "holger".into(),
..cfg()
};
c.media = [
("front", "front"),
("holger-front", "holger-front"),
("holger", "holger"),
("holger-keep", "holger-keep"),
("holger-front-keep", "holger-front-keep"),
]
.into_iter()
.map(|(k, v)| (k.to_string(), format!("r10-{v}")))
.collect();
for kind in ["holger", "holger-front"] {
c.live_apps.insert(
kind.into(),
update::LiveRecord {
base: "b".repeat(64),
app: "a".repeat(64),
app_len: 9,
sig: "5".repeat(128),
..Default::default()
},
);
c.media_objects.insert(
format!("{kind}{}", update::LIVE_SUFFIX),
monetize_cloud::lay::MediaObject {
url: format!("https://objsto.example/release/{kind}/r10.live-app.znippy"),
sha256: "c".repeat(64),
bytes: 9,
},
);
}
let r = rig_with("f8-holger-paired", c, "PASS");
r.kit.silent.store(true, Ordering::SeqCst);
r.kit.bundles.store(true, Ordering::SeqCst);
start_for(
&r.state,
"acme",
"buyer@example.org",
"",
"alice",
&EstateSizes::default(),
)
.unwrap();
r.pay.authorize(&handle_of(&r, "acme"));
crate::estate_order::drive(&r.state, "acme");
assert_eq!(
r.state.estates.get("acme").unwrap().phase,
phase::PAIRED,
"no box reported yet"
);
let key = client_of(&r, "acme");
runs_older(&r.state, "acme");
let slot = PrimarySlot::default();
slot.keep(&r.state, &r.clock, &key, "acme")
.expect("a paired Holger estate's front takes its material");
assert!(!take_as(&r.state, &r.clock, &key, "acme").open);
let u =
update::start(&r.state, "acme", "", "operator").expect("a paired estate takes the update");
assert_eq!(u.release, "r10-holger", "{}", u.line());
slot.twin_door().expect("the twin's live door answers");
finish_every_box(&r.state, &r.clock, &key, "acme", "holger");
assert_eq!(
r.cloud.keep_booted.lock().unwrap().last().unwrap().1,
"r10-holger-keep"
);
}
/// ★ **An installing estate is still refused its material** — its boxes are not all PAIRED;
/// its install then goes on.
#[test]
fn an_installing_estate_is_refused_its_material() {
let (state, pay, _cloud, kit, _dir, clock, _mails) =
site_rig_with_cfg("f8-installing", true, site_release("r10"));
kit.bundles.store(true, Ordering::SeqCst);
let run = site::start_site(
&state,
"gunnar-rs",
"ops@example.org",
&EstateSizes::default(),
)
.unwrap();
pay.authorize(&run.handle.clone().unwrap().id);
kit.install_gate.arm();
let s = state.clone();
let driver = std::thread::spawn(move || super::drive(&s, SITE));
kit.install_gate.reached();
assert_eq!(state.estates.get(SITE).unwrap().phase, phase::INSTALLING);
// The estate's own client key, as the hand-off holds it while the installer runs.
let hexed = std::fs::read_to_string(
state
.estates
.run_dir(SITE)
.unwrap()
.join("handoff")
.join(crate::custody::handoff::ESTATE_CLIENT),
)
.expect("the hand-off holds the client key");
let seed: Vec<u8> = (0..hexed.trim().len())
.step_by(2)
.map(|i| u8::from_str_radix(&hexed.trim()[i..i + 2], 16).unwrap())
.collect();
let key = monetize::Signer::from_bytes(&seed.try_into().expect("32 bytes"));
// Named on the run as an adopted one is (F7), so the refusal is the phase's, not the key's.
let _pen = state.estates.pen(SITE);
let mut run = state.estates.get(SITE).unwrap();
run.client_key = key
.public_key()
.iter()
.map(|b| format!("{b:02x}"))
.collect();
save(&state, &run).unwrap();
drop(_pen);
for done in [false, true] {
let (at, sig) = call(
&clock,
&key,
SITE,
sold::CALL_TAKE_MATERIAL,
&[],
u64::from(done),
"",
);
match sold::take_material(&state, SITE, done, at, &sig) {
Err(sold::SoldRefusal::Refused(w)) => assert!(
w.contains("not delivered (installing)") && w.contains("PAIRED"),
"{w}"
),
other => panic!("{:?}", other.map(|a| a.words)),
}
}
assert_eq!(state.estates.get(SITE).unwrap().material_taken_at_ms, 0);
kit.install_gate.open();
driver.join().unwrap();
assert_eq!(
state.estates.get(SITE).unwrap().phase,
phase::CAPTURED,
"its install went on"
);
}