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

nordisk/monetize

docs/v2/06-data-model.md

6. The data model

From monetize main c9b473a5. Field lists are verbatim from the structs they cite.

6.1 The five rules the model rests on

These are the rules the code is built to (they were first written down as "the base model" in 2026-09; the code still names them by number in its comments):

# the rule what it forbids where it is real
1 reserve → ensure → capture. Lock the money, buy the iron, then draw; a failure at any point releases charging for what was not delivered crates/monetize/src/transaction.rs:1-50
2 No balance. Every byte bought is attached to one customer's order buying ahead, inventory, credit, "paid, not yet provisioned" the reference <product>/<tenant>/<date>+<caps> on every row; Resource labelled monetize_ref at the cloud
3 The ledger is the only state, append-only, written BEFORE every external call any number that cannot be rebuilt from the rows ledger.rs:79-88; Ledger::attempt
4 Three seams: who takes money (Payment), who sells iron (Cloud), who counts usage (the product's report) vendor or product logic in the engine chapter 3
5 The product counts, monetize decides. The product measures and enforces at its one choke point; monetize owns the money and measures nothing monetize measuring; the product knowing about money Pulled answers every usage verb from the main's report (monetize-product/src/pulled.rs:141-143)

Rule 2 is the one that kills ideas: is every byte we bought owned by a specific order? If not, it does not ship.

6.2 The stores on the box

All under <data>/monetize/ unless said (crates/monetize-server/src/config.rs:1672-1700):

store holds written by
ledger.redb tables steps (reference, seq) → Row, open, entitlements (product, tenant) → Fact, recent, pushes the transaction, the estate orders, the nonpay engine (crates/monetize/src/ledger.rs:1-36)
usage.redb the tenants' meters as reported the fleet reports
signups.redb tables signups, refunds, minted, card_checks, welcomes, name_holds the signup engine (signup.rs:56-84)
callbacks.redb, reconcile.redb vendor deliveries seen; reconcile cursors the webhook; the reconcile job
fleet.redb the Guardian store: estates, keys (pinned box keys), boxes, orders, counters (the sequences), auto_fix, cases, holds the fleet door, the Guardian (crates/monetize-guardian/src/store.rs:16-24)
estate-orders/<company>/run.json, journal, laid.json, grow-<role>.journal one private estate's run, its cloud journal, what was laid the estate driver
nonpay.journal the nonpay engine's steps nonpay.rs:74
signing.key the Ed25519 seed (32 bytes) every fact and order is signed with; one derived key per product minted once at first start (crates/monetize/src/sign.rs; monetize-front/src/intake.rs:1105-1145)
<name>-spend.json, <name>-guest-state.json, signup-guest-state.<product>.json a cloud's spend cap state, a cloud guest's state, a signup guest's state the hosts
attach/tokens.json, data/joined/<estate>.json one-shot attach tokens; enrolled estates the attach door
test-purchases.json the console's test buys test_purchase.rs:36
<data>/monetize-ui/state.redb the console's passkeys and sessions the console (monetize-admin/src/lib.rs:28)
<data>/money/lines.log every money line PID 1 saw, in its own home money_log.rs
<data>/solo/door-feed.json, door-feed.taken, door-feed.next.json the feed in force, its receipt, the staged next feed the door, PID 1

Backup: the ledger backup is one deterministic bundle (MFB1 magic; the manifest, ledger.jsonl, entitlements.jsonl, pushes.jsonl, usage.jsonl, signups.jsonl, refunds.jsonl, passes.jsonl, nonpay.jsonl, secret/signing.key, the identities), named snapshot-<unix>-<sha256>.mfb, made through Ledger.Export over loopback (crates/monetize-front/src/ledger_backup.rs). The off-box copy seals the export, the signing key, the custody and the journals with age to the operator's X25519 recipient every 360 min and PUTs it to the object store (monetize-admin/src/offsite.rs:1-28).

6.3 The ledger rows

Step kinds (crates/monetize/src/ledger.rs:89-352): Reserved { reference, handle, purchase }, Declined, Ensured, Destroyed, Captured, Settled, Released, EntitlementPushed { fact }, Reclaimed, Grown, RefundRequested, Reversed, TenantDeleted, UsersGranted, RoomTakenBack, Instance { reference, product, tenant, company, host, phase, monthly, paid_until_unix_ms, vendor, operator_test, note }.

The grammar of one attempt (:79-88):

Reserved (Ensured | Destroyed)* ( Captured ( EntitlementPushed | Destroyed* Released ) | Released )
Declined stands alone

The fold: Attempt { handle, purchase, ensured, captured, settlement, closed } with Closed::{ Released { reason }, Pushed { fact } } (:402-434). A Sale (:1727-1820) is the customer-facing fold: reference, product, tenant, price, cost, fee, vendor_tax, vendor_tax_basis_points, market, vat_basis_points, vendor, livemode, cloud, caps, delta, started_unix_ms, at_unix_ms, steps, drawn, outcome, refunded_minor, lost_minor, dispute, customer_tax_id, customer_country, granted_unix_ms, back_steps, with SaleOutcome::{ Granted, Refunded { reason, drawn }, Declined { reason }, Open }.

6.4 The purchase and the plan

pub struct Purchase { product, tenant, caps, delta, resources, price, market, cost,
                      vat_basis_points, policy, starts_unix_ms, payer, absorb, users, plan }   // transaction.rs:91-155
pub struct Order { product, tenant, caps }                                                  // order.rs:40-45
pub enum Period { Month, Year }
pub struct PlanTerms { verb, period, size_bytes, month_price, accounts, cost_month, until_unix_ms, credit }   // plan.rs:81-104
pub struct PlanSpec { size_bytes, period, target }
pub struct Policy { period, grace_days: 14, retention_days: 90 }                              // order.rs:99-117

The account's state is derived, never stored (order.rs:131-146): Free; else Paid until paid_until; then Grace; then Suspended; then Retention.

6.5 The signed entitlement fact

What a product holds about a tenant, verified offline with the public half it was given at enrolment (monetize_pubkey), on one atomic load and a map lookup; no network call on the request path (crates/monetize-product/src/lib.rs:631-692; crates/monetize-embed/src/signing.rs:111-166):

pub struct EntitlementFact {
    pub tenant: TenantId,
    pub plan: String,                      // the order reference
    pub state: State,                      // Free | Paid | Grace | Suspended | Retention
    pub paid_until_unix_ms: Option<u64>,
    pub caps: BTreeMap<String, u64>,       // meter → cap
    pub source: String,                    // "operator" | "payment:<vendor>:<reference>" | "nonpay:deleted"
    pub signature: Vec<u8>,                // V1: Ed25519 over the canonical JSON of the fields above
    pub issued_unix_ms: Option<u64>,
    pub issued_signature: Vec<u8>,         // V2: the same, with `issued` inside
}

The product refuses a fact not newer than the one it holds, a lone issued time, and a lone issued signature. It arrives as the entitlement order on the pull channel; the newest per tenant wins.

6.6 The estate run

EstateRun (crates/monetize-server/src/estate_order.rs:849-1110) is one private estate: the reference, the company, the host, the account, the quote (sizes, twin_plan, the price), the phase and its history, laid (what the cloud answered, per role), release_of per role, dns, paid_until, captured_at_ms, growth (the signed EstateGrowth), reminded (<paid_until>:<kind>:<day> per mail), stopped, new_account (the account a visitor's order makes, once, by its session), the vendor handle, operator_test, the spend cap fixed at its order, and the custody's sealed material. The Instance ledger row mirrors its phase, monthly price and paid_until.

6.7 The account records

pub struct PaidSignup { vendor, session, reference, email, username, product, plan, amount: Money, tax: Money, paid_at_unix_ms, customer_country }   // monetize-pay/src/signup.rs:43-102
pub struct SignupRecord { signup: PaidSignup, state: SignupState, recorded_at_unix_ms, estate, card }                                           // signup.rs:535-554
pub enum SignupState { Pending, Created { namespace, username, at_unix_ms }, Refused { reason, at_unix_ms, words, … } }
pub struct NewAccount { product, plan, currency, username, email, welcome_url, session, estate, site, back_to, cancel_to, test_payer }           // estate_order/account.rs:185-216
pub struct SignupPlan { amount_minor, currency, cap_bytes, period_days }                                                                      // config.rs:655-666

A free account plan is one with amount_minor = 0 (signup.rs:462-464). A name is held for 35 min while a page runs (NAME_HOLD_MS); a pending signup is forgotten after 24 h (PENDING_CEILING_MS).

6.8 The fleet store and the pinned keys

Per estate the Guardian store keeps (crates/monetize-guardian/src/store.rs): the estate record (product, instance, the laid roles), one pinned box key per role (the Ed25519 public key the box first presented inside its pin window), the box record (last heard, health, build), the open orders (SignedOrder until acked), the per-instance sequence counter, the auto-fix switch, the cases, and the holds. forget_box removes the key, the record and the pin window (:248-264). An order is SignedOrder { seq, product, instance, role, kind, body (JSON), issued_unix_ms, not_after_unix_ms, signature } (crates/monetize-embed/src/fleet.rs:142-154), signed under the domain nordisk-order-v1.

6.9 What signs what

signer key signs verified by
the ONE box signing.key, derived per product entitlement facts, orders, EstateGrowth, the attach CA's issue the product (offline), every box (monetize_pubkey)
a box its fleet key <data>/fleet/box.ed25519 Fleet.Report/Orders/Ask the ONE box against the pinned key
a front the estate client key Fleet.Ask as the front the ONE box
the release manager plugin-signing.pk8 (CMS, chained to the plugin root) every plugin .p7s and the plugin list the box against guest_trust_roots
the korp root the root seed on the operator's machine the box's media and the live app korp at boot and at stage_on against the enrolled root
the vendor its webhook secret webhook deliveries the host's hmac door