Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@
# The server records what it is told here -- it is an audit stamp, not an
# optimistic-concurrency check (that is `UpdateRule`'s `expectedVersion`).
#
# Wire shapes: RuleTrigger::DescriptionContains is 0 and RuleAction::
# SetCategory is 0. CreateRule, CreateCategory, CreateBudget and SetBudgetLimit
# Wire shapes: enums travel as their enumerator names, so RuleTrigger is
# "DescriptionContains" and RuleAction is "SetCategory" -- each has one
# enumerator today, and neither is an integer on the wire.
# CreateRule, CreateCategory, CreateBudget and SetBudgetLimit
# each answer with a bare id, so their captures read `$`.
#
# ── How this file stays re-runnable ─────────────────────────────────────
Expand All @@ -53,10 +55,10 @@ client books model=LedgerModel
session principal=$who token=$token

# The nonce: the first account's own id, which no earlier run had.
do OpenAccount ledgerId=1 name="Ruled cash" kind=0 currency=1
do OpenAccount ledgerId=1 name="Ruled cash" kind="Asset" currency="EUR"
expect ok capture cash=$.id

do OpenAccount ledgerId=1 name="Ruled spend" kind=1 currency=1
do OpenAccount ledgerId=1 name="Ruled spend" kind="Expense" currency="EUR"
expect ok capture spend=$.id

client budgets model=BudgetModel
Expand All @@ -71,7 +73,7 @@ expect ok capture household=$
client rules model=RuleModel
session principal=$who token=$token

do CreateRule ledgerId=1 trigger=0 matchText="SUPERMARKET-$cash" action=0 actionValue="Ruled household $cash"
do CreateRule ledgerId=1 trigger="DescriptionContains" matchText="SUPERMARKET-$cash" action="SetCategory" actionValue="Ruled household $cash"
expect ok capture rule=$

# ── Two budgets, so both sides of the move are observable ────────────────
Expand All @@ -83,10 +85,10 @@ expect ok capture groceryBudget=$
do CreateBudget ledgerId=1 name="Ruled household budget $cash" categoryId=$household
expect ok capture householdBudget=$

do SetBudgetLimit budgetId=$groceryBudget month="2026-08" limit={"num":100000,"den":1,"dp":2} currency=1
do SetBudgetLimit budgetId=$groceryBudget month="2026-08" limit={"num":100000,"den":1,"dp":2} currency="EUR"
expect ok

do SetBudgetLimit budgetId=$householdBudget month="2026-08" limit={"num":100000,"den":1,"dp":2} currency=1
do SetBudgetLimit budgetId=$householdBudget month="2026-08" limit={"num":100000,"den":1,"dp":2} currency="EUR"
expect ok

# ── The account starts on groceries, stamped with the rule that put it there
Expand All @@ -97,7 +99,7 @@ expect ok

# ── A transaction the rule does *not* match ─────────────────────────────
do StoreTransaction ledgerId=1 description="corner bakery" date="2026-08-01T00:00:00.000Z" legs=[{"accountId":$cash,"amount":{"num":-1000,"den":1,"dp":2}},{"accountId":$spend,"amount":{"num":1000,"den":1,"dp":2}}]
expect ok field accounts ~ "\"id\":$spend,\"name\":\"Ruled spend\",\"kind\":1,\"currency\":1,\"balance\":.\"num\":1000"
expect ok field accounts ~ "\"id\":$spend,\"name\":\"Ruled spend\",\"kind\":\"Expense\",\"currency\":\"EUR\",\"balance\":.\"num\":1000"

use budgets

Expand All @@ -111,7 +113,7 @@ expect ok field spent.num == 0
use books

do StoreTransaction ledgerId=1 description="SUPERMARKET-$cash run" date="2026-08-02T00:00:00.000Z" legs=[{"accountId":$cash,"amount":{"num":-2000,"den":1,"dp":2}},{"accountId":$spend,"amount":{"num":2000,"den":1,"dp":2}}]
expect ok field accounts ~ "\"id\":$spend,\"name\":\"Ruled spend\",\"kind\":1,\"currency\":1,\"balance\":.\"num\":3000"
expect ok field accounts ~ "\"id\":$spend,\"name\":\"Ruled spend\",\"kind\":\"Expense\",\"currency\":\"EUR\",\"balance\":.\"num\":3000"

use budgets

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ session principal=$bob token=$bobToken
# ── She opens the accounts ─────────────────────────────────────────────
use hers

do OpenAccount ledgerId=1 name="Shared book cash" kind=0 currency=1
do OpenAccount ledgerId=1 name="Shared book cash" kind="Asset" currency="EUR"
expect ok capture cash=$.id
expect ok field balance.num == 0

do OpenAccount ledgerId=1 name="Shared book spend" kind=1 currency=1
do OpenAccount ledgerId=1 name="Shared book spend" kind="Expense" currency="EUR"
expect ok capture spend=$.id

# ── He sees them, on his own connection ────────────────────────────────
Expand All @@ -45,50 +45,50 @@ expect ok capture spend=$.id
use his

do GetLedger ledgerId=1
expect ok field accounts ~ "\"id\":$cash,\"name\":\"Shared book cash\",\"kind\":0,\"currency\":1,\"balance\":.\"num\":0"
expect ok field accounts ~ "\"id\":$spend,\"name\":\"Shared book spend\",\"kind\":1,\"currency\":1,\"balance\":.\"num\":0"
expect ok field accounts ~ "\"id\":$cash,\"name\":\"Shared book cash\",\"kind\":\"Asset\",\"currency\":\"EUR\",\"balance\":.\"num\":0"
expect ok field accounts ~ "\"id\":$spend,\"name\":\"Shared book spend\",\"kind\":\"Expense\",\"currency\":\"EUR\",\"balance\":.\"num\":0"

# ── He posts ───────────────────────────────────────────────────────────
do StoreTransaction ledgerId=1 description="his entry" date="2026-08-01T00:00:00.000Z" legs=[{"accountId":$cash,"amount":{"num":-2000,"den":1,"dp":2}},{"accountId":$spend,"amount":{"num":2000,"den":1,"dp":2}}]
expect ok field accounts ~ "\"id\":$cash,\"name\":\"Shared book cash\",\"kind\":0,\"currency\":1,\"balance\":.\"num\":-2000"
expect ok field accounts ~ "\"id\":$cash,\"name\":\"Shared book cash\",\"kind\":\"Asset\",\"currency\":\"EUR\",\"balance\":.\"num\":-2000"

# ── ...and she sees it without reopening anything ─────────────────────
use hers

do GetLedger ledgerId=1
expect ok field accounts ~ "\"id\":$cash,\"name\":\"Shared book cash\",\"kind\":0,\"currency\":1,\"balance\":.\"num\":-2000"
expect ok field accounts ~ "\"id\":$cash,\"name\":\"Shared book cash\",\"kind\":\"Asset\",\"currency\":\"EUR\",\"balance\":.\"num\":-2000"

# ── She posts on top of his ───────────────────────────────────────────
do StoreTransaction ledgerId=1 description="her entry" date="2026-08-02T00:00:00.000Z" legs=[{"accountId":$cash,"amount":{"num":-500,"den":1,"dp":2}},{"accountId":$spend,"amount":{"num":500,"den":1,"dp":2}}]
expect ok field accounts ~ "\"id\":$cash,\"name\":\"Shared book cash\",\"kind\":0,\"currency\":1,\"balance\":.\"num\":-2500"
expect ok field accounts ~ "\"id\":$cash,\"name\":\"Shared book cash\",\"kind\":\"Asset\",\"currency\":\"EUR\",\"balance\":.\"num\":-2500"

use his

do GetLedger ledgerId=1
expect ok field accounts ~ "\"id\":$cash,\"name\":\"Shared book cash\",\"kind\":0,\"currency\":1,\"balance\":.\"num\":-2500"
expect ok field accounts ~ "\"id\":$spend,\"name\":\"Shared book spend\",\"kind\":1,\"currency\":1,\"balance\":.\"num\":2500"
expect ok field accounts ~ "\"id\":$cash,\"name\":\"Shared book cash\",\"kind\":\"Asset\",\"currency\":\"EUR\",\"balance\":.\"num\":-2500"
expect ok field accounts ~ "\"id\":$spend,\"name\":\"Shared book spend\",\"kind\":\"Expense\",\"currency\":\"EUR\",\"balance\":.\"num\":2500"

# ── An account he opens is hers to post against ───────────────────────
do OpenAccount ledgerId=1 name="Shared book third" kind=1 currency=1
do OpenAccount ledgerId=1 name="Shared book third" kind="Expense" currency="EUR"
expect ok capture third=$.id

use hers

do StoreTransaction ledgerId=1 description="against his account" date="2026-08-03T00:00:00.000Z" legs=[{"accountId":$cash,"amount":{"num":-750,"den":1,"dp":2}},{"accountId":$third,"amount":{"num":750,"den":1,"dp":2}}]
expect ok field accounts ~ "\"id\":$third,\"name\":\"Shared book third\",\"kind\":1,\"currency\":1,\"balance\":.\"num\":750"
expect ok field accounts ~ "\"id\":$cash,\"name\":\"Shared book cash\",\"kind\":0,\"currency\":1,\"balance\":.\"num\":-3250"
expect ok field accounts ~ "\"id\":$third,\"name\":\"Shared book third\",\"kind\":\"Expense\",\"currency\":\"EUR\",\"balance\":.\"num\":750"
expect ok field accounts ~ "\"id\":$cash,\"name\":\"Shared book cash\",\"kind\":\"Asset\",\"currency\":\"EUR\",\"balance\":.\"num\":-3250"

# ── An exactly-once key is scoped to the ledger, not to the connection ─
# She spends the key; his identical send is the retry, and it must not post.
do StoreTransaction ledgerId=1 description="keyed by her" date="2026-08-04T00:00:00.000Z" opId="shared-$cash" legs=[{"accountId":$cash,"amount":{"num":-100,"den":1,"dp":2}},{"accountId":$spend,"amount":{"num":100,"den":1,"dp":2}}]
expect ok field accounts ~ "\"id\":$cash,\"name\":\"Shared book cash\",\"kind\":0,\"currency\":1,\"balance\":.\"num\":-3350"
expect ok field accounts ~ "\"id\":$cash,\"name\":\"Shared book cash\",\"kind\":\"Asset\",\"currency\":\"EUR\",\"balance\":.\"num\":-3350"

use his

do StoreTransaction ledgerId=1 description="keyed by her" date="2026-08-04T00:00:00.000Z" opId="shared-$cash" legs=[{"accountId":$cash,"amount":{"num":-100,"den":1,"dp":2}},{"accountId":$spend,"amount":{"num":100,"den":1,"dp":2}}]
expect ok field accounts ~ "\"id\":$cash,\"name\":\"Shared book cash\",\"kind\":0,\"currency\":1,\"balance\":.\"num\":-3350"
expect ok field accounts ~ "\"id\":$cash,\"name\":\"Shared book cash\",\"kind\":\"Asset\",\"currency\":\"EUR\",\"balance\":.\"num\":-3350"

use hers

do GetLedger ledgerId=1
expect ok field accounts ~ "\"id\":$cash,\"name\":\"Shared book cash\",\"kind\":0,\"currency\":1,\"balance\":.\"num\":-3350"
expect ok field accounts ~ "\"id\":$cash,\"name\":\"Shared book cash\",\"kind\":\"Asset\",\"currency\":\"EUR\",\"balance\":.\"num\":-3350"
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ expect ok field principal == accountant
# ── No session at all: every model refuses ─────────────────────────────
client books model=LedgerModel

do OpenAccount ledgerId=1 name="Authorised cash" kind=0 currency=1
do OpenAccount ledgerId=1 name="Authorised cash" kind="Asset" currency="EUR"
expect err message == "unauthorized"

client budgets model=BudgetModel
Expand All @@ -47,27 +47,27 @@ expect err message == "unauthorized"

client rules model=RuleModel

do CreateRule ledgerId=1 trigger=0 matchText="anything" action=0 actionValue="Authorised category"
do CreateRule ledgerId=1 trigger="DescriptionContains" matchText="anything" action="SetCategory" actionValue="Authorised category"
expect err message == "unauthorized"

# ── A token shaped like one, signed by nobody ──────────────────────────
use books
session principal=accountant token="eyJwcmluY2lwYWwiOiJhY2NvdW50YW50In0.not-a-real-signature"

do OpenAccount ledgerId=1 name="Authorised cash" kind=0 currency=1
do OpenAccount ledgerId=1 name="Authorised cash" kind="Asset" currency="EUR"
expect err message == "unauthorized"

# ── The real one ───────────────────────────────────────────────────────
session principal=$who token=$token

do OpenAccount ledgerId=1 name="Authorised cash" kind=0 currency=1
do OpenAccount ledgerId=1 name="Authorised cash" kind="Asset" currency="EUR"
expect ok capture cash=$.id

do OpenAccount ledgerId=1 name="Authorised spend" kind=1 currency=1
do OpenAccount ledgerId=1 name="Authorised spend" kind="Expense" currency="EUR"
expect ok capture spend=$.id

do StoreTransaction ledgerId=1 description="authorised work" date="2026-08-01T00:00:00.000Z" legs=[{"accountId":$cash,"amount":{"num":-800,"den":1,"dp":2}},{"accountId":$spend,"amount":{"num":800,"den":1,"dp":2}}]
expect ok field accounts ~ "\"id\":$cash,\"name\":\"Authorised cash\",\"kind\":0,\"currency\":1,\"balance\":.\"num\":-800"
expect ok field accounts ~ "\"id\":$cash,\"name\":\"Authorised cash\",\"kind\":\"Asset\",\"currency\":\"EUR\",\"balance\":.\"num\":-800"

# ── The same token authorises the other models ────────────────────────
use budgets
Expand All @@ -82,7 +82,7 @@ expect ok
use rules
session principal=$who token=$token

do CreateRule ledgerId=1 trigger=0 matchText="authorised" action=0 actionValue="Authorised category"
do CreateRule ledgerId=1 trigger="DescriptionContains" matchText="authorised" action="SetCategory" actionValue="Authorised category"
expect ok capture rule=$

do UpdateRule ruleId=$rule matchText="authorised work" actionValue="Authorised category" expectedVersion=1
Expand All @@ -92,7 +92,7 @@ expect ok field version == 2
use books

do GetLedger ledgerId=1
expect ok field accounts ~ "\"id\":$spend,\"name\":\"Authorised spend\",\"kind\":1,\"currency\":1,\"balance\":.\"num\":800"
expect ok field accounts ~ "\"id\":$spend,\"name\":\"Authorised spend\",\"kind\":\"Expense\",\"currency\":\"EUR\",\"balance\":.\"num\":800"

# ── Dropping the session closes the doors again ───────────────────────
session principal="" token=""
Expand All @@ -107,4 +107,4 @@ expect err message == "unauthorized"
session principal=$who token=$token

do GetLedger ledgerId=1
expect ok field accounts ~ "\"id\":$cash,\"name\":\"Authorised cash\",\"kind\":0,\"currency\":1,\"balance\":.\"num\":-800"
expect ok field accounts ~ "\"id\":$cash,\"name\":\"Authorised cash\",\"kind\":\"Asset\",\"currency\":\"EUR\",\"balance\":.\"num\":-800"
40 changes: 21 additions & 19 deletions scripts/scenario/scenarios/ledger/accounts-of-every-kind.scenario
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
# for a liability, so nothing here assumes a sign convention the model does
# not have.
#
# Wire shapes: AccountKind::Asset is 0, ::Expense 1, ::Revenue 2,
# ::Liability 3; Currency::USD is 0 and ::EUR is 1. Both travel as integers.
# Wire shapes: both enums travel as their *enumerator names*, not as their
# underlying integers (morph#444 gave each a `glz::meta`/`glz::enumerate`) --
# AccountKind is "Asset"/"Expense"/"Revenue"/"Liability" and Currency is its
# ISO code, "USD"/"EUR"/"JPY"/"KRW".
#
# Re-runnable: every account is opened here, so the balances are this run's.

Expand All @@ -25,36 +27,36 @@ expect ok capture who=$.principal
client books model=LedgerModel
session principal=$who token=$token

do OpenAccount ledgerId=1 name="Kinds asset" kind=0 currency=1
do OpenAccount ledgerId=1 name="Kinds asset" kind="Asset" currency="EUR"
expect ok capture asset=$.id
expect ok field kind == 0
expect ok field kind == "Asset"
expect ok field balance.num == 0

do OpenAccount ledgerId=1 name="Kinds expense" kind=1 currency=1
do OpenAccount ledgerId=1 name="Kinds expense" kind="Expense" currency="EUR"
expect ok capture expense=$.id
expect ok field kind == 1
expect ok field kind == "Expense"

do OpenAccount ledgerId=1 name="Kinds revenue" kind=2 currency=1
do OpenAccount ledgerId=1 name="Kinds revenue" kind="Revenue" currency="EUR"
expect ok capture revenue=$.id
expect ok field kind == 2
expect ok field kind == "Revenue"

do OpenAccount ledgerId=1 name="Kinds liability" kind=3 currency=1
do OpenAccount ledgerId=1 name="Kinds liability" kind="Liability" currency="EUR"
expect ok capture liability=$.id
expect ok field kind == 3
expect ok field kind == "Liability"

# ── One entry across all four kinds ───────────────────────────────────────
# Salary arrives (revenue -> asset), rent is paid on credit (expense ->
# liability). Four legs, one currency, summing to zero.
do StoreTransaction ledgerId=1 description="salary in, rent on credit" date="2026-08-01T00:00:00.000Z" legs=[{"accountId":$asset,"amount":{"num":200000,"den":1,"dp":2}},{"accountId":$revenue,"amount":{"num":-200000,"den":1,"dp":2}},{"accountId":$expense,"amount":{"num":90000,"den":1,"dp":2}},{"accountId":$liability,"amount":{"num":-90000,"den":1,"dp":2}}]
expect ok field accounts ~ "\"id\":$asset,\"name\":\"Kinds asset\",\"kind\":0,\"currency\":1,\"balance\":.\"num\":200000"
expect ok field accounts ~ "\"id\":$expense,\"name\":\"Kinds expense\",\"kind\":1,\"currency\":1,\"balance\":.\"num\":90000"
expect ok field accounts ~ "\"id\":$revenue,\"name\":\"Kinds revenue\",\"kind\":2,\"currency\":1,\"balance\":.\"num\":-200000"
expect ok field accounts ~ "\"id\":$liability,\"name\":\"Kinds liability\",\"kind\":3,\"currency\":1,\"balance\":.\"num\":-90000"
expect ok field accounts ~ "\"id\":$asset,\"name\":\"Kinds asset\",\"kind\":\"Asset\",\"currency\":\"EUR\",\"balance\":.\"num\":200000"
expect ok field accounts ~ "\"id\":$expense,\"name\":\"Kinds expense\",\"kind\":\"Expense\",\"currency\":\"EUR\",\"balance\":.\"num\":90000"
expect ok field accounts ~ "\"id\":$revenue,\"name\":\"Kinds revenue\",\"kind\":\"Revenue\",\"currency\":\"EUR\",\"balance\":.\"num\":-200000"
expect ok field accounts ~ "\"id\":$liability,\"name\":\"Kinds liability\",\"kind\":\"Liability\",\"currency\":\"EUR\",\"balance\":.\"num\":-90000"

# The read agrees with the write's own reply.
do GetLedger ledgerId=1
expect ok field accounts ~ "\"id\":$asset,\"name\":\"Kinds asset\",\"kind\":0,\"currency\":1,\"balance\":.\"num\":200000"
expect ok field accounts ~ "\"id\":$liability,\"name\":\"Kinds liability\",\"kind\":3,\"currency\":1,\"balance\":.\"num\":-90000"
expect ok field accounts ~ "\"id\":$asset,\"name\":\"Kinds asset\",\"kind\":\"Asset\",\"currency\":\"EUR\",\"balance\":.\"num\":200000"
expect ok field accounts ~ "\"id\":$liability,\"name\":\"Kinds liability\",\"kind\":\"Liability\",\"currency\":\"EUR\",\"balance\":.\"num\":-90000"

# ── Kind does not exempt anything from the invariant ──────────────────────
# Two legs across two kinds that do not cancel. Two, not one: a single-leg
Expand All @@ -70,9 +72,9 @@ expect err message == "action failed validation: LedgerModel/StoreTransaction"
expect err message !~ "zero-sum"

do GetLedger ledgerId=1
expect ok field accounts ~ "\"id\":$revenue,\"name\":\"Kinds revenue\",\"kind\":2,\"currency\":1,\"balance\":.\"num\":-200000"
expect ok field accounts ~ "\"id\":$revenue,\"name\":\"Kinds revenue\",\"kind\":\"Revenue\",\"currency\":\"EUR\",\"balance\":.\"num\":-200000"

# ── Paying the liability down moves two kinds and still balances ──────────
do StoreTransaction ledgerId=1 description="pay the landlord" date="2026-08-03T00:00:00.000Z" legs=[{"accountId":$liability,"amount":{"num":90000,"den":1,"dp":2}},{"accountId":$asset,"amount":{"num":-90000,"den":1,"dp":2}}]
expect ok field accounts ~ "\"id\":$liability,\"name\":\"Kinds liability\",\"kind\":3,\"currency\":1,\"balance\":.\"num\":0"
expect ok field accounts ~ "\"id\":$asset,\"name\":\"Kinds asset\",\"kind\":0,\"currency\":1,\"balance\":.\"num\":110000"
expect ok field accounts ~ "\"id\":$liability,\"name\":\"Kinds liability\",\"kind\":\"Liability\",\"currency\":\"EUR\",\"balance\":.\"num\":0"
expect ok field accounts ~ "\"id\":$asset,\"name\":\"Kinds asset\",\"kind\":\"Asset\",\"currency\":\"EUR\",\"balance\":.\"num\":110000"
Loading
Loading