holger-estate-front/src/update.rs
//! **The front does the update its shop opened** — the update step is SHARED since P4a
//! (2026-10-25): Skidbladnir nordisk-pairing `frontside::update` (`decide`, `staged_of`,
//! `declared_of`, `wait_confirmed`, `step`, `look`, the [`Hands`] and [`Shop`] seams), moved there
//! from this file; Gunnar's front takes it in T16. The shop's side of it ([`Shop`]) is
//! monetize-estate-side's `ShopDoor` (feature `metered`). What stays Holger's: its hands
//! (`crate::sold::EstateHands`: the boxes' live doors over the kept material, the release
//! fetch, the keep upgrade through the ONE box) and the words of its front's keep boot.
pub use nordisk_pairing::frontside::update::*;
/// **What the shop keep-boots when Holger's front needs it** (`needs-keep`): its front, from
/// the `holger-front-keep` medium ([`Hands::front_keep`]).
pub const FRONT_KEEP: &str = "Holger's front from holger-front-keep";
#[cfg(test)]
mod tests {
use super::*;
use nordisk_pairing::live::{Declared, Running, Stage};
use std::time::Duration;
/// A front whose own slot is on an old BASE: only its keep boot can lay the release.
struct OldBase;
impl Hands for OldBase {
fn running(&self, _: &str) -> Result<Running, String> {
Ok(Running {
base: "c".repeat(64),
app: "0".repeat(64),
slot: "a".into(),
live_update: true,
})
}
fn stage(&self, _: &str, _: &Stage, _: &Declared) -> Result<String, String> {
panic!("a moved BASE is never staged")
}
fn fetch(&self, _: &str) -> Result<Vec<u8>, String> {
panic!("a moved BASE is never fetched")
}
fn keep(&self, _: &str) -> Result<String, String> {
panic!("the front never upgrades itself through Upgrade")
}
fn wait(&self, _: Duration) {}
fn staged_self(&self, _: &str) -> bool {
false
}
fn mark_staged_self(&self, _: &str) -> Result<(), String> {
Ok(())
}
fn front_keep(&self) -> String {
FRONT_KEEP.into()
}
}
/// ★ P4a: on the shared step, **Holger's front asks for its keep boot in the words it always
/// did** — the shop keep-boots Holger's front from holger-front-keep.
/// What a Holger main answers on `GET /live/running` after its keep medium ran in place and
/// its upgraded system booted: the OPEN install listener (nordisk-pairing `bootstrap::respond`).
const OPEN_LISTENER: &str = "REFUSED [install-path] this listener has /install/hello, /install/log, /install/feed, /install/status, /install/offer, /install/secret-offer and /install/close";
/// A Holger estate whose main waits at its open install listener; the front re-admits.
struct Waiting {
readmitted: std::cell::RefCell<Vec<String>>,
app: String,
}
impl Hands for Waiting {
fn running(&self, role: &str) -> Result<Running, String> {
if role == "main" && self.readmitted.borrow().is_empty() {
return Err(OPEN_LISTENER.into());
}
Ok(Running {
base: "b".repeat(64),
app: self.app.clone(),
slot: "a".into(),
live_update: true,
})
}
fn stage(&self, _: &str, _: &Stage, _: &Declared) -> Result<String, String> {
panic!("a box on the release is never staged")
}
fn fetch(&self, _: &str) -> Result<Vec<u8>, String> {
panic!("nothing is fetched")
}
fn keep(&self, role: &str) -> Result<String, String> {
panic!("no second keep boot of the {role}")
}
fn wait(&self, _: Duration) {}
fn staged_self(&self, _: &str) -> bool {
false
}
fn mark_staged_self(&self, _: &str) -> Result<(), String> {
Ok(())
}
fn readmit(&self, roles: &[&str]) -> Result<String, String> {
self.readmitted.borrow_mut().push(roles.join("+"));
Ok(format!(
"{} re-admitted by the front after its upgrade (its data kept)",
roles.join(" and ")
))
}
}
/// ★ T16 (RED before the shared step took Gunnar's re-admission): **a Holger main left at
/// its open install listener is re-admitted first**, read again, and — on the release
/// already — reported `done:keep`, never "its live door did not answer".
#[test]
fn a_holger_main_left_at_its_open_listener_is_readmitted_and_reported_kept() {
let h = Waiting {
readmitted: Default::default(),
app: "1".repeat(64),
};
let app = LiveApp {
base: "b".repeat(64),
app: "1".repeat(64),
sig: "ab".into(),
..LiveApp::default()
};
let t = Take {
open: true,
release: "04".into(),
live: true,
role: "main".into(),
app: Some(app),
..Take::default()
};
let (outcome, words) = step(&h, &t).expect("reported");
assert_eq!(outcome, DONE_KEEP, "{words}");
assert_eq!(*h.readmitted.borrow(), vec!["main".to_string()]);
assert!(words.contains("never re-admitted"), "{words}");
}
#[test]
fn holgers_front_asks_for_its_keep_boot_in_holgers_words() {
let app = LiveApp {
base: "b".repeat(64),
app: "1".repeat(64),
sig: "ab".into(),
..LiveApp::default()
};
let t = Take {
open: true,
release: "03".into(),
live: true,
role: "front".into(),
front: Some(app),
..Take::default()
};
let (outcome, words) = step(&OldBase, &t).expect("reported");
assert_eq!(outcome, NEEDS_KEEP);
assert!(
words.ends_with(": the shop keep-boots Holger's front from holger-front-keep"),
"{words}"
);
}
}