crates/gunnar-front/tests/shop_pairing_contract.rs
//! ★ **The shop's side and the front's thin client speak the same words** (G11 2026-10-10;
//! K3 under ONE monetize, 2026-10-11).
//!
//! The installer a shop pairs a sold estate with is monetize's `monetize-shop-pairing` (G11),
//! which names no gunnar crate and keeps the few words of gunnar's contract it speaks as its
//! own constants. The front's side is now as thin: `shop_door` (the signed `SoldEstate`
//! client over monetize-proto) DUPLICATES the shop's call names and bundle words instead of
//! linking `monetize-server` (K3: the front embeds no monetize). So this test is the ONE
//! place both copies are held equal — monetize-server and monetize-shop-pairing are
//! dev-dependencies here, and nothing of them ships in any front build
//! (tests/one_monetize.rs, tests/one_front_pull.rs).
use std::collections::BTreeMap;
use gunnar_front::shop_door;
use gunnar_monetize_guest::lifecycle::doors;
use monetize_server::estate_order::LaidOrder;
use monetize_shop_pairing::contract as c;
/// Gunnar's contract, as the shop's pairing reads it off Gunnar's own facts (op 90).
fn gunnar() -> monetize_shop_pairing::ProductContract {
monetize_shop_pairing::ProductContract::of(&gunnar_monetize_guest::lifecycle::facts())
.expect("Gunnar's facts declare its contract")
}
fn order() -> LaidOrder {
serde_json::from_value(serde_json::json!({
"company": "acme", "apex": "test2.gunnar.rs", "domain": "",
"names": ["acme.test2.gunnar.rs", "git.acme.test2.gunnar.rs"],
"estate": {"reference": "estate/acme", "company": "acme", "zone": "se-sto1", "machines": [
{"role": "front", "hostname": "f", "title": "F", "plan": "STARTER-4xCPU-8GB", "server": "s1", "public_ipv4": "198.51.100.1", "utility_ipv4": "10.6.0.1", "disks": [{"title": "sys", "id": "d1", "gib": 40, "tier": "standard", "address": "virtio:0"}]},
{"role": "main", "hostname": "m", "title": "M", "plan": "STARTER-4xCPU-16GB", "server": "s2", "public_ipv4": "198.51.100.2", "utility_ipv4": "10.6.0.2", "disks": [{"title": "sys", "id": "d2", "gib": 50, "tier": "standard", "address": "virtio:0"}, {"title": "data", "id": "d4", "gib": 10, "tier": "maxiops", "address": "virtio:1"}]},
{"role": "twin", "hostname": "t", "title": "T", "plan": "STARTER-1xCPU-2GB", "server": "s3", "public_ipv4": "198.51.100.3", "utility_ipv4": "10.6.0.3", "disks": [{"title": "sys", "id": "d3", "gib": 20, "tier": "standard", "address": "virtio:0"}]}
]},
"first_login_ttl_secs": 604800
}))
.unwrap()
}
/// ★ Every word of gunnar's contract the shop's pairing keeps is gunnar's own.
#[test]
fn the_shop_pairings_contract_words_are_gunnars() {
use gunnar_grpc::topology as t;
assert_eq!(
c::FEED_SHOP_DOOR,
gunnar_front::recharge::FEED_SHOP_DOOR,
"the shop door the front calls IN through (F4)"
);
assert_eq!(
c::FEED_OPERATOR_WELCOME,
gunnar_server::fed_principals::FEED_OPERATOR_WELCOME
);
assert_eq!(
(c::WELCOME_REPO, doors::WELCOME_REPO),
(
gunnar_server::fed_principals::WELCOME_REPO,
gunnar_server::fed_principals::WELCOME_REPO
)
);
assert_eq!(
c::FEED_BILLING_URL,
gunnar_server::feed::FEED_BILLING_URL,
"the sold site's pay page (UW-6)"
);
assert_eq!(
[
c::FEED_RECORD,
c::RECORD_SCHEMA,
c::FEED_VARIANT,
c::FEED_VERSIONS,
c::FEED_BOXES,
c::FEED_BOX_PREFIX
],
[
t::FEED_RECORD,
t::RECORD_SCHEMA,
t::FEED_VARIANT,
t::FEED_VERSIONS,
t::FEED_BOXES,
t::FEED_BOX_PREFIX
]
);
}
/// ★ **The doors the shop's sale check reads are the console's own** (S4, 2026-10-11): before
/// a sale is captured the shop proves the sold front's PUBLIC `https://<host>` — the login page
/// (and the passkey sign-in only that page carries), the console's status and the first-login
/// door the ready mail opens, all reached with no session as the buyer reaches them; and the
/// repository list, which only the console's bearer reads, in the console's `Page` shape.
#[test]
fn the_sale_checks_doors_are_the_consoles_own() {
use gunnar_ui_server::auth::middleware::is_public;
for door in [
doors::LOGIN_PATH,
doors::AUTH_STATUS_PATH,
doors::REGISTER_START_PATH,
] {
assert!(
is_public(door),
"{door}: the buyer reaches it with no session"
);
}
assert!(
!is_public(doors::REPOS_PATH),
"the repository list is read with the console's bearer"
);
let page = gunnar_ui_server::auth::routes::login_html();
assert!(
page.contains(doors::LOGIN_PAGE_MARK) && page.contains(doors::REGISTER_START_PATH),
"the login page carries its passkey doors"
);
// Every route Gunnar's sale proof asks is one of these, asked as the console serves it.
let proof = gunnar().proof;
for check in &proof.checks {
let path = check.ask.path.split('?').next().unwrap_or_default();
assert!(
is_public(path) != check.ask.bearer,
"{path}: asked with the bearer exactly when only the bearer reads it"
);
}
// A listed repository's `path` and `name`, as the console serializes its own `Repo`.
fn _keys(r: &gunnar_ui_server::api::repos::Repo) -> (&str, &str) {
(&r.path, &r.name)
}
let listed = serde_json::to_value(gunnar_ui_server::api::Page::of(
vec![serde_json::json!({"path": "alice/welcome", "name": doors::WELCOME_REPO})],
doors::REPOS_MAX,
))
.unwrap();
assert_eq!(
(
listed["items"][0]["path"].as_str(),
listed["truncated"].as_bool()
),
(Some("alice/welcome"), Some(false))
);
let all: Vec<u32> = (0..u32::try_from(doors::REPOS_MAX).unwrap() + 1).collect();
assert_eq!(
gunnar_ui_server::api::Page::of(all, doors::REPOS_MAX)
.items
.len(),
doors::REPOS_MAX,
"the most the console lists at once"
);
}
/// ★ **The thin client's duplicated words are the shop's own** (K3): the signed `SoldEstate`
/// call names, the seed file names, the kept bundle's version and life, the client key's
/// secret name, and each verb's patience. A drift here is a refusal at the ONE monetize's
/// door — this test is why the duplication is safe.
#[test]
fn the_thin_shop_doors_words_are_the_shops_own() {
use monetize_server::estate_order::sold;
use monetize_server::shop_cloud;
assert_eq!(shop_door::CALL_RECHARGE, sold::CALL_RECHARGE);
assert_eq!(shop_door::CALL_UPGRADE, sold::CALL_UPGRADE);
assert_eq!(shop_door::CALL_TAKE_MATERIAL, sold::CALL_TAKE_MATERIAL);
assert_eq!(
shop_door::CALL_UPDATE_TAKE,
monetize_server::estate_order::update::CALL_UPDATE_TAKE
);
assert_eq!(
shop_door::CALL_UPDATE_REPORT,
monetize_server::estate_order::update::CALL_UPDATE_REPORT
);
assert_eq!(
shop_door::SEED_FILE_PREFIX,
monetize_server::estate_order::SEED_FILE_PREFIX
);
assert_eq!(shop_door::BUNDLE_V, monetize_shop_pairing::BUNDLE_V);
assert_eq!(
shop_door::MATERIAL_VALID,
monetize_shop_pairing::MATERIAL_VALID
);
assert_eq!(shop_door::CLIENT_KEY_SECRET, shop_cloud::CLIENT_KEY_SECRET);
// The front never gives up on a Recharge before the shop does (the ONE client's bound is
// Holger's hour; the shop lays a lost box within its 30 min, T16 / P4-REST).
assert!(shop_door::RECHARGE_WITHIN >= shop_cloud::RECHARGE_WITHIN);
assert_eq!(shop_door::UPGRADE_WITHIN, shop_cloud::UPGRADE_WITHIN);
assert_eq!(shop_door::READ_WITHIN, shop_cloud::READ_WITHIN);
}
/// ★ **The bundle the shop keeps for a sold estate is the one the front's `recharge` reads**:
/// minted by monetize-shop-pairing, parsed by `Kept`, and the front can be the estate's
/// installer from it.
#[test]
fn the_shops_kept_bundle_is_read_by_the_fronts_recharge() {
let files: BTreeMap<String, String> =
monetize_shop_pairing::mint_material_for(&gunnar(), "acme.test2.gunnar.rs")
.unwrap()
.into_iter()
.collect();
let plan = monetize_shop_pairing::plan_of_for(
&gunnar(),
&order(),
BTreeMap::new(),
BTreeMap::new(),
&BTreeMap::new(),
600,
)
.unwrap();
// The very shape `monetize_shop_pairing::bundle` renders (it reads from a material dir on
// disk; the shape is its contract, under the shop's own BUNDLE_V).
let bytes = serde_json::to_vec(&serde_json::json!({"v": monetize_shop_pairing::BUNDLE_V, "files": files, "ledger": "ledger = \"\"\n", "plan": plan})).unwrap();
let kept =
gunnar_front::recharge::Kept::parse(&bytes).expect("the front reads the shop's bundle");
assert_eq!(kept.plan, plan);
assert!(
kept.material().is_ok(),
"the front can be the estate's installer from it"
);
assert!(
kept.fresh_seed(gunnar_front::recharge::GUNNAR_SEED).is_ok(),
"a fresh seed from the kept CA"
);
}
/// ★ **The estate record a sold estate is fed is the one gunnar reads**, and exactly what
/// gunnar's own `Record::to_feed` writes for it.
#[test]
fn the_record_the_shop_feeds_is_the_one_gunnar_reads() {
use gunnar_grpc::topology::{Record, RecordBox};
let f = monetize_shop_pairing::shared_feed_for(
&gunnar(),
&order(),
"acme-owner",
"ssh-ed25519 AAAA op",
"invite",
)
.unwrap();
let b = |name: &str,
host: &str,
title: &str,
public: &str,
utility: &str,
size: &str,
sys: u64,
data: &str| RecordBox {
name: name.into(),
laid_as: name.into(),
hostname: host.into(),
title: title.into(),
public: public.into(),
utility: utility.into(),
size: size.into(),
vcpu: 0,
ram_mb: 0,
system_disk_gb: sys,
data: data.into(),
};
let want = Record {
variant: gunnar().sold_variant,
versions: Vec::new(),
boxes: vec![
b(
"front",
"f",
"F",
"198.51.100.1",
"10.6.0.1",
"STARTER-4xCPU-8GB",
40,
"",
),
b(
"main",
"m",
"M",
"198.51.100.2",
"10.6.0.2",
"STARTER-4xCPU-16GB",
50,
"10 GB maxiops",
),
b(
"twin",
"t",
"T",
"198.51.100.3",
"10.6.0.3",
"STARTER-1xCPU-2GB",
20,
"",
),
],
};
assert_eq!(
Record::from_feed(&f),
Some(want.clone()),
"gunnar reads the record back"
);
let record: BTreeMap<String, String> = f
.iter()
.filter(|(k, _)| k.starts_with("estate."))
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
assert_eq!(
record,
want.to_feed(),
"key for key what gunnar's own writer feeds"
);
}
/// ★ **Each kind's seed is read by the box as its `user_data`** (gunnar-server `pairing`).
#[test]
fn every_seed_the_shop_mints_is_read_by_the_box() {
let files =
monetize_shop_pairing::mint_material_for(&gunnar(), "acme.test2.gunnar.rs").unwrap();
for name in ["seed.front.toml", "seed.gunnar.toml"] {
let seed = &files
.iter()
.find(|(k, _)| k == name)
.unwrap_or_else(|| panic!("{name}"))
.1;
assert!(
nordisk_pairing::seed::metadata::of_user_data(seed).is_ok(),
"{name}: the box reads it as its user_data"
);
}
}
/// ★ **HOL-REPO-NAME: a shop-laid Gunnar front certifies `git.<site>` and sends it to the site**
/// (measured 2026-10-31: test2.gunnar.rs's certificate named test2 and www only, while
/// git.test2.gunnar.rs resolves to the front; the kit's gunnar.rs carries it, h-front-sans).
/// Gunnar's OWN facts (its guest's `redirect_names`) through the shop's front feed: the forge's
/// name is one of the front's served names (on its certificate) and a callback-only one (every
/// path there goes to the site), as edda's web reads the feed. No repository is routed.
#[test]
fn a_shop_laid_gunnar_front_certifies_git_of_its_site_as_a_redirect_only_name() {
let dir = tempfile::tempdir().unwrap();
for (n, v) in [("console-admin-token", "aa"), ("estate-client.key", "cc")] {
std::fs::write(dir.path().join(n), v).unwrap();
}
let mut o = order();
o.names = [
"acme.test2.gunnar.rs",
"www.acme.test2.gunnar.rs",
"git.acme.test2.gunnar.rs",
]
.map(String::from)
.to_vec();
let f =
monetize_shop_pairing::front_feed_for(&gunnar(), &BTreeMap::new(), &o, dir.path()).unwrap();
let web = gunnar_front::web::WebConfig::from("acme.test2.gunnar.rs", &f);
assert_eq!(
web.hosts,
[
"acme.test2.gunnar.rs",
"www.acme.test2.gunnar.rs",
"git.acme.test2.gunnar.rs"
],
"every served name is on the certificate"
);
assert_eq!(
web.callback_only,
["git.acme.test2.gunnar.rs"],
"the forge's name answers no web face"
);
assert!(
!f.contains_key("repo_hosts"),
"Gunnar routes no repository through :443"
);
}