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

nordisk/monetize

docs/INTEGRATION-v2.md

Integrating an application with monetize v2.0

Write a small operator and a resource. Monetize is the control plane that runs it. If you know Kubernetes, the parts map like this:

Kubernetes monetize v2 Who writes it
custom resource the shape: roles (e.g. front, be, fe), sizes, disks, networks, as data (section 3.3) you
operator the product operator (guest): your signed .wasm with 6 ops (section 3.1) you
client-go the SDK monetize-product-guest premade, open; you link it
control plane / reconcile loop the ONE box, monetize-admin premade
status the order state and your boxes' reports monetize and your boxes
cloud provider the UpCloud plugin (upcloud.wasm) premade
external-dns the Cloudflare plugin (dns_cloudflare.wasm) premade

The SDK is a library compiled into your operator: the types and the signatures of the 6 ops. The product operator is your signed .wasm; the ONE box pulls it and calls it through the 6 ops. Unlike a Kubernetes operator it runs no loop of its own and calls nothing: monetize runs the loop and asks it the product questions (pull only, no secrets).

Premade with monetize: Stripe, UpCloud and Cloudflare DNS.


This guide is for the author of an application that the ONE box is to sell. It describes the v2.0 target (MONETIZE-V2.0-PLAN.md, A24, A25), not today's 28-op contract. The older ASD-STE100 manual (integration-guide.md) describes today's code and stays until P3 lands; where the two differ, this guide wins for new work.

Every statement that depends on a phase that has not landed carries its tag: [P1], [P2], [P3], [P4]. A statement with no tag holds on main today. Where a v2.0 name differs from today's, today's name follows in brackets.

The pictures of this guide are operator-v2.svg (the whole idea, above), integration-v2.svg (the parts and the plugins, section 2), datamodel-v2.svg (the data model, section 7) and buyflows-v2.svg (the buy flows, section 8).

Contents:

  1. The parts and who owns them
  2. The plugins, and who writes each one
  3. Write: your product operator, your pages, your estate front
  4. How it ships
  5. How you test it
  6. A worked example: the Lake
  7. The data model: what is stored where
  8. The buy flows, step by step
  9. Where it lives: traits and crates

1. The parts and who owns them

Three parties. Each owns what it writes.

Part Owner What it is
monetize-admin (the ONE box) monetize (open, AGPL) PID 1 of one machine. Inside it runs the open monetize-server: the ledger, the estate state machine, custody, the pull door every box reports to, the guardian runner, nonpay, mail, DNS, the shop's installer, the operator console. It knows no product name (xtask/tests/product_names.rs holds it).
The signed vendor guests monetize-impl (private) stripe.wasm, upcloud.wasm, dns_cloudflare.wasm live today; scaleway.wasm, klarna.wasm, swish.wasm are next and are built, signed and tested in CI [P4]. The Build Thing of monetize-impl signs every guest the ONE box runs.
The shared crates monetize (open) monetize-product-guest (the SDK of your product operator), monetize-site-buy (the buy flow, the relay, the browser scripts), monetize-estate-side (the box side: the pull pump over Fleet.Report / Orders / Ask, and the front's signed shop door, over Fleet.Ask since P3 (the old SoldEstate door served one release more); landed in P4a, the products move onto it [P4]), nordisk-pairing (pairing and the live door; Skidbladnir). monetize-embed is the product's entitlement cache, not the pump. The shared update step moves into nordisk-pairing [P4]; today it is three copies: gunnar-front/src/update.rs, holger-estate-front/src/update.rs and monetize-server's estate_order/update.rs.
Your product operator (guest) you A signed .wasm with 6 pure ops. It says what an estate of your product IS and plans ONE order. It opens no door and holds no key.
Your box side you Your box binaries implement Estate [P3/P4] over the shared pump, and nordisk-pairing's Product and FrontProduct.
Your site you Your pages implement BuySite and mount the shared relay. The buyer stays on your site; the only page outside it is the vendor's checkout.
Your declaration you + the operator One [products.<id>] block in the ONE box's feed (solo.toml in private-monetize-ops): the module pin, prices, bundles, sign-up plans, how your kit makes media, mail words.

Two rules shape everything below.


2. The plugins, and who writes each one

The ONE box runs four kinds of plugin, one slot per trait. A plugin is a signed .wasm guest that the box pulls from the release bucket and pins by sha256, or an open impl linked into the open binary. You write exactly one of them: your product operator (guest). Everything else is premade.

The picture integration-v2.svg draws the same list. Its colours mean the same in every picture of this guide: green = you write it, cyan = premade in open monetize, amber = premade in monetize-impl (private, signed), grey = outside (the buyer, the vendors, the operator). A dashed outline is a stub of the lowest priority (owner rule).

Plugin Type (slot) Who provides it Where it lives Signed? Priority
gunnar_monetize_guest.wasm Product the integrator (Gunnar) nordisk/gunnar crates/gunnar-monetize-guest yes, by the Build Thing live
holger_monetize_guest.wasm Product the integrator (Holger) nordisk/holger crates/holger-monetize-guest yes, by the Build Thing live
the Lake's product operator (monetize-guest-lake) Product the integrator; kept in open monetize as the example of what a new product writes: a thin operator and its shape as data nordisk/monetize guests/lake yes, by the Build Thing the reference [P1]
stripe.wasm Payment premade, monetize-impl monetize-impl guests/stripe yes high: live on the ONE box, the one that matters
klarna.wasm Payment premade, monetize-impl monetize-impl guests/klarna yes stub, lowest
swish.wasm Payment premade, monetize-impl monetize-impl guests/swish yes stub, lowest
invoice Payment premade, open nordisk/monetize crates/monetize-pay/src/invoice no: linked into the open binary open default
sandbox (payment) Payment premade, open nordisk/monetize crates/monetize-pay/src/sandbox no: linked the money-free default [P1]
upcloud.wasm Cloud premade, monetize-impl monetize-impl guests/upcloud yes high: live on the ONE box, the one that matters
scaleway.wasm Cloud premade, monetize-impl monetize-impl guests/scaleway yes stub, lowest
server Cloud premade, open nordisk/monetize crates/monetize-cloud/src/server no: linked open default: the box's own iron, buys nothing
sandbox (cloud) Cloud premade, open nordisk/monetize crates/monetize-cloud/src/sandbox no: linked the money-free default [P1]: fake iron at real prices
dns_cloudflare.wasm DnsWriter premade, monetize-impl monetize-impl guests/dns-cloudflare yes high: live on the ONE box
no DNS guest fed (ManualDns) DnsWriter premade, open nordisk/monetize crates/monetize-server/src/estate_order.rs no: linked open default: the alert estate-orders.dns-by-hand asks the operator to set the records

The money-free default (owner 2026-10-24) is the open build's default features: the sandbox payment and cloud and the invoice vendor. A fresh open build sells free plans and lays on play iron until a payment guest and a cloud guest are fed.

Test doubles stand in for a vendor's API, not for a plugin. They run only in tests:

Test double Stands in for Who provides it Where it lives
mock-upcloud, mock-scaleway the UpCloud and Scaleway APIs premade, open nordisk/monetize crates/mock-upcloud, crates/mock-scaleway
fake_stripe_serve, fake_klarna_serve, fake_swish_serve the Stripe, Klarna and Swish APIs premade, monetize-impl (PRIVATE today) monetize-impl crates/monetize-pay-impl/examples/
toy, toy-vendor, toy-cloud, toy-dns, toy-signup one ABI per slot, end to end premade, open nordisk/monetize guests/toy*

An outside author cannot run the fake Stripe or monetize's cargo xtask trials (the 12 buy trials, which build from the private siblings ../monetize-impl, ../gunnar and ../holger). Use the money-free default.

The crates you link are not plugins: monetize-product-guest (the SDK of your product operator), monetize-site-buy (the buy flow, the relay, the browser scripts), nordisk-pairing (pairing and the live door) and monetize-estate-side (the box pump). Section 9 lists them with their paths.

Also reused as they are:

What a vendor guest reads is NAMES only: MONETIZE_STRIPE_<INSTANCE>_SECRET_KEY, UPCLOUD_TOKEN, and so on. The keys stay on the ONE box in the operator's custody. Your product never sees them.


3. Write: your product operator (guest), your pages, your estate front

3.1 The product operator: one crate, six ops

Make a crate in your own repository, crates/<id>-monetize-guest, with crate-type = ["cdylib", "rlib"] and the dependency monetize-product-guest. Gunnar's is gunnar/crates/gunnar-monetize-guest; the Lake's is monetize/guests/lake [P1].

The trait, as A24 §2.2 writes it [P3]. The names in brackets are today's ops (monetize_product_guest::Guest), which are what you implement until P2/P3 land.

pub trait Product {
    /// Op 1. Who the product is: id, ABI, meters. No grants.
    fn manifest(&self) -> Manifest;                                   // [today: manifest(), the meters in it (Manifest::with_meters), P2 T13]

    /// Op 90. What an estate of this product IS. Pure data, read once per guest digest.
    fn facts(&self) -> Facts;                                         // [today: lifecycle_facts(host) -> LifecycleFacts, with tickets, test_actions, guardian (P2 T13)]

    /// Op 91. The plan of ONE order: machines (role, plan, disks, medium), stores, names.
    fn plan(&self, order: &Order, ctx: &PlanContext) -> Result<EstatePlan, Refusal>;   // [today: lifecycle_lay_plan(host, &LayPlanReq) -> serde_json::Value]

    /// Ops 100-102. The guardian: pure, over the estate as the boxes reported it.
    fn diagnose(&self, view: &EstateView) -> Vec<Finding>;                              // [today: guardian_diagnose]
    fn remedy(&self, remedy: Remedy, view: &EstateView) -> Result<Vec<Step>, String>;   // [today: guardian_plan]
    fn observe(&self, role: &str, what: &str, view: &EstateView) -> StepState;          // [today: guardian_observe]
}
export_product!(YourGuest);   // [today: export_product!(YourGuest, grants = [])]

What happened to the other 22 ops of the old table (A25 §2; P2 T13, monetize_product_guest::ops::RETIRED):

Today Fate
list_tenants, read_usage, read_activity, read_usage_page, tenant_owners die: the ONE box reads them from your boxes' reports
push_entitlement, purge_tenant, add_users, users_fit die: they are orders your boxes pull
can_absorb, servable, metered_door, twins die: answered from the fresh report
grow_flush, grow_start, grow_status, grow_resume die: a grow is a new order and a plan diff
may_act_for moved to the host: the ticket is checked on the ONE box (site_ticket) under the block's appliance_key or the site's key [the install-time custody of ticket_pubkey: P3]
id, meters, tenant_renewal, test_actions, lanes merged into manifest() (op 1) and facts() (op 90: tickets, test_actions, guardian)

A guest implements only the six. Grants: none, for every product guest (Gunnar, Holger and the Lake ask for [] since P2 T13).

3.2 manifest()

Manifest::new("<id>", meters![...])          // [today: Manifest::new("<id>", &[]) and meters() -> Vec<WireMeter>]

One meter per thing you measure or cap. Gunnar: pack_bytes (enforced, disk). Holger: disk_bytes (enforced, disk). The Lake: ingested_bytes (enforced, disk: Backing::DiskBytes) and query_seconds (cap only).

3.3 facts(): the shape as data

Everything the core and the shop's installer know about your product is in this one answer. Nothing about your product is written in the core [P3].

pub struct Facts {
    pub kind: String,                  // "gunnar" | "holger" | "lake"
    pub words: Words,                  // name, title, account_home, the mail words       [today: words + default_shape.mail_words]
    pub roles: Vec<RoleFacts>,         // one per role the plan may lay                   [today: medium_of, other_medium, keep_medium, data_roles, live_slot, backup_roles]
    pub front_role: String,            // serves the public names, drives the update and is upgraded LAST [today: front_role]
    pub primary_role: String,          // holds the tenants, the ticket key, the backups  [today: the first data role]
    pub twin: Option<TwinFacts>,       // None = no election, no mirror                   [today: a data role with mirrors_primary = true]
    pub update_order: Vec<String>,     //                                                 [today: update_order]
    pub names: Vec<String>,            // served name prefixes: "", "www.", "git."        [today: default_shape.names]
    pub proof: SaleProof,              // the routes the sale check reads and what proves the sale [today: per-kind code in monetize-shop-pairing sale_check.rs; the type: P3a]
    pub form: OrderForm,               // the fields a buyer sets, their ranges, the named offers  [today: default_shape.ladder and EstateSizes]
    pub grow: GrowRule,                // what may change on a running estate             [today: "data never shrinks" in the core]
    pub tickets: bool,                 // your main mints actor tickets                   [today: LifecycleFacts.tickets]
    pub test_actions: Vec<TestAction>, // the shop's test buttons                         [today: LifecycleFacts.test_actions]
    pub feed: Vec<FeedKey>,            // the product-specific feed keys your boxes read  [today: monetize-shop-pairing CONTRACTS]
}
pub struct RoleFacts { role, medium, keep_medium: Option<String>, live_slot: bool, backed_up: bool, data: Option<DataRole>, many: bool, optional: bool }

Landed (P3a, additive): monetize_product_guest::facts::Facts, today's LifecycleFacts field for field plus the shop contract's fields as data (billing, offsite, boxes_hold_console_token, welcome, sold_variant, site_variant, box_kind, free_feed; proof_name is domain_proof_name, and the old name is still read) and proof: Option<SaleProof>. Every field is defaulted, so today's lifecycle.facts answer reads as a Facts.

Landed (P3, T15): Facts IS the answer to lifecycle.facts (op 90); wire::LifecycleFacts is its old name. It also carries console_token_feed, passkey_rp_feed, welcome_readme (your welcome README, {host}, {account}, {repo}), reserved_accounts (your boxes' own principals, never a buyer's account) and form (your OrderForm, below). The shop's installer reads its contract for your estate off these facts (monetize_shop_pairing::ProductContract::of) and holds none of its own: a fact your guest does not declare is refused by name ([contract-…]), and no estate of yours is paired or captured until it does. roles, twin and grow are still to come.

Each product declares its own sizes, networks and disks. The core has no opinion:

Today the shape rides in LifecycleFacts.default_shape as JSON (front, main, twin machine shapes with plan, disks, interfaces). Keep that until P3; A24 §4.1-4.3 shows the same facts in the target form.

3.4 plan(order, ctx): the "instantiate" function

Pure. The buyer's order is validated against facts().form before it reaches you. ctx is what the operator fed: zone, labels, the template tiers (today's LayPlanReq: config, company, sizes, twin_plan, template_tiers).

You answer an EstatePlan: reference = estate/<company>, zone, one machine per role instance (role, hostname, title, plan, boot order, labels with role = <role>, disks with the system disk first), and stores [P3]. The core prices it at the cloud (Cloud.price), lays it (Cloud.lay), and on a change diffs the laid plan against your new one (Cloud.change) [P3]. A refusal refuses the order with your words; there is no native fallback [P3].

Today's rules that still hold: each title unique; each role laid once (the Lake names its n-th member <role>-<n> [P1]); a plan that names front, main or twin must have exactly those three [until P3].

Gunnar's and Holger's plan is one call to the shared plan_from_shape(ctx, &shape) [P4]; today each has its own lay_plan (lifecycle.rs, estate.rs).

3.5 The guardian: diagnose, remedy, observe

Unchanged from today. Pure, over EstateView (what the boxes reported). diagnose answers findings with a code, a role, words, evidence and the remedies a person or auto-fix may press. remedy answers steps (cloud steps, orders for a box, waits). observe answers whether a wait is met. The shared step helpers (guardian::steps::plan, ::observe) replace the four copies [P4].

3.6 Your declaration: the toml block

The operator's feed file (solo.toml in private-monetize-ops) carries one block per product. You write it with the operator. After P3 the product's own facts are gone from it: no grants, no shape, no contract.

[products.<id>]
module = "<id>_monetize_guest.wasm"     # your signed product operator, pinned by sha256 (cargo xtask pins)
sha256 = "..."
purge_after_days = 10                   # an unpaid account is deleted 10 days after its end
prices = """
[SE]
disk_gib_month = 1000
"""
[products.<id>.media]                   # how YOUR kit makes the media the ONE box lays from
kit = "private-<id>-ops"
argv = ["cargo", "xtask", "estate-media", "--where", "{where}", "--media-to", "{to}", "--apply"]
[products.<id>.bundles."10gb"]
cap_delta = { pack_bytes = 10737418240 }
[products.<id>.signup]
cap_meter = "pack_bytes"
[products.<id>.signup.plans."<id>/account"]   # the free account an order lands on
amount_minor = 0
cap_bytes = 0
[products.<id>.mail]

The ONE box also needs [vendors.stripe.instances.<id>] (the operator's; it names the key NAMES) and [boxes.prod.estates.<site>] for each site that sells your product (product, vendor, owner). Your estate's own declaration on its media (Holger's RON for its server) is your kit's business; monetize does not read it.

Migration (RA2-16, 2026-10-26): two retired keys are refused by name. [products.<id>.plans.<plan>] and return_hosts fed only the ONE box's old /kop purchase page, which OD6 deleted. A config or a feed (plugin.<id>.toml) that still sets either is now refused at load, naming the key; before, both were parsed and ignored. To migrate, delete the [products.<id>.plans.*] tables and the return_hosts line from the kit that writes the feed (for private-monetize-ops, solo.toml and the writer in xtask/src/solo.rs). Nothing else changes: a product sells through its own sign-up ([products.<id>.signup.plans], fed as [signups.<id>.plans]) and its [products.<id>.bundles]. Deploy the kit's change BEFORE this monetize: an older monetize reads a block without those keys as before.

3.7 Your buy pages: BuySite and the assets

Implement monetize_site_buy::BuySite once, in your site's server (Gunnar: gunnar-ui-server/src/auth/buy_site.rs; Holger: holger-estate-front/src/buy.rs). Twelve methods, 6 required + 6 defaults (P4b added operator; A32-9 deleted the grow_needs_checked_tickets knob: every site's grow doors wait for checked tickets; monetize-site-buy tests/h15_stepped_up_refuses.rs holds the count). The default of stepped_up REFUSES (Err(STEP_UP_WORDS)): a site that does not prove a fresh key touch cannot end an estate. Implement it.

pub trait BuySite: Send + Sync + 'static {
    fn origin(&self) -> String;                              // "https://www.holger.rs"
    fn product_word(&self) -> String;                        // "Holger"
    fn who(&self, headers: &HeaderMap) -> Future<Result<Who, Refusal>>;            // Visitor | Person | Machine, from YOUR session
    fn ticket(&self, headers, buyer: &Buyer, purpose: &str, caps: &BTreeMap<String, u64>) -> Future<Result<Vec<u8>, Refusal>>;  // your main signs it
    fn held_caps(&self, headers, buyer: &Buyer) -> Future<Result<BTreeMap<String, u64>, Refusal>>;
    fn signup_account(&self, user: &str) -> Future<Result<Account, Refusal>>;      // NoPasskey | HasPasskey
    // defaults: pages(), allowed(), trusted_proxies(), fixed_buy_url(), operator(), and
    fn stepped_up(&self, headers) -> Future<Result<(), String>> { Err(STEP_UP_WORDS) }  // a fresh key touch; ending an estate needs it, so implement it
}

Then: a ShopLink to the ONE box with your site's bearer, a Relay over your site and the link, and monetize_site_buy::router(relay) mounted on your web server. Do not copy a buy door; the relay has all 26.

The browser side is one script, rendered once: monetize_site_buy::assets::buy_js(&Settings) and welcome_js(&Settings). Your Settings names the product word, the storage prefix, the free plan id, the manage URL, the bearer key, the status and offer URLs and the contact. Your UI server serves the rendered script; your markup and CSS are your own. No product server serves a byte of UI.

The relay carries your product's own order: your page sends the buyer's form as order (a JSON object) through untouched, and the relay passes it on as EstateSizes.order on EstateOrder.Start and ChangePlan. The ONE box checks it against your facts' form (OrderForm::check: every key a field of the form, each value of its kind and in its range, the rest filled in from the defaults) before anything is priced or held, keeps the filled order with the run, and hands it to your plan() as LayPlanReq.sizes.order. A product with no form is sized by the three plan fields (front_plan, main_plan, main_data_gib), and an order for it is refused by name.

The buyer's path: your sign-up page → your relay → the ONE box mints the vendor's page → the buyer pays at the vendor (the only page off your site) → back to your paid page → your welcome page polls until the estate is ready. The buyer never sees monetize. Stripe once, e-mail once, "Pay ".

3.8 Your estate front and boxes

Every box of your estate runs:

pub trait Estate: Send + Sync {
    fn report(&self) -> Report;                                   // build, health, usage per tenant, twin review
    fn execute(&self, order: &Order) -> Result<Ack, Refusal>;     // one signed order the box pulled; an unknown kind is refused BY NAME
    fn box_key(&self) -> &SigningKey;                             // what the box signs its Fleet calls with
}

The core's kinds are ONE list, monetize_product::pull (A28 M25): 7 orders, entitlement, add_principal, add_users, grow_room, purge_tenant, grow_go_ahead, sign_in_again (order::CORE), and 1 ask, grow_set (ask::CORE). A product whose guardian runs a twin adds its own guardian kinds (Gunnar's repair verbs: monetize_embed::fleet::kind::GUARDIAN). Name the kinds your boxes run in your facts (order_kinds); Facts::runs(kind) is the refusal BY NAME for any other kind, from those facts alone.

The front also runs nordisk-pairing's FrontProduct (the relay's tunnel key, its offer, the live door) and the shared update step [P4] (Fleet.Ask kinds update_take / update_report since P3, the old SoldEstate.UpdateTake / UpdateReport served one release more; the step itself is still in gunnar-front/src/update.rs and holger-estate-front/src/update.rs). The update is an order the front pulls; the front stages the live app through each box's live door, in facts().update_order, or reports falls_to_keep.

The sale check: before the capture the ONE box reads, over public HTTPS, the routes your facts().proof names on the front [P3]. A SaleProof (landed in P3a: monetize_product_guest::facts, run by monetize_shop_pairing::sale_proof) is a list of typed checks, each one request and what its answer must hold: the method and body (POST with {invite} and {account}), a JSON status field (passkey_enabled true, bootstrap_spent false), a non-empty challenge, and the bearer list rule (exactly one <account>/welcome). Each product names its own routes; there is no shared /auth/first-admin (Gunnar: /auth/status and /auth/passkey/register/start; Holger: /-/auth/passkey/config; the Lake: /first-login). The shop runs your guest's own facts().proof and has no judge of its own (P3: the judges went once their SaleProof was proven to give the same answers). Serve the login page and the first-admin door on the front from the first boot.


4. How it ships

Pull only, at every hop.

  1. The Build Thing of monetize-impl builds your product operator (guest) from your repository ([[build.variant]] name = "<id>-guest", manifest_dir = "../<id>", target = "wasm32-unknown-unknown", sign.purpose = "plugin") and signs it under the plugin root the ONE box trusts. Every vendor guest is a row of the same file.
  2. cargo xtask pins (private-monetize-ops) checks the sha256 of every built guest against solo.toml. A stale pin is refused before anything is laid.
  3. cargo xtask release --publish --apply writes the signed release-manifest.toml and puts the guests into the release bucket: release/plugins/<blake3>/<name> and its .p7s.
  4. cargo xtask feed --where upcloud --box prod --apply puts the feed built from solo.toml on the box's door. It prints key NAMES only.
  5. The ONE box pulls: its own Plugins.Update (monetize box plugins --apply) takes the plugins the manifest names and verifies each against the root; a module that is not its pin is refused by name.
  6. Your media come the same way: your kit's estate-media step uploads them to the release bucket and writes media-<kind>.toml; the ONE box lays from them.

A release of the ONE box itself is one verb (self-release) and goes out only from a green, gated main. Your estates are updated by the ONE box (EstateOrder.UpdateEstate), which your front pulls.


5. How you test it

Three rings, innermost first.

Ring Verb What it runs Time
unit cargo test in your product operator's (guest's) crate your facts() and plan() as pure functions; the conformance table of monetize-product-guest over your signed .wasm: each op answers a known case, an unknown op answers the frozen phrase, a trap is refused by name [P2] seconds
fast cargo xtask fast --product <id> (holger, gunnar; the names come from your product operator's lifecycle.facts) the REAL monetize-admin server in a container with your signed product operator, mock-upcloud with its container engine (one container per laid machine, from your box image), the fake Stripe, the printed DNS writer; today the operator orders the site, pays at the fake, and the box lays, installs, pairs and captures (ready), then the run tears down; the buyer over your site's HTTP, grow, update and the estate's terminate [P3]; a receipt row per check minutes
gate cargo xtask gate <id> (private-monetize-ops) the KVM gate: real media under QEMU, the real ONE box medium, WireGuard on the mock's utility fabric; before a release about 45 minutes

What the fast test checks, among others [P1]: the quote's words match the plan your product operator answered; the vendor page is minted once; the lay has exactly the plan's machines; every box offered fleet_pubkey; the election happened where facts.twin is Some and did not where it is None; your product operator made ZERO door calls over the run; a grow changed exactly the diff at the mock; a shrink was refused by name; the terminate left zero resources with the reference, backups included.

The Lake is the reference product: it runs the same rings in monetize's own CI (cargo xtask ci). When your product behaves like the Lake, it behaves like Gunnar and Holger.

Money: your tests run on the money-free default [P1] or the fake Stripe. A change to the money flow is proven by one real purchase on test2 before gunnar.rs. Test addresses are *@mock.invalid.


6. A worked example: the Lake

The Lake is the parametric case: the buyer sets numbers, the product operator (guest) turns them into a plan, the core prices the plan at the cloud. No Lake code is in the core. On main it is guests/lake and crates/lake-product [P1]; what it still waits for from P3 is listed in its P3_WAITS.

The order:

struct LakeOrder { store_gib: u64, ingestors: u32, ingestor_plan: String, be: u32, fe: u32, graph: bool }
// store_gib 100..=100_000 in steps of 100; ingestors 1..=64; be 1..=32; fe 1, 3 or 5; graph yes/no

The facts (A24 §4.3, target form) [P3]:

// one medium for every role: "lake" (keep medium "lake-keep"); one binary runs each role
roles: [ role("front", medium "lake", live_slot),
         role("ingestor", medium "lake", many),
         role("be", medium "lake", many, data: data1),
         role("fe", medium "lake", many),
         role("graph", medium "lake", optional),
         store("input", class "object") ],
front_role: "front", primary_role: "fe", twin: None,      // today the core's primary is the first data role, `be` (P3_WAITS)
update_order: ["ingestor", "fe", "be", "front"],        // the front LAST (guests/lake role::UPDATE_ORDER)
names: ["", "www.", "s3."],
proof: SaleProof { checks: [check("the login page", GET "/login"), check("the first-login door", GET "/first-login")] },   // its own routes (lake_box.rs)
form: OrderForm { fields: [gib("store_gib", 100..=100_000, step 100), count("ingestors", 1..=64), plan("ingestor_plan"),
                           count("be", 1..=32), count("fe", 1..=5, odd), bool_("graph")],
                  offers: [offer("Starter", {store_gib: 500, ingestors: 2, be: 3, fe: 1, graph: false}), offer("Team", ...)] },
grow: GrowRule { never_shrinks: ["store_gib"], may_add: ["ingestors", "be", "fe"], may_remove: ["ingestors"] },
tickets: true, test_actions: [private("lake-mini")],     // "Test: the smallest Lake"

The plan (plan_of on main today [P1]; plan() at P3):

let o: LakeOrder = order.parse()?;
let mut p = EstatePlan::new(ctx, "lake");
p.machine("front", "STARTER-1xCPU-2GB", disks![system 20]);
for i in 1..=o.ingestors { p.machine_n("ingestor", i, &o.ingestor_plan, disks![system 20]); }
for i in 1..=o.be        { p.machine_n("be", i, "STARTER-4xCPU-16GB", disks![system 20, data1 (o.store_gib / o.be).max(50) tier "maxiops"]); }
for i in 1..=o.fe        { p.machine_n("fe", i, "STARTER-2xCPU-4GB", disks![system 20]); }
if o.graph               { p.machine("graph", "STARTER-4xCPU-16GB", disks![system 20, data1 100]); }
p.store("input", o.store_gib, "object");                      // [P3: today the input store is the BE data disks]
Ok(p)

Machines laid: 1 + n + x + y (+1), roles front, ingestor, ingestor-2, be, be-2, be-3, fe, graph.

What the core does with it, with no Lake code of its own:

The Lake's box side, as a target [P4]: one binary for every role (--role front|ingestor|be|fe|graph --index n) with impl Estate, nordisk-pairing's Product, and FrontProduct on the front. Today it is lake-product/src/lake_box.rs, one in-process LakeBox per role. It runs the kinds its facts name (order_kinds: entitlement, add_principal, purge_tenant, grow_room) and refuses any other kind BY NAME in its ack, from those facts alone (A28 M23; a member joining is a target, no join_member kind exists). Its site is LakeSite: BuySite with Settings { product: "Lake", .. }. Today the Lake implements neither Product nor FrontProduct: its bench pairs its boxes with an in-process double (lake-product/src/installer.rs). nordisk-pairing can pair it since P3a (seed::Kind::Box(role): each box names its own kind, which is not elected). The Lake names its kind in its facts (box_kind = "lake", a box kind, never elected; T15, A28 H13).

The Lake's fast test is monetize's own cargo xtask fast --product lake [P3] (today the verb refuses lake and names cargo xtask bench lake), and its receipt adds the rows that only a parametric product can prove: a grow be: 3 → 4, a shrink refusal, an order with graph: true, a terminate that lists zero stores.

7. The data model: what is stored where

  1. The buy flows, step by step
  2. Where it lives: traits and crates

The picture is datamodel-v2.svg. Three stores hold the data of a sale, and each has one writer. Only four links cross between them: the tenant name, the paid sign-up that makes an account, the order that ties an account to a sold estate, and the first-login invite.

On the selling site (gunnar.rs, holger.rs). The product owns its people. Gunnar keeps them in one redb file, control.redb: the AuthDoc (refs/gunnar/auth) holds principals, passkeys (WebCredential), SSH keys, tokens, grants and invites; the catalog doc holds namespaces and repos; refs/gunnar/entitlements holds the signed fact per tenant. An account is a principal and the namespace of the same name (owns_account(pid, ns) is pid == ns); both are made in one call (ControlPlane::add_principal). A grant gives a principal a level (Read, Write, Admin) on the server, a namespace or a repo. An invite stores the hash of its secret, never the secret. The site keeps nothing of monetize but its ShopLink (the ONE box's URL, the site's bearer, product, vendor). The tenant name it uses at monetize is the account's namespace (Gunnar) or the account's repository (Holger) (monetize_site_buy::Buyer.tenant).

In monetize (the ONE box). monetize-admin's server writes every row, in redb files and one run file per sold estate. An ACCOUNT is the pair (product, tenant): its signed entitlement fact (ledger.redb entitlements), its usage (usage.redb), and its rows: one subscription, any number of purchases, any number of private instances (monetize.v1.Accounts). A purchase is append-only ledger rows under the reference <product>/<tenant>/<period>. A paid or free sign-up is a row in signups.redb keyed by the vendor's (vendor, session). A sold estate is estate-orders/<company>/run.json (EstateRun): who owns it (account), which site sold it (estate), its quote, its phase and what was laid. Its secrets are sealed apart, in custody/<company>.sealed. The fleet store (fleet.redb) pins each box's key and queues the orders the boxes pull. The operator console keeps its own passkeys and sessions in state.redb. Sites, vendor instances, plans and the release are the feed (solo.toml, the signed release manifest); they are configuration, not customer data.

On the sold instance (acme.gunnar.rs). The instance has its own store, with the same model as the selling site. Its first admin is the principal named EstateRun.account. The installer feeds the hash of the first-login invite (operator_invite_sha256), and Gunnar stores it as the invite operator-bootstrap (single use, short lived). The buyer redeems it from the "ready" mail (/login?invite=…&user=<account>) and registers the first passkey. Everyone else on the instance is invited by that admin. Neither the selling site nor monetize holds a row of the instance's people. Its boxes report and pull their orders. The ONE box never calls into them.

Entity Owner (writer) Key fields Lifetime
Principal the selling site (Gunnar AuthDoc) id, name, kind, created kept on the site; the ONE box may order its tenant purged (purge_tenant) after a lapse
Passkey (WebCredential) the selling site id, principal_id, public_key_hex, sign_count with its principal
Account = namespace the selling site name (= principal id), repos, entitlement held with its principal
Grant the selling site principal, scope (Server, Namespace, Repo), level with its principal
Invite the selling site or the sold instance id, principal_id, secret_hash, created_by, expires single use; spent once a passkey exists
Site ([estates.<label>], SiteRecord) the operator (feed or console) label, product, vendor, owner, domain, first_admin, terms as long as it sells; gunnar.rs is never terminable unless it says terminable = true
Account (product, tenant) monetize EntitlementFact (plan, state, caps, paid_until, signature), usage kept; an unpaid account is purged purge_after_days (10) after its end
Sign-up (SignupRecord) monetize (vendor, session), username, email, plan, estate, card fingerprint kept (it makes the webhook idempotent)
Purchase (ledger rows) monetize reference, steps (Reserved, Ensured, Captured, Settled, Released) append-only, never deleted
Plan / offer the operator (feed) signup plans (amount, cap_bytes, period_days), bundles, prices per release of the feed; [P3] from facts().form of the product operator (guest)
Sold estate (EstateRun) monetize company, reference, estate (site), account, phase, quote, paid_until, laid the run file stays after terminate, phase terminated, as the record
Custody seal monetize first-login invite, signing and pairing seeds, break-glass, estate client seed, pairing material forgotten at terminate or deletion
Fleet (fleet.redb) monetize box key, newest report, orders to ack, cases a box is forgotten at terminate
Vendor instance the operator (feed) stripe/<instance>, guest digest, key NAMES per feed; key values stay in custody
Cloud resources the cloud guest, for monetize machines and disks under reference = estate/<company>, journal, spend cap destroyed at terminate, NO backup [P1: the backups too, F-T-01]
Release and feed the operator (signed release) solo.toml, release-manifest.toml, plugins by blake3, media per release
First admin and users of the sold instance the sold instance as on the selling site gone with the instance at terminate

What changes in v2.0: the sign-up guests are deleted and the entitlement is only a pulled order, never a push [P2]; the feed loses grants, shapes and the shop-pairing contracts [P2]; the quote carries the product's own Order JSON instead of the three Gunnar sizes, the plan offers come from facts().form, SoldEstate folds into Fleet (the front's five calls are Fleet.Ask kinds upgrade, recharge, material, update_take, update_report, signed as role = front by the estate's client key; the ONE box serves the old door for one release more), and custody also keeps the main's ticket_pubkey [P3]; terminate destroys the cloud backups with the estate [P1].

A visitor who buys a private instance gets two things.

  1. An account on the selling site, made with the payment. The page sends EstateOrder.Start with new_account = true, the account name and the e-mail. When the vendor says PAID, the ONE box queues a signed add_principal order. The site's main pulls it and makes the principal and its namespace, and the sign-in link is mailed. The buyer registers a passkey there. A signed-in buyer sends an actor ticket instead, and no new account is made.
  2. The instance itself, <company>.gunnar.rs, with its own first admin. The instance is the EstateRun of that account. Its first admin has the same name as the site account, but it is a separate principal in a separate store, made by the first-login invite. The selling site's account sees the instance as one instance row (its phase, its price, renew, terminate). It holds no rights inside the instance.

8. The buy flows, step by step

Every buy method has an id (B1 … B10), and every step of it has one (B3.4). The picture buyflows-v2.svg shows each method as one row of hops, in the lane of the party that acts. The tables below give, for each step: who calls whom, the relay route and the gRPC call it maps to, the state the order is in after it, the exact words a buyer or the log can show there, and the log lines it writes. The words are copied from the code on main (2026-10-25), with file:line; {…} is a value the code fills in.

Use it two ways. For design: read a method top to bottom. For an error: search this section for a phrase of the error or the log line, and its row names the step, the file and the line.

Id Method Doors (relay) gRPC on the ONE box
B1 A free account (a card check, nothing charged) /auth/signup/* Signup.Plans, CheckName, Start, Status, SendSignInAgain
B2 A paid account (pay once, then the account is made) /auth/signup/* as B1, plus the vendor's callback and Signup.Rename
B3 A private instance as a visitor (new_account = true) /auth/private/start, /status EstateOrder.Ladder, Start, Status
B4 A private instance, signed in (actor ticket) as B3 as B3
B5 Grow the account (a bundle) /api/account/subscription/* Purchase.Bundles, Start, Status
B6 Grow an instance (one step of the ladder) /auth/private/change EstateOrder.ChangePlan
B6b An instance's sign-in link again /auth/private/sign-in-again EstateOrder.SendSignInAgain
B7 Renew (a: an instance, b: the account) /auth/private/renew, /api/account/subscription/renew EstateOrder.Renew, Purchase.Quote / Start{renew}
B8 Cancel (a: an unpaid account order, 409 once paid; b: an instance's renewal; c: an unpaid instance order) /api/account/subscription/cancel, /auth/private/cancel Purchase.Cancel, EstateOrder.Cancel, Settle
B9 Terminate (no backup) /auth/private/terminate EstateOrder.Terminate
B10 Test orders (the operator's) /api/test-purchase/start, the admin door /site/test-order TestPurchase.Start, EstateOrder.OrderSite{test_order}

8.1 The files the tables cite

Every file is named once here with its path in nordisk/monetize. The tables use the short name on the left.

short path in nordisk/monetize
relay.rs crates/monetize-site-buy/src/relay.rs
rules.rs crates/monetize-site-buy/src/rules.rs
site.rs crates/monetize-site-buy/src/site.rs
urls.rs crates/monetize-site-buy/src/urls.rs
link.rs crates/monetize-site-buy/src/link.rs
sb-purchase.rs crates/monetize-site-buy/src/purchase.rs
buy.js / welcome.js crates/monetize-site-buy/assets/{buy.js,welcome.js}
svc-signup.rs crates/monetize-server/src/services/signup.rs
signup.rs crates/monetize-server/src/signup.rs
svc-eo.rs crates/monetize-server/src/services/estate_order.rs
eo.rs crates/monetize-server/src/estate_order.rs
eo/account.rs, eo/term.rs, eo/change.rs, eo/terminate.rs, eo/sold.rs, eo/site.rs, eo/pages.rs crates/monetize-server/src/estate_order/…
svc-purchase.rs crates/monetize-server/src/services/purchase.rs
callback.rs crates/monetize-server/src/callback.rs
returns.rs crates/monetize-server/src/returns.rs
signup_pull.rs crates/monetize-server/src/signup_pull.rs
test_purchase.rs crates/monetize-server/src/test_purchase.rs
tx.rs crates/monetize/src/transaction.rs
admin-door.rs crates/monetize-admin/src/door.rs
pull.rs crates/monetize-product/src/pull.rs
embed-fleet.rs crates/monetize-embed/src/fleet.rs

Log targets.

8.2 The relay's routes (relay.rs router_of 1363–1406; the doc table at 8–34)

group (Doors) method + path handler (relay.rs line) gRPC on the ONE box
signup GET /auth/signup/ladder[?market=] signup_plans 146 Signup.Plans
signup GET /auth/signup/name?u= signup_name 185 Signup.CheckName
signup POST /auth/signup/start signup_start 229 Signup.Start (minting channel)
signup GET /auth/signup/status?session= signup_status 363 Signup.Status, then BuySite::signup_account
signup POST /auth/signup/rename signup_rename 393 Signup.Rename
signup POST /auth/signup/sign-in-again signup_sign_in_again 434 Signup.Status, then Signup.SendSignInAgain
private GET /auth/private/ladder[?company=] private_ladder 565 EstateOrder.Ladder (+ EstateOrder.Sells via private_door)
private POST /auth/private/start private_start 602 EstateOrder.Start (minting) (+ Sells)
private POST /auth/private/change private_change 670 EstateOrder.ChangePlan (minting) (+ Sells)
private GET /auth/private/status?company= private_status 704 EstateOrder.Status
private POST /auth/private/renew private_renew 784 EstateOrder.Renew (minting)
private POST /auth/private/cancel private_stop_renewing 815 EstateOrder.Cancel ("Stop renewing")
private POST /auth/private/terminate private_terminate 883 EstateOrder.Terminate
private POST /auth/private/restore-front private_restore_front 839 EstateOrder.RestoreFront
private POST /auth/private/sign-in-again private_sign_in_again 739 EstateOrder.SendSignInAgain
private GET /paid/estate/{company} paid_estate 921 none: 303 to {pages.private}&company=co (urls.rs:53)
private GET /paid/estate/{company}/{change} paid_estate_change 926 none: 303 with &change=1 only for change-<n> (urls.rs:62)
account GET /api/account/purchases buy_purchases 1227 EstateOrder.Purchases (+ Signup.Plans for the offer)
account GET /api/account/subscription/bundles buy_bundles 962 Products.List (always, A32-9), Purchase.Bundles
account GET /api/account/subscription account_offer 1112 Products.List, then Purchase.Quote{renew}
account GET /api/account/subscription/history account_history 1169 Products.List, then Purchase.History
account POST /api/account/subscription/cancel buy_cancel 1202 Purchase.Cancel
account GET /api/account/subscription/status?reference= buy_status 1050 Purchase.Status
orders POST /api/account/subscription/buy-bundle buy_bundle 989 Purchase.Bundles, then Purchase.Start{bundle} (minting)
orders POST /api/account/subscription/renew account_renew 1141 Products.List, then Purchase.Start{renew} (minting)
offer GET /auth/buy/offer buy_offer 1249 EstateOrder.Sells, Signup.Plans

Routes outside the relay:

Relay deadlines (link.rs:65–76):

8.3 How a refusal reaches the buyer

Private-instance, purchase and grow doors use from_status (relay.rs:524) / refused_call (relay.rs:942), which call rules::http_status_of (rules.rs:190–199):

gRPC code HTTP
InvalidArgument 400
AlreadyExists, FailedPrecondition 409
ResourceExhausted 503
NotFound 404
Unauthenticated, PermissionDenied 403 (PermissionDenied since BUYFLOW-4, also on the sign-up doors)
anything else, including Unavailable, Unimplemented, Internal 502

Sign-up doors have their own maps:

What the buyer reads. rules::words_for_a_visitor (rules.rs:173–187) decides the error text:

Fixed relay refusals:

Cancel of a paid order = 409. Purchase.Cancel returns FAILED_PRECONDITION "{ref} has been paid, so it cannot be cancelled: it finishes as paid, and ending it now is a refund (Abandon)" (svc-purchase.rs:2030–2033). refused_call maps that to 409 (test: crates/monetize-site-buy/tests/a16_cancel_door.rs:100–105).

monetize-admin door map (admin-door.rs:223–233):

8.4 B1 – Free account sign-up (the free row: card check, nothing charged)

# who → whom relay route → gRPC state after error words (file:line) log events (file:line)
B1.1 browser → site GET /auth/buy/offer → EstateOrder.Sells, Signup.Plans — — —
B1.2 browser → relay → box GET /auth/signup/ladder → Signup.Plans — 429 "too many requests from here; wait a moment" (relay.rs:152); 501 "An account is not sold here right now." (relay.rs:60,169); 503 "The shop is unavailable right now — try again in a few minutes." (relay.rs:58,179); box: "Signup.Plans names no product: one monetize sells several (gunnar, holger), and a call says which" (svc-signup.rs:900); "no signup sink is loaded for {product}: …" (svc-signup.rs:907) relay signup.plans_failed (relay.rs:168,172)
B1.3 buy.js → relay → box GET /auth/signup/name?u= → Signup.CheckName — 200 {free:false, reason:"a username is 2 to 32 characters of a-z, 0-9, '.', '_' or '-', starting with a letter or a digit"} (relay.rs:188; rules.rs:9); 429 "too many names checked from here; wait a moment" (relay.rs:192); box taken: "username {name:?} is taken; choose another" (signup_pull.rs:112, pulled product); sink down → Unavailable SHOP_DOWN "The shop is temporarily down for maintenance. Please try again in a little while." (signup.rs:138; svc-signup.rs:923) → 502; buy.js: "Choose another account name." (buy.js:612) relay signup.name_rate_limited (relay.rs:191), signup.name_failed (relay.rs:200); box monetize-server: signup name check unavailable: {why} plus functional_status ("monetize-server/signup","name check") (svc-signup.rs:95–96)
B1.4 buy.js → relay → box POST /auth/signup/start {username,email,plan:'@@FREE_ACCOUNT@@',next:'/'} (buy.js:622) → Signup.Start card check minted. Store holds CardCheckStarted under (vendor, cs_…) (svc-signup.rs:607) and a name hold of 35 min (signup.rs:87; svc-signup.rs:608). No SignupRecord yet. relay: 403 "{line} A member buys nothing: {account}'s room is its admin's to buy." (relay.rs:224,231); 400 username rule (relay.rs:237); 400 "an e-mail address is required: it is where your welcome mail and receipt go" / "that does not look like an e-mail address (name@example.com)" / "the two e-mail addresses do not match" (rules.rs:146,149,152); 400 "that is not a plan this console can ask for" (relay.rs:246); 400 market "X" is not one this estate sells in (SE, FI, DK) (rules.rs:52); 429 "too many signup attempts from here; wait a moment" (relay.rs:254); 504 START_TOO_SLOW (relay.rs:276); 502 "the checkout address is not one this console will send a browser to" (relay.rs:286). Box (→409): name refusal (svc-signup.rs:1067); "This email address already has an account here. Sign in to it instead — nothing has been charged." (signup.rs:142; svc-signup.rs:1083); "Someone is signing up with this account name right now. Choose another name, or try again in half an hour." (signup.rs:153; svc-signup.rs:1092); "A free account cannot be made here right now. Nothing has been charged." (signup.rs:151; svc-signup.rs:555,614,619); plan {id:?} has room and no price: … (svc-signup.rs:577); "REFUSED [signup-other-site] the {which} page {u:?} is not on this estate's site {site}: a signup returns where it began" (svc-signup.rs:1001); "REFUSED [estate-unnamed] …" (PermissionDenied → 502 + WHO_NOT_CONFIRMED) (svc-signup.rs:1054); vendor Unavailable → SHOP_DOWN (svc-signup.rs:616) relay signup.start_rate_limited (relay.rs:253), signup.start_timed_out (275), signup.start_bad_url (285), signup.start (288), signup.start_failed (303), signup.start_fallback (294), signup.not_sold (298). Box: "monetize-server: a card check for a free {product} account {:?} was minted at {vendor} ({session})" (svc-signup.rs:609); "…vendor {:?} checks no card (or named no session): no free {product} account is made without one" (613); "…vendor {:?} would not mint a card check: {e}" (618); "…a free {product} account was asked for and its vendor {route:?} is not loaded: no card check, no account" (554); one line on every return: "monetize-server: signup start {user:?}: {outcome} in {ms} ms — {stages}[ — SLOW: past the 5 s a signup may take (budget FAILED)]" with outcome "card check" or "account" (svc-signup.rs:960–968, 1133,1141)
B1.4b box (no vendor routed: open build) same call → start_account SignupRecord Created under vendor account, session acct_<32hex> (signup.rs:388,407–411). The URL returned is the site's own /welcome. "Payment received, but your account could not be created automatically. Please contact us …" (signup.rs:134; svc-signup.rs:302) "monetize-server: an account at the shop was made for {:?} (account)" (svc-signup.rs:349); sign-in mail line (800/801)
B1.5 browser → Stripe (mode=setup) → back — (the success URL is {origin}/welcome?session={session}[&next=], urls.rs:28) card checked at Stripe buy.js: "Your account could not be made. Please try again." (buy.js:627) —
B1.6 welcome.js → relay → box (every 2 s; after 10 min every 60 s, welcome.js:16,24–25) GET /auth/signup/status?session= → Signup.Status → finish_card_check (svc-signup.rs:1397, 709) Open → PENDING. Expired → REFUSED (Final). Checked → make_account → SignupRecord Created (svc-signup.rs:777). PENDING words "Your card is being checked. Nothing is charged." (signup.rs:149); REFUSED "The card check was not finished, so no account was made. Nothing has been charged — start again to make your free account." (signup.rs:147); "This card already has a free account here — sign in with it, or choose a plan. Nothing has been charged." (signup.rs:145); "{why} Nothing has been charged — start again with another name." (svc-signup.rs:783); vendor unreadable → Unavailable SHOP_DOWN → relay 502 (svc-signup.rs:722–742); relay 400 "that is not a checkout session this console can ask about" (relay.rs:64,365); 429 "too many signup lookups from here; wait a moment" (relay.rs:369); welcome.js: 404 "This console does not do that" / "paid signup is not offered here." (204); 400 "That link is not one of ours" (210); !ok "Setting up your account …" / "@@PRODUCT@@ is not answering yet. Try again in a moment." (221); unknown for 30 s → "This session is not known to us" / "Nothing here matches that address. If you paid, we will find it." (291–292); pending "Waiting for Stripe to confirm your card." (296); refused "We could not finish this" (282); after 10 min "Still setting up" / "This is taking longer than usual. …" (371–372) box "card check {vendor}:{session}: the vendor said {e}" (729); "card check …: the card already has a free {} account here ({held}); no second one" (751); "an account at the shop was made for {:?} (account, card checked)" (349); "the sign-in link for {username:?} was mailed: {sent}" / "…was NOT mailed to {masked} ({e:?}); the account is made" (800–801); functional_status ("monetize-server/signup","card check") on unavailable (95); relay signup.not_created (relay.rs:337)
B1.7 box → mail — mail kind account.sign_in, subject "Your sign-in link for https://{host}" (svc-signup.rs:818–819) — as above
B1.8 relay → site (passkey) (status created) → BuySite::signup_account relay answers invite_url /login?signup=<s>&user=<u> (urls.rs:73) "that order is for {:?} and this console is {product}'s" (relay.rs:346); "this signup's registration link has already been used" (relay.rs:354); the site's own Refusal, e.g. 409 while the product has no such person yet (site.rs:164–166) relay signup.link (relay.rs:351), signup.link_failed (relay.rs:356)
B1.9 welcome → relay → box POST /auth/signup/sign-in-again → Signup.Status + Signup.SendSignInAgain — relay 409 "This account is not made yet: its sign-in link is mailed once it is." (relay.rs:457), "This account already has a security key: sign in with it." (relay.rs:461); box "no {product} signup {session:?} here" (svc-signup.rs:842 →409); "It was sent a moment ago. Look in your inbox (and spam), or try again in a minute." (869 → 429); "The mail could not be sent right now. Try again in a while." (881 → 502); OK words "We’ve sent it again to {masked}." (877) box "the sign-in link for {username:?} was mailed again: {sent}" (876) / "…was NOT mailed again …" (880)

8.5 B2 – Paid account (plan row; pay once, account made after the payment)

# who → whom relay route → gRPC state after error words (file:line) log events (file:line)
B2.1–2.2 as B1.2–B1.3
B2.3 buy.js → relay → box POST /auth/signup/start {plan: step.id} (buy.js:622–624) → Signup.Start Checkout minted. record_minted, record_welcome and a name hold of 35 min (svc-signup.rs:1305–1308). Oversell slot held. as B1.4, plus: "The price of this plan changed while the page was open. Look again: nothing has been charged." (svc-signup.rs:1114); plan {:?} is {of}'s, and this checkout is for {product} (1109); plan {asked:?} is not sold here; [signups.<product>.plans] names {known:?} (118) → the buyer reads it verbatim: the internal-word filter (rules.rs:166) does not list "[signups." (see 8.15); "this deployment sells {n} signup plans and the request named none; …" (125); NO_ROOM "We cannot create an account of this size here right now. Nothing has been charged. Please ask us and we will set it right, or choose a smaller size." (signup.rs:190; svc-signup.rs:1201,1240) → Unavailable → 502 SHOP_DID_NOT_ANSWER at the relay; Unimplemented "no vendor here can sell a {product} signup at …" (1263) / "vendor {:?} minted a checkout with no session id: …" (1300) / "vendor {:?} mints no signup checkout; …" (1317) → relay fixed link or 501; "vendor: {e}" (1322 → 409) relay as B1.4; box timing line, outcome "minted" (svc-signup.rs:1309); queued past the wall: oversell queue_note line (1236); functional_status ("monetize-server/signup","capacity"/"checkout") (95)
B2.4 browser → Stripe checkout → /welcome?session=cs_… — — buy.js "Taking you to pay …" (buy.js:633) —
B2.5 Stripe → box webhook POST /callback/stripe (lib.rs:282) SignupRecord Pending, then settled behind the answer (callback.rs:256–285) 413 "callback body too large" (callback.rs:84); 503 "busy; retry" (88); 404 "no vendor {vendor:?} is linked into this server" (188); 401 "callback did not verify" (200); 503 "temporarily unavailable; retry" (218); 400 "callback not understood: {e}" (310); 500 "internal error" (105); not minted here → 200 ignored "signup {v}:{s} was not minted by this monetize — another system's checkout on the same account; not settled here" (247) "monetize-server: {vendor} callback REFUSED ({kind}) #{seq} — {detail} [recorded; the console reads CallbackAdmin.ListRefusals]" (callback.rs:177); "{vendor} callback IGNORED: {why}" (249); "callback signup {v}:{s} settled behind the answer: {PENDING/CREATED/REFUSED}" (266); "callback signup …: kept and answered PENDING within {ms} ms; the account is made behind the answer" (277); functional_status ("monetize-server/callback", vendor, …) (175, 209, 217, 248)
B2.6 box settle → sink (signup::settle, signup.rs:1683) Created {namespace, username}, Refused {Name/Final}, or Pending (sink Unavailable; refused after the 24 h PENDING_CEILING, signup.rs:107) Refused (checks): plan {:?} is not sold here; …, "paid {} ({} of it tax, {} net), but plan {:?} costs {} here" (signup.rs:1257–1270); sink Refused → customer reads COULD_NOT_CREATE (signup.rs:134); NameTaken → renameable; NEVER_ANSWERED "sink never answered: {why}" (signup.rs:112) "signup {v}:{s} could not be recorded: {e}" (1696); "signup {v}:{s} stays pending: {why}" + functional_status (1760–1761); "…was decided {next:?} and the store refused it: {e}" (1774); "…has no welcome page to mail" (1790); functional_status line "{v}:{s} CREATED {user} in {ns}" / "{v}:{s} REFUSED {reason}" (1795–1797); the room is queued to the grow batch (grow/batch.rs:807–815, "grow-batch {product}: request {id} was NOT queued: {why}"); sign-in mail (svc-signup.rs:800)
B2.6p (pulled product, e.g. Holger) box queues → estate main pulls add_principal Fleet.Orders (pull.rs:37) stays Pending (sink Unavailable "queued: …"). The main acks: ok → Created and the entitlement order is queued (pull.rs:24); taken… → Refused Name "queued: {instance}'s main creates {:?} when it next calls monetize" (signup_pull.rs:82); "username {:?} is taken on {instance}; choose another — the money is already at the vendor and nothing was refunded automatically ({why})" (92–95); "{instance}'s main refused the account {:?}: {why}" (96) as B2.6 (stays-pending line)
B2.7 welcome.js → relay → box (2 s poll) GET /auth/signup/status → Signup.Status (svc-signup.rs:1387): finish_paid_signup asks the vendor when no row exists yet (640) PENDING / CREATED / REFUSED / UNKNOWN PENDING words (signup::pending_words, PAID-WORDS): "Payment received. Setting up your account; this takes a few seconds." (also while a pulled queue or any "not now" waits); past SETTLE_BOUND_MS (2 min) "…This is taking longer than usual; your payment is safe…"; "…temporarily down for maintenance…" ONLY when the product says so by name (SignupError::Maintenance, RA4-20); vendor open: "Waiting for your payment to be confirmed. Nothing more is needed from you." (155); closed unpaid → REFUSED "The payment was not finished, so no account was made. Nothing has been charged — start again to sign up." (157); CREATED words "Payment received. Your account is ready." (131); welcome.js as B1.6, plus pending fallback "The payment has reached us. Making the account now." (306), refused+renameable "Choose another username" (275) "signup {v}:{s} read PAID at the vendor by its return page: {decided:?}" (svc-signup.rs:658); "…the vendor could not say where it stands ({e}); the webhook decides" (671); relay signup.not_created (337)
B2.8 welcome → relay → box POST /auth/signup/rename → Signup.Rename Refused(Name) → Created / Refused "no signup here has that checkout session" (svc-signup.rs:1535 → 404); NotRenameable why (1536 → 409); new-name refusal (1537 → 409); Unavailable → MAINTENANCE (1539 → 502); relay 400 username / NOT_A_SESSION (relay.rs:396–399); 429 (403); welcome.js "Use a-z, 0-9, dot, underscore or dash — 2 to 32 characters, starting with a letter or a digit." (317), "@@PRODUCT@@ is not answering. Try again in a moment." (348) relay signup.rename_rate_limited (402), signup.rename_failed (408), signup.renamed (420); box "{v}:{s} RENAMED {:?} -> {user} REFUSED {reason}" (signup.rs:1990)
B2.9 as B1.8–B1.9

8.6 B3 – Private instance as a visitor (new_account = true, "pay once")

# who → whom relay route → gRPC state after error words (file:line) log events (file:line)
B3.1 browser → relay → box GET /auth/private/ladder → (Sells) EstateOrder.Ladder — 429 (relay.rs:535); 404 "A private {word} is not sold here." (538); Ladder NotSold "A private {title} is not sold here right now." (svc-eo.rs:77–79,109); per-step reason comes from the quote error (eo/ladder.rs:166); buy.js "Nothing is on sale here right now." (buy.js:560) box "monetize-server: estate order: not sold here: {w}" (svc-eo.rs:108); relay private.rate_limited (relay.rs:534)
B3.2 buy.js → relay → box POST /auth/private/start {company,email,account,domain,sizes} (buy.js:649–651) → EstateOrder.Start{new_account:true, actor_ticket:[], welcome_url = {origin}/welcome?session={session}&next=<private>&company=co (urls.rs:42), cancel_url = {origin}{pages.private} (urls.rs:48)} EstateRun phase ordered. Customer state waiting_for_payment "Waiting for your payment on the payment page." (eo.rs:969). Name held under ACCOUNT_VENDOR (svc-signup.rs:495–499). History "{shown} ordered at {price}; the hold of {amount} is asked for" (eo.rs:3258) relay 400: "which instance? a company name: lowercase letters and digits, a letter first, at most 16" (relay.rs:605); "that email address is too long" / "that domain is too long" / "which size? that plan is not one sold here" (499–505); "which account? the name your new account is to have" (619); "the e-mail address your new account's sign-in link goes to" (622); 504 START_TOO_SLOW (655); 502 PAY_URL_REFUSED (660). Box, NewAccount checks: "the account name: an account on this site, without @/" (svc-eo.rs:167 → 400); "the cancel page is an https:// address" (svc-signup.rs:396 → 400); "An account cannot be made with this order here. Sign in to your account, or create it first." (375,421 → 409); "the welcome page is an https:// address with {session} in it" (433 → 400); name refusal (439 → 409); email refusal (443 → 400); EMAIL_HAS_ACCOUNT (447 → 409); NAME_BEING_BOUGHT (452 → 409); no_sink (Unimplemented → 502 SHOP_DID_NOT_ANSWER). Box, start_with_on: a company name is 1 to 16 characters; {name:?} is {n} / "a company name starts with a lowercase letter: …" / "…lowercase letters and digits only: …" / "{name}" is reserved / "…is reserved for the test estates" (monetize-cloud/src/lay.rs:445–462 → 400); "{host} is taken" (eo.rs:3096,3134,3151 → AlreadyExists 409); "{domain} is already ordered" (3138 → 409); the account name {account:?} is one the new {title} keeps for itself … (2769 → 400); "an order needs the address its "ready" mail goes to" (3111); "a domain of your own cannot be used here yet: leave it empty, and your {title} is {host}" (3120); NotSold "this shop has no way to install a private {title} yet ([estate_orders] …)" / NO_DNS_WRITER (2116–2123, 3155, 3162) → buyer reads "A private {title} is not sold here right now." (svc-eo.rs:109); Price → "This size is not offered right now." (svc-eo.rs:99,116 → 409); Vendor "the vendor {:?} is not linked here" (eo.rs:3178) / "the vendor refused the hold: {e}" (3214) → Unavailable → 502 SHOP_DID_NOT_ANSWER; Busy "{host} is being worked on right now; ask again in a minute — nothing was held or changed" (eo.rs:2620 → 502) box "monetize-server: estate order estate/{co}: ordered" (eo.rs:3317); on a refused start after the page opened: "ALERT estate-orders.page {ref}: the order was refused ({e}) and its payment page {v}:{id} is NOT closed ({…}); close it at the vendor" (eo.rs:3226); nudge "estate orders: the tick an answer nudged is still running after 2 s; answered, it finishes behind" (eo.rs:3387); relay private.start (relay.rs:662), private.monetize_failed (527)
B3.2r same visitor retries (F-B-01) same the same unpaid ORDERED run is answered (eo.rs:3131–3132, 3327–3350) — —
B3.3 browser → Stripe checkout — (reference estate/<co>; return per the vendor's config, see 8.15) — buy.js "The payment page did not open. Nothing has been charged — please try again." (buy.js:666) —
B3.4 box tick → vendor (driver, eo.rs:3806–3835) ordered → held on capture ("paid: {amt} captured ({ref}); the estate's secrets are made and sealed; queued", eo.rs:4935). Unpaid at order_ttl → released "the payment page expired unpaid; nothing was held" (3823); declined → released "the payment was not authorised; nothing was held" (3833) park whys: "waiting for the buyer's payment" (3830); "the capture did not go through yet: {e}" (3812); "the vendor did not answer: {e}" (3834); "paid, and the custody refused: {e}" (4936) "estate order {ref}: ordered → held" (eo.rs:3710); "…→ failed: {why}" (3726)
B3.5 box (in paid()) → sink: makes the visitor's account make_paid_order_account (svc-signup.rs:514) SignupRecord Created under ACCOUNT_VENDOR with the order's session. Until then the run parks in ordered, captured "ALERT estate-orders.account-not-made: paid, and its account {:?} could not be made ({why}); an operator sets it right — nothing is laid for another's account" (svc-signup.rs:537); "ALERT estate-orders.account-not-made: paid, and the sink refused its account {:?}; …" (538); "paid; its account {:?} is not made yet: {msg}" (539) printed as "monetize-server: estate order {ref}: {why}" (eo.rs:4931); "an account at the shop was made for …" (svc-signup.rs:349); sign-in mail account.sign_in with the welcome_url (800)
B3.6 box tick: queue / own-domain DNS — held → waiting_dns (own domain; "waiting for the customer's records at {domain}", eo.rs:3838) → held → laying ("a slot is free; laying", 4075) customer state queued "Your {t} is paid and being prepared." (eo.rs:970); waiting_dns "{…} Add the two records below at the DNS provider of {domain}; we look every minute." (972–975); fail "the records for {domain} were not in place within {h} hours" (3842); queue whys "queued: {n} estate(s) are being laid" (3862), "queued behind {host}, ordered first" (3870), "orders wait: raise max_estates: …" (4052), "orders wait: raise the cap: …" (944, 4059, 4077) "estate order {ref}: queued: waits for the cap or a slot ({words})" (eo.rs:4118); "ALERT estate-orders.queue {ref}: {words}" (4130, at most hourly); functional_status ("monetize-server/estate-orders","queue") (4132)
B3.7 box → cloud: lay — laying → installing ("{n} server(s) and {d} disk(s) laid; {dns line}", eo.rs:3948); DNS records written Fail: "the lay was refused: {e}" (4038); "the cloud {:?} is not linked" (3979); re-check price sentence (4007); Park: "the cloud is unavailable; laying again next tick: {w}" (4034); "the DNS records were not written: {e}" (3934); back to held "orders wait: raise the cap: …" (4002, 4037) "ALERT estate-orders.dns-takeover …" for site orders only (3970)
B3.8 box → installer: install — installing → paired ("the installer's verdict is PASS…", eo.rs:4328) Park "the estate's DNS records do not answer yet: …" (1160, 4199); Fail "the installer's verdict is {v}: {detail}" (4296); "the installer: {e}" (4298); "the custody could not make the buyer's first-login invite: {e}" (4243); deadline "the install did not finish within {m} minutes ([estate_orders] install_deadline_mins) without every box PAIRED: …" (4800, 4864) "ALERT estate-orders.install-deadline {ref}: {why}" (4792, 4844–4846, 3897); "ALERT estate-orders.guardian {ref}: installed (PASS), but the guardian was NOT told of the estate yet: …" (4303, 4321); customer state installing "{t} is being installed on your machines." (977)
B3.9 box ← boxes pair / call home (Fleet.Report), then sale check — stays paired until all three capture steps pass whys "held at step 1 of 3 (the call home): …", "held at step 2 of 3 (the main's word that it serves): …" (4360–4364, 4395–4406); "the sale check over https://{host} did not pass: {why}; nothing is captured" (2050, 4461), asked every 30 s (2047); "the estate did not answer within {s} s of its lay" (3792 → failed) —
B3.10 box: capture (already paid at the order) — captured, paid_until = now + term ("the estate called home …; paid at the order (…), for … from now: paid until …", eo.rs:4419–4427) — "estate order {ref}: paired → captured" (3710)
B3.11 box → mail: ready mail tell() (eo.rs:5149) told = true subject "Thank you — your {title} is ready: https://{host}" (kind estate.ready) with the first-login link https://{host}/login?invite=…&user=… (5136); waits without the invite: history "estate.ready mail NOT sent: the first-login invite could not be read from the custody; asked again within the hour" (5163) "ALERT estate-orders.ready {ref}: the first-login invite could not be read; the ready mail waits" (5164); "ALERT estate-orders.ready {ref}: the {kind} mail was not sent ({e:?}); asked again within the hour" (5246); history "{kind} mail sent: …"
B3.12 buy.js watch → relay → box (every 3 s until ready/failed, buy.js:711) GET /auth/private/status?company= → EstateOrder.Status (a visitor has no ticket: state + words only, svc-eo.rs:232–233) ready: "Your {t} is ready at https://{host}; it is paid until {date}. Your first sign-in link was mailed to you." (eo.rs:980–987) plus " The sign-in link for your new account is in your mail: open it to register your passkey, then sign in." (svc-eo.rs:70,228) "which estate? ?company=" (relay.rs:707); NotFound "no order for {co:?}" (svc-eo.rs:219 → 404); buy.js "no answer yet" (buy.js:703) —
B3.13 failure path: box give-back — unpaid: failed → released ("the hold is released", eo.rs:4507); paid: failed → held (retry 1 of 1, eo.rs:4498) → owed customer: "Your {t} could not be set up. Nothing will be charged: the hold is being released." (1000); paid/owed: "Your {t} is paid. Setting it up takes longer than usual; we are on it, and you get a mail when it runs." (998); mail estate.failed "Your {name} {host} could not be set up — nothing was charged" (5232–5236) "ALERT estate-orders.owed {ref}: paid, failed {n} time(s), last: {why}; retry or cancel with a refund" (4501); functional_status ("…/estate-orders","owed") (4502)

8.7 B4 – Private instance, signed in (actor ticket)

Same as B3 except for the following rows.

# who → whom relay route → gRPC state after error words (file:line) log events (file:line)
B4.1 relay: who + ticket (inside POST /auth/private/start) BuySite::who, ticket(PURPOSE_ESTATE) — 401 "A private {word} is bought by your account at {host}, where you see and grow what you bought. Sign in, or Create your account at {host} first." (relay.rs:516–518,548; only on the signed-in doors, because a visitor at /start takes B3); 403 "{line} Members don’t buy: {buys} buys for {account}." (552); the site's ticket Refusal (554) —
B4.2 relay → box POST /auth/private/start → EstateOrder.Start{account, actor_ticket, email:"", cancel_url} (relay.rs:642–651) ordered (OrderedBy::Shop when cancel_url is set, svc-eo.rs:198–203) Unauthenticated → 403 "a private {title} is bought, read and changed by its {home }account: sign in to that account and ask again" (eo/account.rs:30–33; svc-eo.rs:133); Unavailable "the account cannot be checked right now: {why}" (svc-eo.rs:134 → 502); "This account has no e-mail address here for the order's mail. Contact us, and nothing has been charged." (svc-signup.rs:472,489 → 409); otherwise as B3.2 start_with_on "an estate call for account {account:?} was refused: {why}" (eo/account.rs:51); "estate order …: ordered" (eo.rs:3317); relay private.start
B4.3–B4.12 as B3.3–B3.13, without B3.5 (no account is made) Status carries the ticket → whole answer (paid_until, records, change_status) (svc-eo.rs:235–264) same phases owner_of refusals: "an estate call for {co:?} was refused: no such order" (eo/account.rs:121); "…from {caller} was refused: it was bought on {bought_on:?}" (127) → both 403 with NOT_PROVEN words —

8.8 B5 – Grow the account (bundle; caps)

# who → whom relay route → gRPC state after error words (file:line) log events (file:line)
B5.1 buy.js → relay → box GET /api/account/subscription/bundles → (Products.List) + Purchase.Bundles with a read ticket (relay.rs:950–958) — 401 "Sign in to the account that buys first." (relay.rs:938); 403 member (935); 501 "this appliance's monetize does not verify tenant renewal tickets, so a subscription cannot be renewed from here. An operator can renew it." (1078, every site since A32-9); box "tenant is required" (svc-purchase.rs:1605); check_actor "no actor ticket, and this request did not come through the operator's door: an empty ticket is nobody" (1234 → PermissionDenied → 502 + WHO_NOT_CONFIRMED); "{product} cannot be asked who you are right now, so this request cannot be authorised: {why}" (1257). While reading: relay window pending "Awaiting the cloud provider…" / late "The cloud provider has not answered yet. The sizes on offer appear here as soon as it does." (sb-purchase.rs:115–117); buy.js re-asks every 3 s (buy.js:539); "None of the sizes on offer can be added to this account right now." (sb-purchase.rs:119) box "the shop window for {p}/{t} is still being read …; answered reading, the console asks again" (svc-purchase.rs:1621–1625); "bundle not offered for {p}: {why}" (987); relay info "a bundle monetize cannot serve is left out of the window" (sb-purchase.rs:133)
B5.2 buy.js → relay → box POST /api/account/subscription/buy-bundle {bundle} → held_caps, Purchase.Bundles again, ticket order with the target caps (or renew for a renewal row), then Purchase.Start{bundle} (relay.rs:989–1045) Ledger Reserved row (attempt open). PurchaseState Pending, phase waiting_for_payment (svc-purchase.rs:1377) relay 400 "that is not something this account can order right now. Reload the page for what is available." (relay.rs:1010); 504 (1037); 502 PAY_URL_REFUSED (1042). Box: {bundle:?} is not a bundle {p} offers; ask Purchase.Bundles for the ones it does (1090 → 409); {bundle:?} is not on sale here (1096 → 409); "a request names a bundle OR caps, never both. …" (1073 → 400); "a renewal names no bundle: …" (1685 → 400); free product "nothing is sold here: {p} is served free on this deployment …" (900 → 409); quote refusals → FAILED_PRECONDITION with the operator's text (tx.rs:387–436, e.g. "nothing to sell {tenant}: the order raises no cap above what they already have", "the product cannot deliver this to {tenant}: {reason}", "the sale to {tenant} at {price} is under the floor …", "the plan {tenant} asked for is not sold: {why}") → 409; customer words ride in metadata monetize-customer-words-bin (svc-purchase.rs:871–883) but this door shows the message (see 8.15); no vendor → NotFound "this order costs {price} in {market:?}; {said} (asked: {asked:?}), and no vendor here takes that money; serving: {…}" (1127, 1759 → 404); "vendor: {e}" Unavailable (619 → 502) "purchase refused for {p}/{t}: {refusal}" (1746); queued past the room: oversell queue_note / grow queue_past_room line (1766–1773), which queues a grow_room order for a pulled product's main (pull.rs:51; monetize-guardian/src/pulled.rs:271); "bundle {:?} was ordered and is not offered for {p}: {why}" (1095)
B5.3 browser → Stripe → site /paid/<reference> — (site page; is_relay_path excludes /paid/<ref>, relay.rs:1420) — buy.js "The payment page did not open. Nothing has been charged — please try again." (666); a no-URL answer shows j.words/j.reason (664) —
B5.4 Stripe → box webhook POST /callback/stripe → ledger attempt → inbox → grant (callback.rs:313–389) ledger Captured (+ Settled), then EntitlementPushed → attempt closed Pushed: PurchaseState Paid, phase done; while captured but not pushed: paid_adding (svc-purchase.rs:1376) 200 "no open attempt for {ref}" (315); 409 "{ref} was reserved as {v}:{id}, callback names {v}:{id}" (326–331, HandleMismatch row); duplicate → {…,"duplicate":true} (348) "{vendor} callback {id} for {ref} (delivery {n}): kept; granting behind the answer" (357); "…delivered again …: already {word} — answered from the kept row, nothing moves" (344); "…its grant is in flight — answered at once, nothing started" (351); "callback grant {v}:{id} {ref} attempt {a}/{t}: {said}" (inbox.rs:343, 356); functional_status ("monetize-server/callback", v, …) "{ref} -> {state} {reason}" (callback.rs:371)
B5.5 site page → relay → box GET /api/account/subscription/status?reference= → Purchase.Status with a read ticket (asks the vendor when due) Pending / Paid / Released / Declined / Unknown; reason "reserved, awaiting the vendor" or "captured, entitlement not yet pushed" (svc-purchase.rs:1842) "this tenant has no purchase by that reference" (1285, 1336 → 404); "an unscoped read of any order is the operator's, through the bearer's door" (1312) "status {ref}: the vendor was asked while the customer watched: {state:?}" (1828); "status {ref}: asking the vendor: {msg}" (1830)
B5.6 box → product entitlement push (EntitlementPushed). For a pulled product: an entitlement order the main pulls (pull.rs:24) granted. Words ORDER_GRANTED "Your account's limit is raised now — the extra room is already yours to use. There is nothing to set up and nothing to wait for." (tx.rs:549–550) — —

8.9 B6 – Grow an instance (ChangePlan; one step of the ladder)

# who → whom relay route → gRPC state after (PlanChange.status, eo/change.rs:41–46) error words (file:line) log events (file:line)
B6.1 buy.js → relay → box GET /auth/private/ladder?company= (with the estate ticket) → EstateOrder.Ladder current, floor "which instance? ?company=" (relay.rs:570); NOT_PROVEN 403; "no order for {co:?}" (eo/ladder.rs:208) —
B6.2 buy.js → relay → box POST /auth/private/change → EstateOrder.ChangePlan (minting) (relay.rs:670–699) price difference > 0: waiting_for_payment (hold opened, reference estate/<co>/change-N, change.rs:250); 0: queued (no URL; buy.js shows words, buy.js:659) relay "which instance? a company name" (673); 504 (688); 502 PAY_URL_REFUSED (693). Box: "there is no estate {host}" (change.rs:325); "{host} is {phase}: only a delivered, paid estate changes plans" (327); "a plan change is already {status} ({ref})" (330); "data never shrinks: main's data disks are {a} GB each now, and {b} GB was asked" (215–218 → 400); the product's grow rule refuses a machine added or taken away, or one whose data its estate does not move, by name ("… nothing was held"; A4-FACTS); "the new numbers grow the data disks of …, and this cloud grows no disk" (a cloud with no grow path); "nothing to change: front and main are already on those plans and main's data at that size" (233) → all NotNow → 409; Price "the provider's plan list: {e}" (239) → "This size is not offered right now." (409); "the vendor refused the hold: {e}" (280 → 502) history "plan change {ref} opened: {words}" (change.rs:307); relay private.change (relay.rs:695)
B6.3 browser → Stripe → /paid/estate/<co>/change-N → relay 303 → {private}&company=co&change=1 GET /paid/estate/{co}/{change} (relay.rs:926; urls.rs:62) — buy.js "Payment received. Your change is being made: the machines restart for a few minutes while it is done, and come back by themselves." (buy.js:726) —
B6.4 box term tick (change::advance) → vendor — waiting_for_payment → queued (Reserved) why "waiting for the payment" (407); fail "the payment page expired unpaid; nothing was changed" (398) / "the payment was not authorised; nothing was changed" (408) / "the vendor CAPTURED the money before the change; it must be refunded" (404, 409) / "the estate's paid period ended ({phase}); the change is called off and nothing is charged" (389) history "plan change {ref}: {before} → {after} ({why})" (change.rs:362)
B6.5 box: notice, price check, cap — queued → resizing why "{host} is told first: its machines stop at {time}" (453); "orders wait: raise the cap: …" (475, 488, via cap_alert); fail "at the buy: {sentence} For this plan change: nothing was changed." (501); "the provider's price: {e}; nothing was changed" (467) "ALERT estate-orders.queue {ref}: …" (eo.rs:4130)
B6.6 box → cloud resize, estate calls home, capture — resizing → done fail "{role} could not change to {plan}: {e}; {left}" (548); "resized, and the estate did not call home within {m} minutes ({e}); …" (557); whys "resized; waiting for the estate to call home: {e}" (559), "the capture did not go through yet: {e}" (574) —
B6.7 box → estate main + twin pull grow_go_ahead (data step only) Fleet.Orders kind grow_go_ahead (embed-fleet.rs:58), then the box's Fleet.Ask grow_set (embed-fleet.rs:73) run.growth signed grow_set refusals: "{host}'s {role} data is paid to {paid} GB a disk; {gib} GB is bought at {apex} first", "a disk never shrinks: …", "a growth of {host}'s disks is already running here" (eo/sold.rs:944–953) history "the growth go-ahead is signed for {co}: …" (sold.rs:143–147); "ALERT sold-estate.grow the go-ahead ({ref}) was NOT queued for {co}'s boxes: {e}" + functional_status ("monetize-server/sold-estate","grow-go-ahead") (sold.rs:151–154)
B6.8 box → mail finish() change recorded mail estate.changed "Your {name} {host} has its new plans" (change.rs:616–617); estate.change_failed "Your {name} {host}'s plan change did not go through — nothing was charged" (644–645) —
B6.9 buy.js watch GET /auth/private/status → change_status / change_words (why only when failed, svc-eo.rs:249) — — —

8.10 B6b – Instance sign-in link again (method the code has beyond the list)

POST /auth/private/sign-in-again → EstateOrder.SendSignInAgain (relay.rs:739–758; svc-eo.rs:267).

8.11 B7 – Renew

B7a – Renew an instance ("Renew ")
# who → whom relay route → gRPC state after error words (file:line) log events (file:line)
B7a.0 box → mail before the end term::remind — mail estate.renewal_reminder "Your {name} {host} is paid until {date}" (eo/term.rs:690–691) history "{kind} mail sent/NOT sent" (term.rs:655–657)
B7a.1 buy.js renewing() → relay → box POST /auth/private/renew {company} → EstateOrder.Renew (minting) run.renewal opened, reference estate/<co>/renewal-N (term.rs:297). Phase unchanged (captured/grace/suspended) relay 504 (relay.rs:799), 502 PAY_URL_REFUSED (804); a visitor gets a link "Sign in at {host} to renew {co}" (buy.js:845–848). Box: "there is no estate {host}" (term.rs:318); "{host} is {phase}: there is nothing to renew" (320); "{host} is paid until {date}; its renewal opens on {date}" (324) → 409; "the renewal's old payment page could not be closed yet: {e}; ask again in a minute" (276 → 502); "the vendor refused the renewal: {e}" (307 → 502). OK words "{term} more: {amount} ({monthly} a month). Pay on the payment page; it is charged at once." (svc-eo.rs:295) "ALERT estate-orders.queue {ref}: renewed over its spend cap: {…}" (term.rs:295); history "renewal {ref} opened: …" (310); relay private.renew (relay.rs:806)
B7a.2 browser → Stripe → /paid/estate/<co>/renewal-N (returns.rs:14–17, 42–45) → relay 303 to plain {pages.private} (urls.rs:62–65, because it is not change-) GET /paid/estate/{co}/{change} — — —
B7a.3 box term tick → vendor capture term::step (term.rs:418–489) paid_until extended; grace/suspended → captured ("paid until {date}; the machines are started again, every disk as it was", 513; "paid until {date}", 515) park "the renewal's capture did not go through yet: {e}" (436-ish); "renewal {ref} was paid as its page expired; capturing it" (451); notes "renewal {ref} expired unpaid" (447), "renewal {ref} was not authorised" (460) history "renewal {ref} captured ({vref}): …" (477); mail estate.renewed "Your {name} {host} is paid until {date}" (480–486); a re-signed go-ahead is queued (sold::renewed, sold.rs:167)
B7a.4 unpaid path term::step captured → grace → suspended → deleting → deleted (561, 586, 598, 623) mails estate.lapsed / estate.unpaid (term.rs:728–730), estate.suspended (577), estate.deleted (615); customer "overdue"/"suspended"/"deleted" words (eo.rs:989–996) —
B7b – Prolong the account (hosted plan)
# who → whom relay route → gRPC state after error words (file:line) log events (file:line)
B7b.1 page → relay → box GET /api/account/subscription → Products.List, then Purchase.Quote{renew} with a renew ticket quote (no move) 501 RENEWAL_NOT_VERIFIED (relay.rs:1078,1089,1095); FAILED_PRECONDITION becomes a 200 Renewal with can_serve=false and words from metadata (relay.rs:1131–1134; sb-purchase.rs:410); "We cannot renew this account automatically. Nothing has been charged — please contact us and we will set it right." (tx.rs:522–523) —
B7b.2 page → relay → box POST /api/account/subscription/renew → Purchase.Start{renew:true} Reserved (Pending) → as B5.3–B5.6; RENEWAL_GRANTED "Your subscription is extended — your account carries on exactly as it is. …" (tx.rs:~560) 504 (relay.rs:1156); 502 (1161); "a renewal names no bundle and no caps" (svc-purchase.rs:1115 → 400); "nothing to renew for {tenant}: {why}" (tx.rs:419 → 409) relay subscription.renewed (relay.rs:1163)

8.12 B8 – Cancel

B8a – Cancel an unpaid account order ("Cancel order")
# who → whom relay route → gRPC state after error words (file:line) log events (file:line)
B8a.1 page → relay → box POST /api/account/subscription/cancel {reference} → Purchase.Cancel{reason: "the customer cancelled the order before paying" (relay.rs:1188)} with a cancel ticket vendor hold released, ledger Released "cancelled: {reason}" (tx.rs:1745); PurchaseState Released (or ReleasedWithOrphans), phase cancelled; a second press answers already_closed 401/403 (relay.rs:938,935); box "cancel needs a reason: it is written into the ledger's Released row" (svc-purchase.rs:1995 → 400); "no order was ever placed under {ref}" (2002 → 404); "this tenant has no purchase by that reference" (1336 → 404); "{ref} was reserved at {v:?}, which is not linked into this server — the hold must be let go where it was taken" (2005–2008 → 409); "another caller is finishing this purchase right now; ask again in a moment" (720,2016 → 502) functional_status ("monetize-server/purchase","cancel") "{ref} cancelled: {reason}" (2053); relay subscription.cancelled (relay.rs:1218)
B8a.2 already paid → 409 same the capture is written down and the order finishes as paid (resume_reference, svc-purchase.rs:2027) 409 "{ref} has been paid, so it cannot be cancelled: it finishes as paid, and ending it now is a refund (Abandon)" (svc-purchase.rs:2030–2033); granted: "{ref} was paid and granted; there is nothing unpaid to cancel" (tx.rs:1195 → 409); "no attempt was ever made for {ref}" (tx.rs:1189 → 404) "cancel {ref}: finishing the paid order: {msg}" (2028)
B8b – Cancel an instance ("Stop renewing")
# who → whom relay route → gRPC state after error words (file:line) log events (file:line)
B8b.1 buy.js → relay → box POST /auth/private/cancel {company} → EstateOrder.Cancel (estate ticket) cancelled_at set; phase stays captured/grace; an open renewal page is released "{host} is not running a paid period it could stop renewing ({phase})" (eo/term.rs:212 → 409); "no order for {co:?}" (207 → 404); OK words "{host} runs until {date}, the end of its paid period. Then it is stopped, and every disk and all your data are kept until {date}, when it is deleted. Renew before then to keep it." (217–221) history "cancelled by its buyer: no renewal; stopped after {d}, deleted on {d}" (236); mail estate.cancelled "{host} is cancelled: it runs until {date}" (237); relay private.cancel (relay.rs:831)
B8b.2 box tick at the period's end term::step → suspended ("cancelled by its buyer: stopped at the end of the paid period, every disk kept; deleted on {d}", term.rs:549) → deleting → deleted park "cancelled, and the paid period ended; stopping the machines: {e}" (533) mail estate.stopped (541)
B8c – An unpaid instance order

There is no buyer door. The vendor's Cancel returns to cancel_url {origin}{pages.private} (urls.rs:48). The tick releases the order after order_ttl_secs: released "the payment page expired unpaid; nothing was held" (eo.rs:3823).

The operator can instead call EstateOrder.Settle refund:

8.13 B9 – Terminate (no backup)

# who → whom relay route → gRPC state after error words (file:line) log events (file:line)
B9.1 buy.js → relay POST /auth/private/terminate {company, confirm, backup:'skip'} (buy.js:898) — 400 "which instance? a company name" (relay.rs:886); 400 "Type the instance's name, {co}, to terminate it. Nothing was ended." (889); 400 "the backup: take or skip" (893); 403 {"error":"{why} Nothing was ended.","code":"stepup"} with default why "This needs a fresh touch of your key (a user-verified login from the last 10 minutes)." (relay.rs:900–904; site.rs:95–98); 401/403 from account_ticket —
B9.2 relay → box EstateOrder.Terminate (estate ticket) terminating (history "terminate asked by its account for {host}", eo/terminate.rs:210); OK words "{host} is being ended now: its machines, disks and DNS records are being given back." (232); done:false protected "{site} is one of the operator's own sites and is protected: it is never terminated here (its [estates.{label}] does not say terminable = true)" (116 → 409, which a buyer reads as NOT_DONE_HERE because of "[estates."); "{name} is one of the operator's own estates and is protected: …" (120); backup take → "[terminate-no-backup] backups are not offered at terminate" (38,171 → 409); "{name} is not an estate this shop runs: nothing to terminate" (181 → 404); "{estate} was adopted from its kit's install, not laid by this box: …" (eo/adopt.rs:357); "the name typed back is not {co}: type {co} exactly to terminate it. Nothing was ended." (197 → 400); "{host} is still being set up; it can be terminated once it runs" (200 → 409); "the cloud {:?} is not linked here: …" (206) "monetize-server: estate order: a terminate of {name} was refused: protected" (175); "ALERT estate-orders.terminate {ref}: no backup taken: none is offered at terminate" (93,97,156,211); ledger Instance row per step (150–154); relay private.terminate (warn) (relay.rs:914)
B9.3 box tick → cloud give-back → fleet forget terminate::step / finish (283–339) terminated ("terminated by its account") park "being terminated: {why}" (332); give-back failure backs off 10 s doubling up to 5 min (342–351) "ALERT estate-orders.terminate {ref}: every machine, disk and DNS record given back: …" (290); "…the ONE box's records of it forgotten: …" (306); "…terminated: nothing of it is left, and nothing more is charged" (312); "…the give-back FAILED (try {n}; asked again in {s} s): {why}" (362–365); "…a terminate step failed (asked again each tick): {why}" (103–105); "ALERT estate-orders.terminate {co}: the terminate's drive FAILED to take the run's pen …" (eo.rs:3683)
B9.4 watch Status → "terminated" "Your {t} was terminated, as you asked: its machines, disks and records are gone, and nothing more is charged." (eo.rs:995) — — —

8.14 B10 – Test-order paths (operator)

# who → whom route → gRPC state after error words (file:line) log events (file:line)
B10.1 operator UI → box POST /api/test-purchase/start (ui-server serve.rs:58) → TestPurchase.Start{product, action, site, live} TestRun kept (tp-<stamp>, test_purchase.rs:310). HostedPlan → start_signup(PayBy::TestPayment) → settle (B2.6). PrivateInstance → account via TestPayment (made at once, no card check) + estate start_with_on(OrderedBy::Test{payer}) → run.test = true (eo.rs:3060–3066, 3292) "the test button is the operator's: the operator bearer, or the console's signed call" (services/test_purchase.rs:21); "product {p:?} is not served here" (278); "{p} declares no test action {a:?}" (279); "REFUSED [test-site-unnamed] …" (283–286); "REFUSED [test-site-undeclared] {site:?} is not a site {p} is sold as here: nothing is bought for it" (289); offer.why_not, e.g. "no vendor is routed for it here", "{v} has no test payment method: nothing can pay without a person", "{v}: the vendor's key is not a TEST key (live, or it cannot say): a test button never spends live money" (195–203; svc-signup.rs:1005); live without confirm: "{host}: LIVE — you will be charged. This site's vendor key is live: …" (298–301); pay_as_test "{v}: {NOT_A_TEST_KEY}" (svc-signup.rs:1340), "vendor {:?} cannot pay a signup without a person (it has no test payment method)" (1346), "vendor (test payment): {e}" (1347) "monetize-server: test run {id}: {source}: {words}" (test_purchase.rs:267), sources signup/vendor/order/run, e.g. "stopped: {why}" (397); "a TEST signup {v}:{s} for {:?} was paid with the vendor's test payment method: {decided:?}" (svc-signup.rs:1357)
B10.2 CLI monetize … site test-order <site> → admin door POST /site/test-order (admin-door.rs:254,869; cli lib.rs:200–207) → EstateOrder.OrderSite{test_order:true} site run: test=true, site_order=true, no checkout page (eo.rs:3216). Then the same driver as B3.4–B3.11; the ready mail is the site variant "The site is ready: https://{host}" (eo.rs:5197); the site never lapses (term.rs:497) CLI "site-test-order-return" refusal (cli lib.rs:204); box "REFUSED [test-order-return] a test order opens no page, so it names no return page" (svc-eo.rs:501); "ordering a site is the operator's: …" (495); test_card_of: "REFUSED [test-order-live-key] …", "REFUSED [test-order-no-test-card] …", "REFUSED [test-order-key-unknown] …" (eo/site.rs:89–91), "REFUSED [test-order-no-vendor] …" (99); "{estate:?} is not a declared operator site …" (site.rs ~117); "{estate} stands: its {roles} called home in the last {m} minutes — a site that runs is not laid over" (~125); "{estate} is already ordered" (~132); operator cap "raise the estate's spend cap first: …" (eo.rs:4171–4175). The door reports {"refused":"<Code>","words":…} (admin-door.rs:232) admin tracing warn "door: the operator ordered the site {estate} laid by this box ({ref}), paid with the vendor's TEST card" (admin-door.rs:283); OK words "{estate} is ordered and paid with the vendor's TEST card ({card}): {amt} held, nobody pays at a page, and this box lays it" (svc-eo.rs:517)
B10.3 operator's own buy (site admin buying on an operator site) EstateOrder.Start (B4) operator_test = true, held at EUR 0.50 (eo.rs:912, 3166–3167); description prefix "operator test: " (3185) — —

8.15 What the trace found worth a look

  1. Where the vendor sends the buyer back after paying for an instance (B3/B4). Fixed by BUYFLOW-4 (2026-10-25): a console's buyer (signed in, or a visitor paying once) now returns to https://<site>/paid/estate/<co> of the site bought on, and the page shows "paid, setting up": the steps, where the order stands, and the instance's link when ready. As traced before:

    • The relay always sends cancel_url, so a signed-in start becomes OrderedBy::Shop{back_to:"", cancel_to} (svc-eo.rs:198–203). A visitor's start is OrderedBy::NewAccount with an empty back_to.
    • Both therefore call reserve_returning(req, "", cancel_to) (eo.rs:3196–3213). The default trait impl ignores cancel_to and calls reserve(req) (crates/monetize-pay/src/lib.rs:456–463).
    • Only OrderedBy::Buyer gets site_returns (https://<site>/paid/estate/<co>, eo.rs:3204).
    • So the return page depends on the vendor's configured success_url (https://{site}/paid/{reference}, monetize-admin/src/app_config.rs:178) and on whether the Stripe guest honours cancel_to. The trace did not read that guest.
    • The visitor's welcome page is reached through the mailed sign-in link (welcome_url plus the acct_ session), not through the vendor return.
  2. Renewal return lands on the plain private page. Fixed by BUYFLOW-4: renewal-<n> lands on {private}&company=<co>, as the first buy's way back. As traced before: A renewal's return is /paid/estate/<co>/renewal-N (returns.rs). paid_change_to only recognises change-<n>, so the buyer lands on plain /signup?plan=private without &company= (urls.rs:62–65). That looks unintended.

  3. Buyers may see operator text on bundle and renew refusals. Fixed by BUYFLOW-4: rules::buyer_words shows the customer's words from metadata first; without them, a message with a word of rules::OPERATOR_WORDS (lay, estate, guest, operator, cap, …) reads NOT_DONE_HERE; tests/buyflow4.rs scans the buy pages' and the relay's sentences. As traced before: On buy-bundle and renew, refused_call shows the gRPC message, which is the operator's sentence, e.g. "the product cannot deliver this to …". It does not use the customer words in metadata monetize-customer-words-bin (svc-purchase.rs:876–883); only account_offer reads them (relay.rs:1133). The NOT_DONE_HERE filter catches only messages containing the keywords at rules.rs:166.

  4. PermissionDenied surfaces as 502. Fixed by BUYFLOW-4: 403 with WHO_NOT_CONFIRMED. As traced before: http_status_of does not map PermissionDenied, so it falls to the default 502 (rules.rs:190–199) with WHO_NOT_CONFIRMED words. Examples: "REFUSED [estate-unnamed] …" and a refused actor ticket.

  5. Pulled-product sign-up shows a maintenance sentence while it waits. While the main has not yet acked add_principal, note_waiting is set, so a pulled product's pending sign-up reads the MAINTENANCE sentence (signup.rs:1762, 1850–1851), not "being created".

  6. Some line numbers are approximate. The lines marked "~" in eo/site.rs (~117–132) and term.rs (~436) were not read one by one. The B2.3 note about [signups. is also uncertain: the internal-word filter (rules.rs:166) does not list "[signups.", so that plan refusal probably reaches the buyer verbatim.

  7. Not traced:

    • (The ONE shop's own /shop/signup*, /shop/private* and /kop pages are deleted: OD6, A33-12.)
    • Purchase.Abandon (refund of a captured order, svc-purchase.rs:1946–1983).
    • EstateOrder.RestoreFront details.

Proposal (not done in this change): put the step id (for example B3.4) into each of these error and log lines in the code, so that a log line maps to its row of this guide.


9. Where it lives: traits and crates

Every trait and crate an integrator touches or should know. The links go to the source on gunnar.rs. monetize-impl is private and is not on gunnar.rs; its rows name the path in that repository only. "Integrator" = you implement or link it; "premade" = you only name it. The last column names the buy steps of section 8 the row serves.

9.1 The traits

Trait Repo + path Open or private What it is for Who implements it Buy steps
Cloud (9 ops) nordisk/monetize crates/monetize-cloud/src/cloud.rs open a cloud in nine verbs: manifest, catalog, price, lay, change, boot, firewall, destroy, inventory, each kind as an enum (Priced, Change, Boot, Give, Listing). The server and the core speak only these; the spend cap and the own-iron wall are the host's. A guest's wire keeps CloudGuest's op numbers, answered from the nine ops premade: upcloud.wasm, scaleway.wasm (monetize-impl); server, sandbox (open) B3, B4, B6, B8b, B9, B10
CloudGuest (the wire) nordisk/monetize crates/monetize-cloud-guest/src/lib.rs open what a cloud guest compiles against: the op table, export_cloud! premade (the cloud guests) as Cloud
Payment (10 ops) nordisk/monetize crates/monetize-pay/src/payment.rs open a payment vendor in ten verbs: manifest, page, status, capture, release, refund, callback, settlement, paid_since, livemode. The server holds every vendor as a monetize_pay::Vendor (its instance name and markets as data, over these ten ops); a guest's wire is still VendorGuest's 20 ops premade: stripe.wasm, klarna.wasm, swish.wasm (monetize-impl); invoice, sandbox (open) B1, B2, B3, B4, B5, B6, B7, B8, B10
VendorGuest (the wire) nordisk/monetize crates/monetize-pay-guest/src/lib.rs open what a payment guest compiles against: the op table, export_vendor! premade (the payment guests) as Payment
DnsWriter (6 ops) nordisk/monetize crates/monetize-cloud/src/dns.rs open writes a sold estate's names at the zone: write, write_taking_over, remove, listed, settled premade: dns_cloudflare.wasm (monetize-impl); ManualDns (open, the alert) B3, B4, B9, B10
DnsGuest (the wire) nordisk/monetize crates/monetize-dns-guest/src/lib.rs open what a DNS guest compiles against: the op table, export_dns! premade (the DNS guest) as DnsWriter
Product (6 ops) [P3] target: A24 §2.2; today Guest in nordisk/monetize crates/monetize-product-guest/src/lib.rs (28 ops → 6 [P2]) open what an estate of your product IS (facts) and the plan of ONE order (plan); the guardian (diagnose, remedy, observe) the integrator B3, B4, B6, B10
Facts, SaleProof (landed in P3a) nordisk/monetize crates/monetize-product-guest/src/facts.rs open your product's shape and the routes the sale check reads, as data the integrator (answers them) B3, B4, B10
Product (the host's plugin seam) nordisk/monetize crates/monetize-product/src/lib.rs open the native side the guest host serves a guest through (usage in, entitlement out) premade (the guest host, monetize-product-wasm) B1–B10
Store nordisk/monetize crates/monetize-front/src/ledger_backup.rs open where a product keeps its front's backups (Holger: a raw repository on its primary). The store of an EstatePlan (the Lake's object store input) is P3 the integrator, where the product keeps backups — (not a buy step)
BuySite nordisk/monetize crates/monetize-site-buy/src/site.rs open your site's half of the buy flow: who, ticket, held_caps, signup_account, stepped_up the integrator B1–B9
PumpBox (the box's half of the pump) nordisk/monetize crates/monetize-estate-side/src/pump/puller.rs open what your box reports and how it runs a pulled order (add_principal, entitlement, grow_room, grow_go_ahead, sign_in_again) the integrator B2, B3, B4, B5, B6, B6b, B7
Estate [P3] target: A24 §2.3 (section 3.8) open report, execute(order), box_key over the shared pump the integrator as PumpBox
Product (box side) nordisk/Skidbladnir crates/installer/nordisk-pairing/src/boxside.rs open the box at pairing: kind, tunnel_pubkey, capacity, offer, fed, the live door the integrator B3, B4, B6, B10
FrontProduct nordisk/Skidbladnir crates/installer/nordisk-pairing/src/frontside.rs open the front at pairing and update: the relay's tunnel key, its offer, the live door the integrator B3, B4, B10

9.2 The crates

Crate Repo + path Open or private What it is for Who uses it Buy steps
monetize-product-guest nordisk/monetize crates/monetize-product-guest open the SDK of your product operator (guest): the op table, the wire, the host doors, export_product!, Facts the integrator links it B3, B4, B6, B9, B10
monetize-site-buy nordisk/monetize crates/monetize-site-buy open the buy flow your site relays: BuySite, ShopLink, the relay router (26 routes), buy_js / welcome_js the integrator links it B1–B9
nordisk-pairing nordisk/Skidbladnir crates/installer/nordisk-pairing open pairing over WireGuard and mTLS, the live door, the shared update step [P4] the integrator links it (box and front) B3, B4, B6, B10
monetize-estate-side nordisk/monetize crates/monetize-estate-side open the box pump (Fleet.Report / Orders / Ask) and the front's signed shop door (P4a; over Fleet.Ask since P3, the old SoldEstate door for one release more) the integrator links it [P4] B2, B3, B4, B5, B6, B6b, B7
monetize-embed nordisk/monetize crates/monetize-embed open the product's entitlement cache (Ed25519-verified, answers while the ONE box is unreachable) and the pull channel's signed forms (SignedOrder, the order kinds) the integrator links it B1–B7
monetize-admin nordisk/monetize crates/monetize-admin open the ONE box: PID 1 runs server and ui premade (the operator lays it) B1–B10
monetize-server nordisk/monetize crates/monetize-server open the core: the doors (Signup, EstateOrder, Purchase, Fleet, the callbacks), the ledger, the estate state machine, custody, mail premade B1–B10
monetize-shop-pairing nordisk/monetize crates/monetize-shop-pairing open the shop's installer: pairs a laid estate once, runs the sale check premade B3, B4, B10
monetize-cloud nordisk/monetize crates/monetize-cloud open Cloud (nine ops) and ask, DnsWriter; the open clouds server and sandbox premade B3, B4, B6, B8b, B9, B10
monetize-pay nordisk/monetize crates/monetize-pay open Payment, Vendor; the open vendors invoice and sandbox premade B1–B8, B10
monetize-product-wasm, monetize-cloud-wasm nordisk/monetize crates/monetize-product-wasm, crates/monetize-cloud-wasm open the guest hosts: load a signed .wasm, check its signature, run it under fuel, deadline and memory caps premade B1–B10
mock-upcloud, mock-scaleway nordisk/monetize crates/mock-upcloud, crates/mock-scaleway open the clouds' APIs, faked, for tests premade (tests) B3, B4, B6, B9, B10 in tests
lake-product, the Lake's product operator nordisk/monetize crates/lake-product, guests/lake open the reference product: a parametric operator, its box side and its bench the integrator's example B3, B6, B9, B10
gunnar-monetize-guest nordisk/gunnar crates/gunnar-monetize-guest open (Gunnar) Gunnar's product operator the integrator (Gunnar) B1–B10
holger-monetize-guest nordisk/holger crates/holger-monetize-guest open (Holger) Holger's product operator the integrator (Holger) B1–B10
monetize-guest-stripe, -klarna, -swish monetize-impl guests/stripe, guests/klarna, guests/swish private the payment guests (Klarna and Swish: stub, lowest priority) premade B1–B8, B10
monetize-guest-upcloud, -scaleway monetize-impl guests/upcloud, guests/scaleway private the cloud guests (Scaleway: stub, lowest priority) premade B3, B4, B6, B8b, B9, B10
monetize-guest-dns-cloudflare monetize-impl guests/dns-cloudflare private the DNS guest premade B3, B4, B9, B10
monetize-pay-impl monetize-impl crates/monetize-pay-impl (examples/fake_stripe_serve.rs, fake_klarna_serve.rs, fake_swish_serve.rs) private the paid vendors' native impls and their fakes for tests premade (tests) B2–B8 in tests
monetize-impl-build-thing monetize-impl crates/monetize-impl-build-thing private the Build Thing: builds and signs every guest under the plugin root the ONE box trusts premade (the release) — (ships the plugins)

Sources: MONETIZE-V2.0-PLAN.md (§2 decisions, §3 traits, §6 phases); A24 monetize-small-trait-design (§2.2 Product, §2.3 Estate, §2.4 BuySite, §4.1-4.3 the three guests, §8.2 the fast test); A25 p2-cut-list §2 (the 28 → 6 mapping); A21 (what runs on the ONE box); the STE manual integration-guide.md (today's names). The data model: monetize-server (estate_order.rs, custody.rs, signup.rs, signup_pull.rs, config.rs), monetize/src/ledger.rs, monetize-guardian/src/store.rs, monetize-proto (accounts.proto, site_book.proto, estate_order.proto), monetize-site-buy/src/site.rs, and Gunnar's gunnar-core (model.rs, auth.rs) and gunnar-server (control_plane.rs, fed_principals.rs, repair.rs).