spend like a wallet, keep like a vault

Top-ups, bonuses, loyalty and referral credit land in separate buckets on one ledger — each with its own rules about spending, expiry, and whether it can ever be withdrawn.

withdrawable

cash

Real money the user put in. The only kind that can ever leave again — and the only kind a withdrawal can draw on.

locked · expires

bonus

Promotional credit with conditions still outstanding. Spends first, never cashes out, and disappears on its own schedule.

earned

loyalty

Value the user earned rather than bought. Its own expiry, its own spend priority, its own rules.

granted

referral

Credit granted for bringing someone in. A module the customer didn't buy has no pod, no schema, no consumer group.

a portfolio project, built like something a bank would buy

Nothing here is a demo shortcut. The decisions are argued in full, the contradictions between them are tracked with IDs, and the part that decides money is under test.

single-tenant

One Kubernetes cluster per customer. Isolation is the cluster boundary, not a query filter.

modular by contract

Seven sellable modules are line items. Unsold means not deployed, not a disabled flag.

running, not described

This page is served from the cluster it describes — RKE2, Argo CD, Gateway API, automatic certificates, secrets in OpenBao.

open about gaps

Twenty-five unresolved items tracked in public, four of them blocking.

the stack, layer by layer

Hover any tile for why it's there. running on this cluster codebuilt, not deployed planneddecided, not built

frontend

what you're looking at

backend

java services and what they share

data & events

where money is recorded

security

secrets, certificates, identity

infrastructure

the machine and the cluster on it

devops

build, test, publish

gitops

git is the only way anything changes

observability

decided, not built yet
check-and-reserveappend-only journaltyped money single-tenantevent-driven modulesexpand–contract migrations immutable image tagsblue-green deploysper-cluster GitOps RFC 9457 problem detailsschema registryseven-year retention

an overdraft isn't a race — it's a row the database refuses

The first decision record specified Redis locking. Reviewed before implementation, the lock turned out to guard a balance read — while the write happened asynchronously, after the lock was released.

trequest arequest bledger
1 msreads balance: €100€100
2 ms€100 ≥ €60 → approve€100
4 msreleases lock€100
51 msreads balance: €100€100
52 ms€100 ≥ €60 → approve€100
~200 msledger consumes a€40
~250 msledger consumes b−€20
CONSTRAINT account_not_overdrawn
  CHECK (posted_balance - held_total >= 0)
cash in → grants drip outclick to top up

try to overdraw it

  • cash€100.00
  • bonus€40.00
  • loyalty€12.50
held€0.00
withdrawable€100.00

No holds. The full cash balance is available.

built in the open, including the gaps

13
decision records, one superseded
18
ledger tests against real Postgres
25
open items tracked publicly
128
module subsets, all valid

get in touch

Questions about the architecture, the licence, or the product are all welcome.