Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

crates/monetize-server/src/custody.rs

//! **The custody of a private estate's secrets** — monetize keeps them sealed.
//!
//! Owner, 2026-10-04: a private gunnar's estate-level secrets are made and kept
//! by MONETIZE (gunnar.rs's own, on the front) and never on an operator's
//! laptop. `private-gunnar-ops/PRIVATE-GUNNAR.md` §3 has the decision and the
//! blast radius; this file is the mechanism.
//!
//! # What is kept here, and what is not
//!
//! Kept, per estate, sealed in `custody/<company>.sealed`:
//!
//! * the **media signing seed** — the root every medium of that estate is
//!   signed with (the kit's `root_seed`), 32 bytes;
//! * the **pairing seed**, 32 bytes, and the **pairing material** the kit
//!   minted for the estate's boxes (the installer CA, its keys and the link
//!   keys), handed back after the install and sealed here;
//! * the **recovery (break-glass) bearer** for the estate's console;
//! * the **backup key** that seals the estate's monetize state on its way to
//!   the estate's own twin;
//! * the **buyer's first-login invite** (private ownership, 2026-10-07): the
//!   kit feeds its hash to gunnar as the operator's first invite, single use
//!   and short lived, and the "ready" mail carries it as the buyer's link.
//!
//! * the estate's **off-site key pair** (lane O1, an estate whose main ships an
//!   off-site copy: Holger's) — minted with the pairing material, its public
//!   half fed as the main's `offsite_recipient`; the hand-off keeps it in a
//!   seal of its own (`<company>.offsite.sealed`), so the operator can still
//!   open the estate's copies; a full forget shreds it.
//!
//! NOT kept here: a box's own host, TLS and forge SSH keys. Those are made on
//! the box and only their public halves leave it, exactly as on gunnar.rs.
//!
//! # How the seal is protected
//!
//! * [`crate::seal`] — AES-256-GCM in monetize-front's ledger-backup format
//!   (feature `custody-ring`) — under the custody key `custody/kek` (32
//!   random bytes, mode 600, made on first use). The company name is in the associated data, so
//!   a sealed file copied onto another estate's name does not open.
//! * A secret is unsealed only for the step that needs it, into a HAND-OFF
//!   directory (mode 700, files mode 600) that [`HandOff`] deletes when it is
//!   dropped.
//! * No secret is ever written to the job's run file, its journal, a log line
//!   or an RPC answer — `estate_order::tests` searches all four for every one.
//! * The custody key is not in monetize's text backup (`crate::backup` keeps no
//!   secret, by design).

use std::io::Write as _;
use std::os::unix::fs::{OpenOptionsExt as _, PermissionsExt as _};
use std::path::{Path, PathBuf};

use crate::seal;

/// What a custody file starts with.
const MAGIC: &[u8] = b"MCUS1\0";

fn aad(company: &str) -> Vec<u8> {
    format!("monetize-custody-1:{company}").into_bytes()
}

/// A credential's associated data: its name, and that it is not an estate's.
fn credential_aad(name: &str) -> Vec<u8> {
    format!("monetize-custody-1:credential:{name}").into_bytes()
}

/// **A health reader sealed apart by a 5.3.1 custody** (`<company>.reader.sealed`): LEGACY (ONE
/// monetize, G12/F4 2026-10-10) — monetize dials no estate, so nothing reads the twin through
/// the estate's door and no reader is kept; [`Custody::forget`] removes one left behind.
const LEGACY_READER_SUFFIX: &str = ".reader.sealed";

/// **A narrated site's installer link file, kept past the hand-off** (`<company>.link.sealed`,
/// lane N2): the ONE file of the estate's pairing material its DEV-NARRATE receiver reads —
/// moved out of the estate's seal when the hand-off forgets the rest, sealed under its own
/// associated data, removed by [`Custody::forget`].
const LINK_SUFFIX: &str = ".link.sealed";

/// The link seal's associated data: its company, and that it is the link file alone.
fn link_aad(company: &str) -> Vec<u8> {
    format!("monetize-custody-1:link:{company}").into_bytes()
}

/// **An estate's OFF-SITE key pair, kept past the hand-off** (`<company>.offsite.sealed`, lane
/// O1): the X25519 pair whose public half the estate's main (and the twin that may become main)
/// seals its off-site copy for (Holger's `offsite_recipient`). The shop's pairing mints it with
/// the order's material ([`OFFSITE_KEY_FILE`], [`OFFSITE_PUB_FILE`]); the hand-off moves it out
/// of the estate's seal into this one, so the ONE box — the operator — can still open the
/// estate's copies when the estate is gone (the ONE box's own off-box copy carries
/// `custody/*.sealed`). The estate itself never holds the private half. Removed by
/// [`Custody::forget`] (a refund, a deletion: the copies are then shredded with it).
const OFFSITE_SUFFIX: &str = ".offsite.sealed";

/// The off-site seal's associated data: its company, and that it is the off-site pair alone.
fn offsite_aad(company: &str) -> Vec<u8> {
    format!("monetize-custody-1:offsite:{company}").into_bytes()
}

/// The pairing material's names of the estate's off-site key pair (X25519, base64 of 32 bytes
/// each: Holger's `offsite::key32`): the private half, kept by the ONE box alone, and the public
/// half the estate is fed.
pub const OFFSITE_KEY_FILE: &str = "offsite.key";
pub const OFFSITE_PUB_FILE: &str = "offsite.pub";

/// The pairing material's name of the installer's link file (nordisk-pairing
/// `installer::LINK_KEYS`, the same literal: monetize-server links no installer).
pub const LINK_KEYS_FILE: &str = "link.toml";

/// The custody key's file name in the custody directory.
pub const KEK_FILE: &str = "kek";
/// The hand-off file names the kit reads (`cargo xtask … estate install --laid`).
pub mod handoff {
    /// The media signing seed, 32 raw bytes — what the kit takes as the
    /// instance's `root_seed` (the format `dwarves root-key generate` writes).
    pub const ROOT_SEED: &str = "root.seed";
    /// The console's break-glass bearer, 64 hex (the kit's `console-admin-token`).
    pub const CONSOLE_TOKEN: &str = "console-admin-token";
    /// The pairing seed, 64 hex.
    pub const PAIRING_SEED: &str = "pairing.seed";
    /// **The buyer's first-login invite**, 64 hex (private ownership,
    /// 2026-10-07): the kit feeds its HASH as the operator's first invite
    /// (`operator_invite_sha256`), and the "ready" mail carries it as
    /// `https://<company>.<apex>/login?invite=…&user=<account>`.
    pub const FIRST_LOGIN: &str = "first-login-invite";
    /// **The estate's client key** (one shop, 2026-10-07), 64 hex: the seed of
    /// the Ed25519 key a SOLD estate signs its `SoldEstate` calls to the ONE
    /// monetize with (it runs no monetize of its own, G16); the kit feeds it to
    /// the estate. Its public half is on the order.
    pub const ESTATE_CLIENT: &str = "estate-client.key";
    /// Where the kit hands the pairing material it minted BACK to the custodian.
    pub const RETURN_DIR: &str = "return";
    /// **The installer material the estate keeps** (F4): the bundle the shop's pairing hands
    /// back after a PASSed pairing (material, ledger, plan); the sold estate's front TAKES it
    /// (`SoldEstate.TakeMaterial`, the pull: monetize never dials an estate) before the
    /// hand-off forgets it with the rest.
    pub const INSTALLER_MATERIAL: &str = "installer-material.json";
    /// Where a later step finds the pairing material handed back at the
    /// install (the kit's installer material directory).
    pub const MATERIAL_DIR: &str = "material";
}

/// **One estate's secrets, in memory only.** Overwritten when dropped.
pub struct EstateSecrets {
    pub signing_seed: [u8; 32],
    pub pairing_seed: [u8; 32],
    pub break_glass: [u8; 32],
    /// **The buyer's first-login invite** ([`handoff::FIRST_LOGIN`]); `None`
    /// in a seal made before it existed, until [`Custody::mint`] adds one.
    pub first_login: Option<[u8; 32]>,
    /// **The estate's client key's seed** ([`handoff::ESTATE_CLIENT`]); `None`
    /// in a seal made before it existed, until [`Custody::mint`] adds one.
    pub estate_client: Option<[u8; 32]>,
    /// The pairing material the kit handed back: file name → contents.
    pub pairing_material: Vec<(String, String)>,
}

impl Drop for EstateSecrets {
    fn drop(&mut self) {
        for b in [&mut self.signing_seed, &mut self.pairing_seed, &mut self.break_glass] {
            b.iter_mut().for_each(|x| *x = 0);
        }
        for b in [self.first_login.as_mut(), self.estate_client.as_mut()].into_iter().flatten() {
            b.iter_mut().for_each(|x| *x = 0);
        }
        for (_, v) in &mut self.pairing_material {
            v.clear();
        }
    }
}

impl std::fmt::Debug for EstateSecrets {
    /// Names, never values: a `{:?}` in a log line must not be a leak.
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "EstateSecrets {{ signing_seed, pairing_seed, break_glass, first_login, estate_client, pairing_material: {} file(s) }}", self.pairing_material.len())
    }
}

#[derive(serde::Serialize, serde::Deserialize)]
struct Plain {
    signing_seed: String,
    pairing_seed: String,
    break_glass: String,
    #[serde(default)]
    first_login: String,
    #[serde(default)]
    estate_client: String,
    #[serde(default)]
    pairing_material: Vec<(String, String)>,
}

/// **The custodian**: a directory of sealed files and the key they are sealed under.
#[derive(Clone, Debug)]
pub struct Custody {
    dir: PathBuf,
}

fn hex(b: &[u8]) -> String {
    b.iter().map(|x| format!("{x:02x}")).collect()
}

fn unhex(s: &str) -> Result<Vec<u8>, String> {
    if s.len() % 2 != 0 {
        return Err("odd hex".into());
    }
    (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).map_err(|e| e.to_string())).collect()
}

fn random32() -> Result<[u8; 32], String> {
    seal::random32().map_err(|e| format!("custody: {e}"))
}

fn arr(v: &[u8], what: &str) -> Result<[u8; 32], String> {
    v.try_into().map_err(|_| format!("custody: {what} is {} bytes, not 32", v.len()))
}

/// Write `bytes` to `path`, mode 600, created fresh (a stale file is replaced).
pub fn write_private(path: &Path, bytes: &[u8]) -> Result<(), String> {
    let _ = std::fs::remove_file(path);
    let mut f = std::fs::OpenOptions::new().write(true).create_new(true).mode(0o600).open(path).map_err(|e| format!("{}: {e}", path.display()))?;
    f.write_all(bytes).and_then(|_| f.sync_all()).map_err(|e| format!("{}: {e}", path.display()))
}

impl Custody {
    /// The custodian at `dir` (made, mode 700, when absent).
    pub fn open(dir: impl Into<PathBuf>) -> Result<Custody, String> {
        let dir = dir.into();
        std::fs::create_dir_all(&dir).map_err(|e| format!("{}: {e}", dir.display()))?;
        std::fs::set_permissions(&dir, std::fs::Permissions::from_mode(0o700)).map_err(|e| format!("{}: {e}", dir.display()))?;
        Ok(Custody { dir })
    }

    pub fn dir(&self) -> &Path {
        &self.dir
    }

    fn sealed_path(&self, company: &str) -> PathBuf {
        self.dir.join(format!("{company}.sealed"))
    }

    /// The custody key: the one in `kek`, else 32 random bytes made here, mode 600.
    ///
    /// **Read or made under one lock** (found by the UpCloud mockup, 2026-10-08:
    /// two orders paid in the same second — capture at the order — both found
    /// no key, both made one, the second write replaced the first, and the
    /// first estate's seal no longer opened: "WrongKey").
    fn kek(&self) -> Result<[u8; 32], String> {
        static MINTING: std::sync::Mutex<()> = std::sync::Mutex::new(());
        let _one = MINTING.lock().unwrap_or_else(|p| p.into_inner());
        let path = self.dir.join(KEK_FILE);
        match std::fs::read(&path) {
            Ok(b) => arr(&b, "the custody key"),
            Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
                let k = random32()?;
                write_private(&path, &k)?;
                Ok(k)
            }
            Err(e) => Err(format!("{}: {e}", path.display())),
        }
    }

    /// Whether the estate's secrets are kept here.
    pub fn holds(&self, company: &str) -> bool {
        self.sealed_path(company).is_file()
    }

    /// **Make the estate's secrets and seal them** — once: a second call for
    /// the same company keeps what is there (a resumed job must not re-key an
    /// estate whose boxes already trust the first seed).
    ///
    /// A seal made before the first-login invite or the estate's client key
    /// existed is given them here, every other secret kept (a job resumed past
    /// the hold calls this again before its install).
    pub fn mint(&self, company: &str) -> Result<(), String> {
        if self.holds(company) {
            let mut s = self.unseal(company)?;
            if s.first_login.is_none() || s.estate_client.is_none() {
                if s.first_login.is_none() {
                    s.first_login = Some(random32()?);
                }
                if s.estate_client.is_none() {
                    s.estate_client = Some(random32()?);
                }
                self.seal(company, &s)?;
            }
            return Ok(());
        }
        let s = EstateSecrets {
            signing_seed: random32()?,
            pairing_seed: random32()?,
            break_glass: random32()?,
            first_login: Some(random32()?),
            estate_client: Some(random32()?),
            pairing_material: Vec::new(),
        };
        self.seal(company, &s)
    }

    fn seal(&self, company: &str, s: &EstateSecrets) -> Result<(), String> {
        let plain = serde_json::to_vec(&Plain {
            signing_seed: hex(&s.signing_seed),
            pairing_seed: hex(&s.pairing_seed),
            break_glass: hex(&s.break_glass),
            first_login: s.first_login.as_ref().map(|b| hex(b)).unwrap_or_default(),
            estate_client: s.estate_client.as_ref().map(|b| hex(b)).unwrap_or_default(),
            pairing_material: s.pairing_material.clone(),
        })
        .map_err(|e| e.to_string())?;
        let sealed = seal::seal(&self.kek()?, MAGIC, &aad(company), &plain).map_err(|e| format!("custody: {e}"))?;
        let tmp = self.dir.join(format!(".{company}.sealed.tmp"));
        write_private(&tmp, &sealed)?;
        std::fs::rename(&tmp, self.sealed_path(company)).map_err(|e| format!("custody: {e}"))
    }

    /// **Open the estate's seal**, in memory.
    pub fn unseal(&self, company: &str) -> Result<EstateSecrets, String> {
        let path = self.sealed_path(company);
        let sealed = std::fs::read(&path).map_err(|e| format!("custody: {company} is not held here ({e})"))?;
        let plain = seal::open(&self.kek()?, MAGIC, &aad(company), &sealed).map_err(|e| format!("custody: {company}'s seal does not open under this custody key ({e:?})"))?;
        let p: Plain = serde_json::from_slice(&plain).map_err(|e| format!("custody: {e}"))?;
        Ok(EstateSecrets {
            signing_seed: arr(&unhex(&p.signing_seed)?, "the signing seed")?,
            pairing_seed: arr(&unhex(&p.pairing_seed)?, "the pairing seed")?,
            break_glass: arr(&unhex(&p.break_glass)?, "the break-glass bearer")?,
            first_login: if p.first_login.is_empty() { None } else { Some(arr(&unhex(&p.first_login)?, "the first-login invite")?) },
            estate_client: if p.estate_client.is_empty() { None } else { Some(arr(&unhex(&p.estate_client)?, "the estate's client key")?) },
            pairing_material: p.pairing_material,
        })
    }

    /// **Unseal for one step**: the files the kit reads, in `dir` (mode 700,
    /// each file 600). Deleted when the returned [`HandOff`] is dropped.
    pub fn hand_off(&self, company: &str, dir: &Path) -> Result<HandOff, String> {
        let s = self.unseal(company)?;
        let _ = std::fs::remove_dir_all(dir);
        std::fs::create_dir_all(dir.join(handoff::RETURN_DIR)).map_err(|e| format!("{}: {e}", dir.display()))?;
        for d in [dir.to_path_buf(), dir.join(handoff::RETURN_DIR)] {
            std::fs::set_permissions(&d, std::fs::Permissions::from_mode(0o700)).map_err(|e| format!("{}: {e}", d.display()))?;
        }
        let guard = HandOff { dir: dir.to_path_buf() };
        write_private(&dir.join(handoff::ROOT_SEED), &s.signing_seed)?;
        write_private(&dir.join(handoff::CONSOLE_TOKEN), hex(&s.break_glass).as_bytes())?;
        write_private(&dir.join(handoff::PAIRING_SEED), hex(&s.pairing_seed).as_bytes())?;
        if let Some(invite) = &s.first_login {
            write_private(&dir.join(handoff::FIRST_LOGIN), hex(invite).as_bytes())?;
        }
        if let Some(seed) = &s.estate_client {
            write_private(&dir.join(handoff::ESTATE_CLIENT), hex(seed).as_bytes())?;
        }
        // A later step reaches the estate with the pairing material the install
        // handed back (the installer CA and its keys, as the kit's material
        // directory holds them) — until the hand-off forgets it all. An upgrade or
        // a recharge of a sold estate therefore has NO material here: the estate
        // took its own (`handoff::INSTALLER_MATERIAL`, F4).
        if !s.pairing_material.is_empty() {
            let m = dir.join(handoff::MATERIAL_DIR);
            std::fs::create_dir_all(&m).map_err(|e| format!("{}: {e}", m.display()))?;
            std::fs::set_permissions(&m, std::fs::Permissions::from_mode(0o700)).map_err(|e| format!("{}: {e}", m.display()))?;
            for (name, text) in &s.pairing_material {
                if name.contains('/') || name.starts_with('.') {
                    return Err(format!("custody: a handed-back file is named {name:?}"));
                }
                write_private(&m.join(name), text.as_bytes())?;
            }
        }
        Ok(guard)
    }

    /// **The buyer's first-login invite**, 64 hex, for the "ready" mail's
    /// link — `None` when the seal holds none. Never logged by a caller.
    pub fn first_login(&self, company: &str) -> Result<Option<String>, String> {
        Ok(self.unseal(company)?.first_login.map(|b| hex(&b)))
    }

    /// **The public half of the estate's client key**, 64 hex — what the order
    /// records and the shop verifies the estate's calls under. `None` when the
    /// seal holds none yet ([`Self::mint`] adds one).
    pub fn estate_client_public(&self, company: &str) -> Result<Option<String>, String> {
        Ok(self.unseal(company)?.estate_client.map(|seed| hex(&monetize::Signer::from_bytes(&seed).public_key())))
    }

    fn credential_path(&self, name: &str) -> PathBuf {
        self.dir.join(format!("credential.{name}.sealed"))
    }

    fn credential_name_ok(name: &str) -> Result<(), String> {
        if name.is_empty() || !name.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '.' || c == '-') || name.starts_with('.') {
            return Err(format!("custody: {name:?} is not a credential's name (lowercase letters, digits, `.` and `-`)"));
        }
        Ok(())
    }

    /// **Seal one of THIS server's own credentials** (a DNS writer's zone and
    /// token: `dns.cloudflare`) under the custody key, the name bound into the
    /// seal. Replaces what was sealed under the name.
    pub fn seal_credential(&self, name: &str, plain: &[u8]) -> Result<(), String> {
        Self::credential_name_ok(name)?;
        let sealed = seal::seal(&self.kek()?, MAGIC, &credential_aad(name), plain).map_err(|e| format!("custody: {e}"))?;
        let tmp = self.dir.join(format!(".credential.{name}.sealed.tmp"));
        write_private(&tmp, &sealed)?;
        std::fs::rename(&tmp, self.credential_path(name)).map_err(|e| format!("custody: {e}"))
    }

    /// **Open one credential**, in memory, for the call that needs it.
    pub fn unseal_credential(&self, name: &str) -> Result<Vec<u8>, String> {
        Self::credential_name_ok(name)?;
        let sealed = std::fs::read(self.credential_path(name)).map_err(|_| format!("custody: no credential {name} is sealed here (`monetize-server --custody-credential {name}` seals one from stdin)"))?;
        seal::open(&self.kek()?, MAGIC, &credential_aad(name), &sealed).map_err(|e| format!("custody: the credential {name} does not open under this custody key ({e:?})"))
    }

    /// **The pairing material sealed for `company`** (the kit's hand-back, or the shop's own
    /// pairing's mint), by file name. Never logged by a caller.
    pub fn pairing_files(&self, company: &str) -> Result<Vec<(String, String)>, String> {
        Ok(self.unseal(company)?.pairing_material.clone())
    }

    /// **Seal pairing material minted in this process** (the shop's own pairing, before the
    /// lay): added to what is sealed, a file of the same name replaced.
    pub fn seal_pairing(&self, company: &str, files: Vec<(String, String)>) -> Result<(), String> {
        for (name, _) in &files {
            if name.is_empty() || name.contains('/') || name.starts_with('.') {
                return Err(format!("custody: a pairing file is named {name:?}"));
            }
        }
        let mut s = self.unseal(company)?;
        s.pairing_material.retain(|(n, _)| !files.iter().any(|(m, _)| m == n));
        s.pairing_material.extend(files);
        s.pairing_material.sort();
        self.seal(company, &s)
    }

    /// **Seal what the kit handed back** (its `return/` directory: the pairing
    /// material it minted) into the estate's custody, beside the seeds.
    /// Answers how many files were taken. The plain copies go with the hand-off.
    pub fn take_back(&self, company: &str, handoff: &HandOff) -> Result<usize, String> {
        let back = handoff.dir.join(handoff::RETURN_DIR);
        let mut files: Vec<(String, String)> = Vec::new();
        if let Ok(rd) = std::fs::read_dir(&back) {
            for e in rd.flatten() {
                if e.path().is_file() {
                    let text = std::fs::read_to_string(e.path()).map_err(|x| format!("{}: {x}", e.path().display()))?;
                    files.push((e.file_name().to_string_lossy().into_owned(), text));
                }
            }
        }
        if files.is_empty() {
            return Ok(0);
        }
        let mut s = self.unseal(company)?;
        let n = files.len();
        // A file handed back replaces the one of its name; the others stand (F4: the pairing
        // hands back only its bundle beside the material minted at the payment).
        let mut all: std::collections::BTreeMap<String, String> = std::mem::take(&mut s.pairing_material).into_iter().collect();
        all.extend(files);
        s.pairing_material = all.into_iter().collect();
        self.seal(company, &s)?;
        Ok(n)
    }

    /// **The installer material its estate keeps** ([`handoff::INSTALLER_MATERIAL`]), while the
    /// shop still holds the estate's seal: `None` when the install handed none back.
    pub fn installer_material(&self, company: &str) -> Result<Option<String>, String> {
        Ok(self.unseal(company)?.pairing_material.iter().find(|(n, _)| n == handoff::INSTALLER_MATERIAL).map(|(_, t)| t.clone()))
    }

    /// **The estate's installer link file** ([`LINK_KEYS_FILE`]: the installer's WireGuard/X25519
    /// private key and the VM's public key), read IN PLACE for the step that needs it (the
    /// DEV-NARRATE receiver, lane N2): from the estate's seal while it is held, else from the
    /// link seal a narrated site keeps past its hand-off ([`Self::forget_keeping_link`]).
    /// `None`: neither holds one. Never written anywhere, never logged by a caller.
    pub fn installer_link(&self, company: &str) -> Result<Option<String>, String> {
        if self.holds(company) {
            return Ok(self.unseal(company)?.pairing_material.iter().find(|(n, _)| n == LINK_KEYS_FILE).map(|(_, t)| t.clone()));
        }
        let path = self.dir.join(format!("{company}{LINK_SUFFIX}"));
        let sealed = match std::fs::read(&path) {
            Ok(b) => b,
            Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
            Err(e) => return Err(format!("custody: {e}")),
        };
        let plain = seal::open(&self.kek()?, MAGIC, &link_aad(company), &sealed).map_err(|e| format!("custody: {company}'s link seal does not open under this custody key ({e:?})"))?;
        String::from_utf8(plain).map(Some).map_err(|e| format!("custody: {company}'s link file: {e}"))
    }

    /// **The hand-off of a NARRATED site** (lane N2): every secret forgotten as [`Self::forget`]
    /// forgets it, except the installer's link file, which moves out of the estate's seal into
    /// a seal of its own (nothing else is in it), so the site's DEV-NARRATE receiver keeps
    /// hearing its boxes. Never a plain copy: the file goes from one seal to the other.
    pub fn forget_keeping_link(&self, company: &str) -> Result<(), String> {
        if let Some(link) = self.installer_link(company)? {
            let sealed = seal::seal(&self.kek()?, MAGIC, &link_aad(company), link.as_bytes()).map_err(|e| format!("custody: {e}"))?;
            let tmp = self.dir.join(format!(".{company}{LINK_SUFFIX}.tmp"));
            write_private(&tmp, &sealed)?;
            std::fs::rename(&tmp, self.dir.join(format!("{company}{LINK_SUFFIX}"))).map_err(|e| format!("custody: {e}"))?;
        }
        for p in [self.sealed_path(company), self.dir.join(format!("{company}{LEGACY_READER_SUFFIX}"))] {
            match std::fs::remove_file(&p) {
                Ok(()) => {}
                Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
                Err(e) => return Err(format!("custody: {e}")),
            }
        }
        Ok(())
    }

    fn offsite_path(&self, company: &str) -> PathBuf {
        self.dir.join(format!("{company}{OFFSITE_SUFFIX}"))
    }

    /// The off-site seal's files, `None` when there is none.
    fn offsite_files(&self, company: &str) -> Result<Option<Vec<(String, String)>>, String> {
        let sealed = match std::fs::read(self.offsite_path(company)) {
            Ok(b) => b,
            Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
            Err(e) => return Err(format!("custody: {e}")),
        };
        let plain = seal::open(&self.kek()?, MAGIC, &offsite_aad(company), &sealed).map_err(|e| format!("custody: {company}'s off-site seal does not open under this custody key ({e:?})"))?;
        serde_json::from_slice(&plain).map(Some).map_err(|e| format!("custody: {company}'s off-site seal: {e}"))
    }

    /// One file of the estate's off-site pair: from the estate's seal while it is held, else
    /// from the off-site seal the hand-off kept.
    fn offsite_file(&self, company: &str, name: &str) -> Result<Option<String>, String> {
        let find = |files: &[(String, String)]| files.iter().find(|(n, _)| n == name).map(|(_, t)| t.trim().to_string()).filter(|t| !t.is_empty());
        if self.holds(company) {
            if let Some(t) = find(&self.unseal(company)?.pairing_material) {
                return Ok(Some(t));
            }
        }
        Ok(self.offsite_files(company)?.as_deref().and_then(find))
    }

    /// **The estate's off-site recipient** (lane O1): the PUBLIC half of its off-site pair,
    /// base64 — what its main is fed as `offsite_recipient`. `None`: no pair was minted.
    pub fn offsite_recipient(&self, company: &str) -> Result<Option<String>, String> {
        self.offsite_file(company, OFFSITE_PUB_FILE)
    }

    /// **The private half of the estate's off-site pair**, base64, for the operator's restore
    /// of one of its copies. Never logged, never written by a caller.
    pub fn offsite_identity(&self, company: &str) -> Result<Option<String>, String> {
        self.offsite_file(company, OFFSITE_KEY_FILE)
    }

    /// The estate's off-site pair moved from its seal into the off-site seal (nothing else in
    /// it); a pair already kept is kept. Nothing to keep: nothing is written.
    fn keep_offsite(&self, company: &str) -> Result<(), String> {
        if !self.holds(company) {
            return Ok(());
        }
        let pair: Vec<(String, String)> = self.unseal(company)?.pairing_material.iter().filter(|(n, _)| n == OFFSITE_KEY_FILE || n == OFFSITE_PUB_FILE).cloned().collect();
        if pair.len() != 2 {
            return Ok(());
        }
        let plain = serde_json::to_vec(&pair).map_err(|e| e.to_string())?;
        let sealed = seal::seal(&self.kek()?, MAGIC, &offsite_aad(company), &plain).map_err(|e| format!("custody: {e}"))?;
        let tmp = self.dir.join(format!(".{company}{OFFSITE_SUFFIX}.tmp"));
        write_private(&tmp, &sealed)?;
        std::fs::rename(&tmp, self.offsite_path(company)).map_err(|e| format!("custody: {e}"))
    }

    /// **The hand-off's forgetting** (lane O1): every secret of the estate forgotten as
    /// [`Self::forget`] forgets it — the installer link kept in its own seal when `keep_link`
    /// (a narrated site, [`Self::forget_keeping_link`]) — except the estate's off-site pair,
    /// which the operator keeps to open the estate's copies ([`OFFSITE_SUFFIX`]).
    pub fn forget_handed_off(&self, company: &str, keep_link: bool) -> Result<(), String> {
        self.keep_offsite(company)?;
        if keep_link {
            return self.forget_keeping_link(company);
        }
        for p in [self.sealed_path(company), self.dir.join(format!("{company}{LEGACY_READER_SUFFIX}")), self.dir.join(format!("{company}{LINK_SUFFIX}"))] {
            match std::fs::remove_file(&p) {
                Ok(()) => {}
                Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
                Err(e) => return Err(format!("custody: {e}")),
            }
        }
        Ok(())
    }

    /// **Forget an estate** — one never delivered (its job was released or
    /// refunded), one deleted, or one handed off (the owner via the
    /// coordinator, 2026-10-08: after a PASSed hand-off the shop keeps only its
    /// own facts key and the estate's PUBLIC client key): every secret — the
    /// seeds, bearer, backup key, invite, client seed, the CA's key and the
    /// operator's certificate — and a 5.3.1 health reader left behind.
    pub fn forget(&self, company: &str) -> Result<(), String> {
        for p in [self.sealed_path(company), self.dir.join(format!("{company}{LEGACY_READER_SUFFIX}")), self.dir.join(format!("{company}{LINK_SUFFIX}")), self.offsite_path(company)] {
            match std::fs::remove_file(&p) {
                Ok(()) => {}
                Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
                Err(e) => return Err(format!("custody: {e}")),
            }
        }
        Ok(())
    }
}

/// **The unsealed files of one step.** Deleted when dropped — whatever the
/// step's outcome, a crash of the step included.
#[derive(Debug)]
pub struct HandOff {
    dir: PathBuf,
}

impl HandOff {
    pub fn dir(&self) -> &Path {
        &self.dir
    }
}

impl Drop for HandOff {
    fn drop(&mut self) {
        // Overwrite before unlinking: a deleted file's blocks are not erased
        // (the files of `return/` and `material/` included).
        fn wipe(dir: &Path) {
            if let Ok(rd) = std::fs::read_dir(dir) {
                for e in rd.flatten() {
                    match e.metadata() {
                        Ok(m) if m.is_file() => {
                            let _ = std::fs::write(e.path(), vec![0u8; m.len() as usize]);
                        }
                        Ok(m) if m.is_dir() => wipe(&e.path()),
                        _ => {}
                    }
                }
            }
        }
        wipe(&self.dir);
        let _ = std::fs::remove_dir_all(&self.dir);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// ★ Two estates paid in the same second: one custody key, every seal opens.
    #[test]
    fn estates_sealed_at_once_share_one_custody_key() {
        let dir = std::env::temp_dir().join(format!("monetize-custody-race-{}", std::process::id()));
        let _ = std::fs::remove_dir_all(&dir);
        let names: Vec<String> = (0..8).map(|i| format!("co{i}")).collect();
        let threads: Vec<_> = names
            .iter()
            .cloned()
            .map(|n| {
                let d = dir.clone();
                std::thread::spawn(move || Custody::open(d).unwrap().mint(&n).unwrap())
            })
            .collect();
        for t in threads {
            t.join().unwrap();
        }
        let c = Custody::open(&dir).unwrap();
        for n in &names {
            c.unseal(n).unwrap_or_else(|e| panic!("{n}: {e}"));
        }
        let _ = std::fs::remove_dir_all(&dir);
    }

    fn tmp(name: &str) -> PathBuf {
        let d = std::env::temp_dir().join(format!("monetize-custody-{}-{name}", std::process::id()));
        let _ = std::fs::remove_dir_all(&d);
        d
    }

    #[test]
    fn a_sealed_estate_opens_only_under_its_own_name_and_key() {
        let dir = tmp("seal");
        let c = Custody::open(&dir).unwrap();
        c.mint("acme").unwrap();
        let first = c.unseal("acme").unwrap().signing_seed;
        c.mint("acme").unwrap();
        assert_eq!(c.unseal("acme").unwrap().signing_seed, first, "a second mint keeps the estate's seed");
        // The file is not the secret.
        let sealed = std::fs::read(dir.join("acme.sealed")).unwrap();
        assert!(sealed.starts_with(b"MCUS1\0"));
        assert!(!String::from_utf8_lossy(&sealed).contains(&hex(&first)), "the seed is not in the sealed file");
        // Another estate's name does not open it.
        std::fs::copy(dir.join("acme.sealed"), dir.join("evil.sealed")).unwrap();
        assert!(c.unseal("evil").is_err(), "the company name is bound into the seal");
        // Another custody key does not open it.
        std::fs::remove_file(dir.join(KEK_FILE)).unwrap();
        assert!(c.unseal("acme").is_err(), "only this custody key opens it");
        let mode = std::fs::metadata(dir.join(KEK_FILE)).unwrap().permissions().mode() & 0o777;
        assert_eq!(mode, 0o600);
    }

    #[test]
    fn a_hand_off_is_private_and_gone_when_dropped_and_the_return_is_sealed() {
        let dir = tmp("handoff");
        let c = Custody::open(dir.join("custody")).unwrap();
        c.mint("acme").unwrap();
        let at = dir.join("step");
        {
            let h = c.hand_off("acme", &at).unwrap();
            let seed = std::fs::read(at.join(handoff::ROOT_SEED)).unwrap();
            assert_eq!(seed.len(), 32);
            assert_eq!(std::fs::metadata(at.join(handoff::ROOT_SEED)).unwrap().permissions().mode() & 0o777, 0o600);
            assert_eq!(std::fs::metadata(&at).unwrap().permissions().mode() & 0o777, 0o700);
            std::fs::write(at.join(handoff::RETURN_DIR).join("ca.key.pem"), "-----BEGIN PRIVATE KEY-----x").unwrap();
            assert_eq!(c.take_back("acme", &h).unwrap(), 1);
        }
        assert!(!at.exists(), "the hand-off is deleted when the step ends");
        let s = c.unseal("acme").unwrap();
        assert_eq!(s.pairing_material, vec![("ca.key.pem".to_string(), "-----BEGIN PRIVATE KEY-----x".to_string())]);
        assert!(!format!("{s:?}").contains("BEGIN"), "Debug names, never values");
        c.forget("acme").unwrap();
        assert!(!c.holds("acme"));
    }

    /// ★ **A later step gets the material the install handed back**: the
    /// hand-off carries `material/` (the kit's installer material directory),
    /// private, and gone — overwritten — when the step ends.
    #[test]
    fn a_later_hand_off_carries_the_handed_back_material_and_wipes_it() {
        let dir = tmp("material");
        let c = Custody::open(dir.join("custody")).unwrap();
        c.mint("acme").unwrap();
        let first = dir.join("install");
        {
            let h = c.hand_off("acme", &first).unwrap();
            assert!(!first.join(handoff::MATERIAL_DIR).exists(), "nothing was handed back yet");
            std::fs::write(first.join(handoff::RETURN_DIR).join("installer-ca.cert.pem"), "-----BEGIN CERTIFICATE-----ca").unwrap();
            std::fs::write(first.join(handoff::RETURN_DIR).join("installer.key.pem"), "-----BEGIN PRIVATE KEY-----k").unwrap();
            c.take_back("acme", &h).unwrap();
        }
        let later = dir.join("twin");
        {
            let _h = c.hand_off("acme", &later).unwrap();
            let m = later.join(handoff::MATERIAL_DIR);
            assert_eq!(std::fs::read_to_string(m.join("installer.key.pem")).unwrap(), "-----BEGIN PRIVATE KEY-----k");
            assert_eq!(std::fs::metadata(m.join("installer.key.pem")).unwrap().permissions().mode() & 0o777, 0o600);
            assert_eq!(std::fs::metadata(&m).unwrap().permissions().mode() & 0o777, 0o700);
        }
        assert!(!later.exists(), "the later hand-off is gone with its material");
    }

    /// ★ **Every estate is minted its buyer's first-login invite** (private
    /// ownership, 2026-10-07): sealed with the other secrets, kept across a
    /// second mint, handed to the kit as `first-login-invite` (0600) — the
    /// kit feeds its hash — and read back for the "ready" mail's link. A seal
    /// made before the invite existed is given one by the next mint, every
    /// other secret kept.
    #[test]
    fn every_estate_is_minted_a_first_login_invite_handed_to_the_kit_and_an_older_seal_gets_one() {
        let dir = tmp("first-login");
        let c = Custody::open(dir.join("custody")).unwrap();
        c.mint("acme").unwrap();
        let invite = c.first_login("acme").unwrap().expect("minted with the estate's secrets");
        assert_eq!(invite.len(), 64);
        assert!(invite.bytes().all(|b| b.is_ascii_hexdigit()));
        c.mint("acme").unwrap();
        assert_eq!(c.first_login("acme").unwrap().as_deref(), Some(invite.as_str()), "a second mint keeps it");
        assert!(!String::from_utf8_lossy(&std::fs::read(dir.join("custody/acme.sealed")).unwrap()).contains(&invite), "sealed, never in the clear");
        let at = dir.join("install");
        {
            let _h = c.hand_off("acme", &at).unwrap();
            assert_eq!(std::fs::read_to_string(at.join(handoff::FIRST_LOGIN)).unwrap(), invite);
            assert_eq!(std::fs::metadata(at.join(handoff::FIRST_LOGIN)).unwrap().permissions().mode() & 0o777, 0o600);
        }
        // A seal from before the invite existed.
        let old = EstateSecrets { signing_seed: [1; 32], pairing_seed: [2; 32], break_glass: [3; 32], first_login: None, estate_client: None, pairing_material: vec![("ca.pem".into(), "x".into())] };
        c.seal("bolt", &old).unwrap();
        assert_eq!(c.first_login("bolt").unwrap(), None);
        c.mint("bolt").unwrap();
        let s = c.unseal("bolt").unwrap();
        assert!(s.first_login.is_some(), "the next mint adds the invite");
        assert_eq!((s.signing_seed, s.pairing_seed, s.break_glass), ([1; 32], [2; 32], [3; 32]), "every other secret kept");
        assert_eq!(s.pairing_material.len(), 1);
    }

    /// ★ **A sold estate's client key** (one shop, 2026-10-07): minted once
    /// with the estate's other secrets (and given to a seal from before it
    /// existed), sealed, handed off as 64 hex for the kit to feed the estate's
    /// front, and named by its PUBLIC half — the key the shop verifies the
    /// estate's calls under.
    #[test]
    fn a_sold_estates_client_key_is_minted_once_handed_off_and_named_by_its_public_half() {
        let dir = tmp("estate-client");
        let c = Custody::open(dir.join("custody")).unwrap();
        c.mint("acme").unwrap();
        let public = c.estate_client_public("acme").unwrap().expect("minted with the estate");
        assert_eq!(public.len(), 64);
        c.mint("acme").unwrap();
        assert_eq!(c.estate_client_public("acme").unwrap().as_deref(), Some(public.as_str()), "a second mint keeps it");
        let at = dir.join("install");
        {
            let _h = c.hand_off("acme", &at).unwrap();
            let seed = std::fs::read_to_string(at.join(handoff::ESTATE_CLIENT)).unwrap();
            assert_eq!(seed.len(), 64);
            assert_eq!(std::fs::metadata(at.join(handoff::ESTATE_CLIENT)).unwrap().permissions().mode() & 0o777, 0o600);
            let seed: [u8; 32] = unhex(&seed).unwrap().try_into().unwrap();
            assert_eq!(hex(&monetize::Signer::from_bytes(&seed).public_key()), public, "the handed-off seed is the named key's");
            assert!(!String::from_utf8_lossy(&std::fs::read(dir.join("custody/acme.sealed")).unwrap()).contains(&hex(&seed)), "sealed, never in the clear");
        }
        let old = EstateSecrets { signing_seed: [1; 32], pairing_seed: [2; 32], break_glass: [3; 32], first_login: Some([5; 32]), estate_client: None, pairing_material: Vec::new() };
        c.seal("bolt", &old).unwrap();
        assert_eq!(c.estate_client_public("bolt").unwrap(), None);
        c.mint("bolt").unwrap();
        let s = c.unseal("bolt").unwrap();
        assert!(s.estate_client.is_some(), "the next mint adds the client key");
        assert_eq!((s.signing_seed, s.first_login), ([1; 32], Some([5; 32])), "every other secret kept");
    }

    /// ★ **This server's own credential** (a DNS writer's zone and token) is
    /// sealed under its name and the custody key, opens only under both, and
    /// is never in the file in the clear; an unsealed name says how to seal it.
    #[test]
    fn a_credential_is_sealed_under_its_name_and_opens_only_there() {
        let dir = tmp("credential");
        let c = Custody::open(&dir).unwrap();
        let plain = br#"{"zone_id": "z0ne-id", "api_token": "cf-s3cr3t-token"}"#;
        let missing = c.unseal_credential("dns.cloudflare").unwrap_err();
        assert!(missing.contains("--custody-credential dns.cloudflare"), "{missing}");
        c.seal_credential("dns.cloudflare", plain).unwrap();
        assert_eq!(c.unseal_credential("dns.cloudflare").unwrap(), plain.to_vec());
        let file = std::fs::read(dir.join("credential.dns.cloudflare.sealed")).unwrap();
        assert!(!String::from_utf8_lossy(&file).contains("cf-s3cr3t"), "the token is not in the sealed file");
        assert_eq!(std::fs::metadata(dir.join("credential.dns.cloudflare.sealed")).unwrap().permissions().mode() & 0o777, 0o600);
        std::fs::copy(dir.join("credential.dns.cloudflare.sealed"), dir.join("credential.dns.other.sealed")).unwrap();
        assert!(c.unseal_credential("dns.other").is_err(), "the name is bound into the seal");
        std::fs::copy(dir.join("credential.dns.cloudflare.sealed"), dir.join("acme.sealed")).unwrap();
        assert!(c.unseal("acme").is_err(), "a credential is not an estate's seal");
        assert!(c.seal_credential("../escape", plain).is_err() && c.seal_credential("", plain).is_err());
    }

    /// ★ **After the hand-off the shop keeps nothing of the estate** (ONE monetize, F4
    /// 2026-10-10: the twin is read from the estate's reports, so no health reader is kept):
    /// the seal with the seeds, bearer, backup key, the CA's key and the operator's certificate
    /// is gone, and a 5.3.1 reader left beside it goes too.
    #[test]
    fn after_the_hand_off_nothing_of_the_estate_is_kept() {
        let dir = tmp("reader");
        let c = Custody::open(dir.join("custody")).unwrap();
        c.mint("acme").unwrap();
        let at = dir.join("step");
        {
            let h = c.hand_off("acme", &at).unwrap();
            for (name, text) in [("ca.cert.pem", "CA CERT"), ("ca.key.pem", "CA KEY"), ("installer.cert.pem", "OP CERT"), ("link.toml", "LINK")] {
                std::fs::write(at.join(handoff::RETURN_DIR).join(name), text).unwrap();
            }
            assert_eq!(c.take_back("acme", &h).unwrap(), 4);
        }
        std::fs::write(dir.join("custody").join("acme.reader.sealed"), b"a 5.3.1 reader").unwrap();
        c.forget("acme").unwrap();
        assert!(!c.holds("acme"), "the estate's seal is gone");
        assert!(c.unseal("acme").is_err() && c.first_login("acme").is_err(), "no seed, bearer, invite or key opens any more");
        let left: Vec<_> = std::fs::read_dir(dir.join("custody")).unwrap().flatten().map(|e| e.file_name().to_string_lossy().into_owned()).filter(|n| n.starts_with("acme")).collect();
        assert!(left.is_empty(), "no reader kept beside it: {left:?}");
    }

    /// ★ **F4: the pairing's bundle is kept beside the material, until the estate takes it**:
    /// a take-back adds the files handed back and keeps the others (the material minted at the
    /// payment stands); the bundle reads back while the seal stands, and is gone with it.
    #[test]
    fn the_installer_material_the_estate_keeps_is_held_beside_the_material_until_the_hand_off() {
        let dir = tmp("bundle");
        let c = Custody::open(dir.join("custody")).unwrap();
        c.mint("acme").unwrap();
        c.seal_pairing("acme", vec![("ca.cert.pem".into(), "CA CERT".into()), ("ca.key.pem".into(), "CA KEY".into())]).unwrap();
        assert_eq!(c.installer_material("acme").unwrap(), None, "none handed back yet");
        let at = dir.join("step");
        {
            let h = c.hand_off("acme", &at).unwrap();
            std::fs::write(at.join(handoff::RETURN_DIR).join(handoff::INSTALLER_MATERIAL), "{\"v\":\"installer-material/1\"}").unwrap();
            assert_eq!(c.take_back("acme", &h).unwrap(), 1);
        }
        let names: Vec<String> = c.pairing_files("acme").unwrap().into_iter().map(|(n, _)| n).collect();
        assert_eq!(names, ["ca.cert.pem", "ca.key.pem", handoff::INSTALLER_MATERIAL], "the material minted at the payment stands beside the bundle");
        assert_eq!(c.installer_material("acme").unwrap().as_deref(), Some("{\"v\":\"installer-material/1\"}"));
        c.forget("acme").unwrap();
        assert!(c.installer_material("acme").is_err(), "forgotten at the hand-off with the rest");
    }

    /// ★ **A narrated site's hand-off keeps its installer link file, and ONLY it** (lane N2):
    /// read in place from the estate's seal before, from its own seal after; every other
    /// secret is forgotten, and a full forget (terminate, refund) removes the link seal too.
    #[test]
    fn a_narrated_hand_off_keeps_the_link_file_alone_and_forget_ends_it() {
        let dir = tmp("narrate-link");
        let c = Custody::open(&dir).unwrap();
        assert_eq!(c.installer_link("site").unwrap(), None, "nothing held: no link");
        c.mint("site").unwrap();
        let link = "installer_private = \"secret-ish\"\nvm_public = \"pub\"\n".to_string();
        c.seal_pairing("site", vec![(LINK_KEYS_FILE.into(), link.clone()), ("ca.key".into(), "CA KEY".into())]).unwrap();
        assert_eq!(c.installer_link("site").unwrap().as_deref(), Some(link.as_str()), "read in place from the estate's seal");
        c.forget_keeping_link("site").unwrap();
        assert!(!c.holds("site"), "every other secret is forgotten");
        assert!(c.unseal("site").is_err() && c.pairing_files("site").is_err());
        assert_eq!(c.installer_link("site").unwrap().as_deref(), Some(link.as_str()), "the link file alone survives, in its own seal");
        let on_disk: Vec<u8> = std::fs::read_dir(&dir).unwrap().flatten().filter(|e| e.path().is_file()).flat_map(|e| std::fs::read(e.path()).unwrap()).collect();
        assert!(!on_disk.windows(10).any(|w| w == b"secret-ish"), "never a plain copy on disk");
        c.forget("site").unwrap();
        assert_eq!(c.installer_link("site").unwrap(), None, "a full forget ends the link seal too");
        let _ = std::fs::remove_dir_all(&dir);
    }

    /// ★ **Lane O1: the hand-off keeps the estate's OFF-SITE pair with the operator** — read
    /// from the estate's seal while it is held, from its own seal after the hand-off (with or
    /// without the narrated link); every other secret is forgotten; a full forget (refund,
    /// deletion) shreds it. An estate whose material carries no pair keeps nothing.
    #[test]
    fn the_hand_off_keeps_the_estates_offsite_pair_and_forget_shreds_it() {
        let dir = tmp("offsite-pair");
        let c = Custody::open(&dir).unwrap();
        let (key, public) = (hex(&random32().unwrap()), hex(&random32().unwrap()));
        for (co, narrated) in [("plain", false), ("narrated", true)] {
            assert_eq!(c.offsite_recipient(co).unwrap(), None, "nothing held: no recipient");
            c.mint(co).unwrap();
            c.seal_pairing(co, vec![(OFFSITE_KEY_FILE.into(), key.clone()), (OFFSITE_PUB_FILE.into(), public.clone()), (LINK_KEYS_FILE.into(), "LINK".into()), ("ca.key".into(), "CA KEY".into())]).unwrap();
            assert_eq!(c.offsite_recipient(co).unwrap().as_deref(), Some(public.as_str()), "{co}: read in place while held");
            c.forget_handed_off(co, narrated).unwrap();
            assert!(!c.holds(co) && c.pairing_files(co).is_err(), "{co}: every other secret is forgotten");
            assert_eq!(c.offsite_recipient(co).unwrap().as_deref(), Some(public.as_str()), "{co}: the recipient is kept past the hand-off");
            assert_eq!(c.offsite_identity(co).unwrap().as_deref(), Some(key.as_str()), "{co}: so is the private half, for the operator's restore");
            assert_eq!(c.installer_link(co).unwrap().is_some(), narrated, "{co}: the link only for a narrated site");
        }
        let on_disk: Vec<u8> = std::fs::read_dir(&dir).unwrap().flatten().filter(|e| e.path().is_file()).flat_map(|e| std::fs::read(e.path()).unwrap()).collect();
        assert!(!on_disk.windows(key.len()).any(|w| w == key.as_bytes()), "never a plain copy on disk");
        c.forget("plain").unwrap();
        assert_eq!((c.offsite_recipient("plain").unwrap(), c.offsite_identity("plain").unwrap()), (None, None), "a full forget shreds the pair");
        c.mint("none").unwrap();
        c.forget_handed_off("none", false).unwrap();
        assert_eq!(c.offsite_recipient("none").unwrap(), None, "no pair minted: none kept");
        assert!(!dir.join("none.offsite.sealed").exists());
        let _ = std::fs::remove_dir_all(&dir);
    }
}