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.ensurereturnsOnHand | Provisioned; only then iscapturecalled. A failure callsrelease. There is no path fromreservetocapturethat skipsensure. - Rule 2 — enforced at the PROVIDER, by construction. Every disk and server
monetize-cloudcreates carries the labelmonetize_ref = <the order's reference>plusmonetize_res(a fingerprint of the resource).ensurelabel-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.rsis append-only with an append grammar. Every derived number refolds it:total_provisioned_minorrecomputes the spend at every start, so<cloud>-spend.jsonis a cache a lost disk cannot use to reopen the budget.upcloud-pool.jsonis 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 privatevetra-automation/monetize-impland plug in throughmonetize_pay::factory/monetize_cloud::factory. The registry names none of them (a test greps its own source for the names). A deployment withvendors [], 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 theReservedrow, and margin is a fold, never a stored field (rule 3). - Rule 5 — gunnar's
UsageandAdmin.Capacityare open source and always on, not behindmetered; 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.
- Show the transaction, not a balance. An attempt is
Reserved → Ensured → Capturedor 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. - 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.
- 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".
- Distinguish sold from bought.
capsare what the tenant is allowed (the order);resourcesare 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. - A refusal must name what is missing and what to do.
GROWTH_SEALEDmeans 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". - Colour carries the
Enforcementword, not decoration.Enforcedis a wall and escalates toward it;Measuredis billed and never blocks, so it must not alarm at 95 %;Provisionedis 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.