Gunnar

nordisk/monetize

BASE-MODEL.md

<!-- nornir:class: law -->
# The base model — monetize

**Five rules. Everything in this repository serves them, and the server and the
console must both REFLECT them, not merely obey them.** Settled 2026-09-05.

Read this before changing anything. A change that cannot be justified against a
rule below does not ship, however clever it is.

---

## The five rules

| # | The rule | What it forbids |
|---|---|---|
| **1** | **reserve → ensure → capture.** Lock the money, buy the iron, *then* draw. Failure at any point releases. | Charging for something you could not deliver. |
| **2** | **No balance.** Every byte bought is attached to one customer's order. | Buying ahead. Inventory. Credit. "Paid, not yet provisioned." |
| **3** | **The ledger is the only state**, append-only, written *before* every external call. | Any number you cannot rebuild by re-reading the rows. A crash losing the truth. |
| **4** | **Three seams** — who takes money (`Payment`, ten ops), who sells iron (`Cloud`), who counts usage (`Product`). | Vendor or product logic leaking into the engine. |
| **5** | **The product counts, monetize decides.** gunnar measures and enforces at one choke point; monetize owns the money. | monetize measuring anything. gunnar knowing about money. |

**Rule 2 is the one that kills ideas.** Run it against anything new: *is every
byte we bought owned by a specific order?* If no — it does not ship. It is why a
buy-ahead pool is forbidden, and why that was scrapped on 2026-09-05 after being
proposed.

---

## How each rule is made REAL (not just stated)

Stating a rule is worth nothing; these are the mechanisms that enforce them, and
they are what a change must not break.

* **Rule 1** — `transaction.rs`. `ensure` returns `OnHand | Provisioned`; only
  then is `capture` called. A failure calls `release`. There is no path from
  `reserve` to `capture` that skips `ensure`.
* **Rule 2 — enforced at the PROVIDER, by construction.** Every disk and server
  `monetize-cloud` creates carries the label **`monetize_ref = <the order's
  reference>`** plus `monetize_res` (a fingerprint of the resource). `ensure`
  label-searches before it buys. A byte with no order cannot exist, because
  nothing creates one without stamping the order on it. This is why rule 2 holds
  even across a crash: the truth is on the provider, not in our memory.
* **Rule 3** — `ledger.rs` is append-only with an append *grammar*. Every
  derived number refolds it: `total_provisioned_minor` recomputes the spend at
  every start, so `<cloud>-spend.json` is a **cache a lost disk cannot use to
  reopen the budget**. `upcloud-pool.json` is likewise an idempotency cache; the
  label search is authoritative.
* **Rule 4** — `monetize-pay`, `monetize-cloud`, `monetize-product`. Each vendor
  and cloud sits behind a cargo feature; a vendor that is not compiled cannot be
  reached by accident, and one that is configured but not compiled is **refused
  by name at boot** rather than starting unable to bill. **Since 2026-09-05 the
  seams are also the open/paid line**: this repo holds the traits, the engine,
  the server, the console and the impls nobody needs a contract for (`server` —
  the box's own iron, `sandbox`, `invoice`); klarna, stripe, swish, upcloud and
  scaleway live in the private `vetra-automation/monetize-impl` and plug in
  through `monetize_pay::factory` / `monetize_cloud::factory`. The registry
  names none of them (a test greps its own source for the names). A deployment
  with `vendors []`, no `[prices]` and `[clouds.server]` is a valid one —
  *"people sharing their servers for free"* — every order is free, and the
  transaction runs for it with zero vendor calls.
* **Rule 2's unit is the ORDER** (2026-09-05, when the plan catalogue was
  removed): `{product, tenant, caps}`, nothing else — no catalogue, no
  rung. What the customer pays is a **price list per product per market, per
  resource unit** (`[prices.gunnar.SE] disk_gib_month = 2200`), applied to the
  DELTA over what the tenant already holds; absent or zero is free. What the
  provider charges us is measured (`Priced::Resource`). Both go on the
  `Reserved` row, and **margin is a fold, never a stored field** (rule 3).
* **Rule 5** — gunnar's `Usage` and `Admin.Capacity` are open source and always
  on, not behind `metered`; a self-hoster gets the numbers. monetize never
  measures — it asks. The product-side check is one atomic load and a map
  lookup, never a network call.

---

## What this means for the CONSOLE, concretely

The console is where the model is either visible or lost. It must show the
model, not a prettier lie about it.

1. **Show the transaction, not a balance.** An attempt is `Reserved → Ensured →
   Captured` or it is released. There is no "credit", no "account balance", no
   "topped up". The Ledger page leads with **open attempts** because money
   reserved and not yet captured or released is the row an operator must see.
2. **Never render a number the ledger cannot rebuild.** If a figure on screen
   cannot be recomputed by folding rows, it is a lie waiting to happen.
3. **Say what is measured and what is not.** A meter with no reading is not
   zero. A cap with no number is *uncapped*, which is its own state — never an
   empty bar and never a full one. An unanswered capacity question is **"a
   measurement that did not happen", not "a full disk"**.
4. **Distinguish sold from bought.** `caps` are what the tenant is *allowed*
   (the order); `resources` are what monetize *bought* for it (the delta's
   iron); the box's own pool is capacity that was there before monetize
   existed. BACKED BY shows the second, distinct from the first, and a quote
   shows the PRICE (the list's) beside the COST (the provider's), because two
   orders identical in caps can differ entirely in what backs them and in what
   they cost to serve.
5. **A refusal must name what is missing and what to do.** `GROWTH_SEALED` means
   attaching a disk is money for nothing — the console must say "a fresh install
   onto a larger set", never let an operator read it as "buy more disk".
6. **Colour carries the `Enforcement` word, not decoration.** `Enforced` is a
   wall and escalates toward it; `Measured` is billed and never blocks, so it
   must not alarm at 95 %; `Provisioned` is bought iron the kernel enforces.

## What the console must NEVER grow

* A "top up" or "add credit" affordance — rule 2.
* A number with no ledger row behind it — rule 3.
* Any measurement of its own — rule 5. It renders what gunnar counted.
* A projection stated as a date without its uncertainty. Git storage is bursty;
  a linear fit produces a confident wrong day and someone will plan a re-image
  around it.

---

## The standing test for any new idea

> **Is every byte we bought owned by a specific order?**
> **Can every number on screen be rebuilt by folding the ledger?**
> **Did monetize measure anything itself?**

Two yeses and a no, or it does not ship.