yichus 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -440,7 +440,7 @@ function renderService(map, W, opts) {
440
440
  // path that takes two lines lifts the row.
441
441
  const roleText = e => [...new Set(e.members.flatMap(m => m.roles))].map(r => dense && rolePrefix ? r.slice(rolePrefix.length) : r).join(', ');
442
442
  // the lines a fork's types take from x to end (none when not even a letter fits: then they are its tooltip)
443
- const forkLines = (types, room) => (!types.length || dense ? [] : room < tw(`${types.join(',')[0]}…`, fsS) ? [''] : wrapped(types.join(','), room, { size: fsS }));
443
+ const forkLines = (types, room) => (dense ? [] : forkTypeLines(types, room, fsS));
444
444
  const gapL = lineGap(fs);
445
445
  const titleSz = phone ? 9.5 : 10.5;
446
446
  // a hull's name over its first row, on as many lines as the gap between risers takes
@@ -1227,9 +1227,26 @@ function wallPathGaps(x0, x1, yTop, yBot, openings, r = 14) {
1227
1227
  // line; each package is a stretch starting with a tick and its name; the
1228
1228
  // parts' labels alternate above and below the line so it never wraps at
1229
1229
  // phone width. A package with many forks draws one fork with a count.
1230
+ // The lines a fork's types take in `room` at `size`: none for no types, one
1231
+ // empty line when not even a letter fits (the types are then its tooltip).
1232
+ // Lines break between types, so a long list keeps its lines, a type too
1233
+ // long for a line clipped where the line ends. Only a type broken mid-word
1234
+ // across lines (a line that ends inside a type) with the rest clipped puts
1235
+ // the types on one line, clipped.
1236
+ export function forkTypeLines(types, room, size) {
1237
+ const s = types.join(',');
1238
+ if (!types.length) return [];
1239
+ if (room < tw(`${s[0]}…`, size)) return [''];
1240
+ const ls = wrapped(s, room, { size });
1241
+ const brokenMidWord = ls.slice(0, -1).some(l => !l.endsWith(',')) && ls.slice(1).some(l => l.endsWith('…'));
1242
+ return brokenMidWord ? [clip(s, size, room)] : ls;
1243
+ }
1244
+
1230
1245
  // A gathered mark's count, said in words: what it counts, never a bare ×n.
1231
1246
  const SET_WORDS = { forkset: ['choice', 'choices'], dbset: ['db use', 'db uses'], genset: ['generic part', 'generic parts'], stepset: ['step', 'steps'] };
1232
1247
  export const setWord = (kind, n) => `${n} ${SET_WORDS[kind][n === 1 ? 0 : 1]}`;
1248
+ // a db use off every path whose handle is handed in on some paths only: a ? beside its station
1249
+ const strayUnknown = (x, y, phone) => mark.unknown(x + (phone ? 6 : 7.5), y - (phone ? 6 : 7.5), phone ? 3.6 : 4.4);
1233
1250
 
1234
1251
  export function quietItems(map, placed, { services = false, dense = false, phone = false, drawn = new Set(), socketFns = new Map(), genericById = new Map(), exportsLeft = [], strays = [] } = {}) {
1235
1252
  const pkgOf = id => String(id).split('::')[0];
@@ -1250,7 +1267,8 @@ export function quietItems(map, placed, { services = false, dense = false, phone
1250
1267
  for (const u of strays) { const p = pkgOf(u.definition); pk(p); if (!strayBy.has(p)) strayBy.set(p, []); strayBy.get(p).push(u); }
1251
1268
  for (const [p, us] of strayBy) {
1252
1269
  const defs = [...new Set(us.map(u => u.definition))];
1253
- P.get(p).parts.push({ kind: 'dbset', label: defs.length === 1 && !dense ? bare(defs[0]) : setWord('dbset', defs.length), n: defs.length, writes: us.some(u => u.writes), keys: us.map(u => `use:${u.i}`) });
1270
+ const mixed = us.filter(u => u.how === 'mixed');
1271
+ P.get(p).parts.push({ kind: 'dbset', label: defs.length === 1 && !dense ? bare(defs[0]) : setWord('dbset', defs.length), n: defs.length, writes: us.some(u => u.writes), mixed: mixed.length > 0, keys: [...us.map(u => `use:${u.i}`), ...mixed.map(u => `unknown:${u.i}`)] });
1254
1272
  }
1255
1273
  // forks (and bare generic parts) of one package gathered under one mark with a count when the line would be long
1256
1274
  const total = [...list.values()].reduce((n, l) => n + l.length, 0);
@@ -1277,7 +1295,7 @@ export function quietItems(map, placed, { services = false, dense = false, phone
1277
1295
  const forks = of('forkset'), dbs = of('dbset'), others = all.filter(it => !forks.includes(it) && !dbs.includes(it));
1278
1296
  const n = list => list.reduce((c, it) => c + (it.n || 1), 0);
1279
1297
  if (forks.length) parts.push({ kind: 'forkset', label: setWord('forkset', n(forks)), n: n(forks), fork: [...new Set(forks.flatMap(it => it.fork))], keys: forks.flatMap(it => it.keys) });
1280
- if (dbs.length) parts.push({ kind: 'dbset', label: setWord('dbset', n(dbs)), n: n(dbs), writes: dbs.some(it => it.writes), keys: dbs.flatMap(it => it.keys) });
1298
+ if (dbs.length) parts.push({ kind: 'dbset', label: setWord('dbset', n(dbs)), n: n(dbs), writes: dbs.some(it => it.writes), mixed: dbs.some(it => it.mixed), keys: dbs.flatMap(it => it.keys) });
1281
1299
  parts.push(...others);
1282
1300
  if (steps.length) parts.push({ kind: 'stepset', label: setWord('stepset', steps.length), n: steps.length, keys: steps.map(st => `step:${st.pkg}:${st.type}`) });
1283
1301
  return [{ pkg: '', exports: [], steps: [], parts, gathered: groups.length }];
@@ -1390,6 +1408,7 @@ function angledQuiet(pkgs, lsx, lex, y0, phone, fsS) {
1390
1408
  g.parts.forEach((it, i) => {
1391
1409
  const x = xs[i];
1392
1410
  let gg = it.kind === 'forkset' ? mark.fork(x - (phone ? 3 : 4), ry, phone ? 0.7 : 0.85) : it.kind === 'dbset' ? mark.station(x, ry, phone ? 3.6 : 4.6, it.writes) : it.socket ? mark.socket(x, ry, phone ? 4.2 : 5.4) : mark.dot(x, ry, phone ? 2.8 : 3.4);
1411
+ if (it.mixed) gg += strayUnknown(x, ry, phone);
1393
1412
  if (it.fork) gg += mark.fork(x + 1, ry - (phone ? 7 : 9), phone ? 0.5 : 0.6);
1394
1413
  gg += rot(x + 2, ry - (phone ? 12 : 15), -ang, ups.get(it));
1395
1414
  over.push(tg(it.keys, gg));
@@ -1468,7 +1487,7 @@ export function layoutQuiet(pkgs, lsx, lex, y0, phone, fsS0, bands = []) {
1468
1487
  // a step set's count starts past its loop's arrowhead
1469
1488
  let gg = '', tx = it.kind === 'stepset' ? x + (phone ? 6 : 8) : x - 3;
1470
1489
  if (it.kind === 'forkset') gg += mark.fork(x - (phone ? 3 : 4), ry, phone ? 0.75 : 0.9);
1471
- else if (it.kind === 'dbset') gg += mark.station(x, ry, phone ? 3.6 : 4.6, it.writes);
1490
+ else if (it.kind === 'dbset') gg += mark.station(x, ry, phone ? 3.6 : 4.6, it.writes) + (it.mixed ? strayUnknown(x, ry, phone) : '');
1472
1491
  else if (it.kind === 'stepset') gg += mark.loop(x, ry - 0.5, phone ? 4.2 : 5.4);
1473
1492
  else if (it.socket) gg += mark.socket(x, ry, phone ? 4.6 : 6);
1474
1493
  else gg += mark.dot(x, ry, phone ? 3 : 3.8);
@@ -20,7 +20,7 @@ from Yichus/IO import IO, flat_map, pure
20
20
  from Yichus/Access import Principal, AccessRule, AccessSpec, OwnerScoped, owner_key
21
21
  from Yichus/Data import Db
22
22
  from Yichus/Service import Reply, Done, Invalid, NotFound, Refused as Turned, TryAgain, handler, route, service_def, ReadPerm, WritePerm, CreatePerm
23
- from Yichus/Transaction import Scope, TxTable, Txn, Committed, transaction, replied, tx_table, tx_bind, tx_pure, tx_read, tx_write, tx_insert, tx_new_token
23
+ from Yichus/Transaction import TxTable, Txn, Committed, transaction, replied, stored_table, tx_bind, tx_pure, tx_read, tx_write, tx_insert, tx_new_token
24
24
  from Yichus/OpenRouter import ProviderPolicy, ModelRequest, ModelResult, Completed, Refused, RetryableFailure, PermanentFailure, TimedOut, openrouter_call
25
25
  from Yichus/Json import JsonObject, JsonString, JsonArray, JsonBool, Parsed, InvalidJson, Rendered, RenderError, Decoded, DecodeError, parse_json, render_json, decode_bind, required_field, as_bool
26
26
  from Zafu/Control/IterState import IterState, Continue, Done as Finished
@@ -53,34 +53,33 @@ access_rules = AccessSpec([
53
53
  AccessRule("refunds", OwnerScoped),
54
54
  ])
55
55
 
56
- def cases[s](scope: Scope[s]) -> TxTable[s, Case]: tx_table(scope, "refunds")
56
+ cases: TxTable[s, Case] = stored_table("refunds")
57
57
 
58
58
  # Open the order's refund, unless it already has one.
59
- def open_plan[s](scope: Scope[s], key: String, refund: Refund) -> Txn[s, RefundError, Unit]:
60
- reference <- tx_bind(tx_new_token(scope))
61
- tx_insert(cases(scope), key, Case(reference, 0, refund, [], None))
59
+ def open_plan[s](key: String, refund: Refund, reference: String) -> Txn[s, RefundError, Unit]:
60
+ tx_insert(cases, key, Case(reference, 0, refund, [], None))
62
61
 
63
- def read_plan[s](scope: Scope[s], key: String) -> Txn[s, RefundError, Option[Case]]:
64
- tx_read(cases(scope), key)
62
+ def read_plan[s](key: String) -> Txn[s, RefundError, Option[Case]]:
63
+ tx_read(cases, key)
65
64
 
66
65
  # Append an exchange to an undecided case, or decide it, only if its
67
66
  # version is still the one the round read; either raises the version.
68
- def append_plan[s](scope: Scope[s], key: String, seen: Int, exchange: Exchange) -> Txn[s, RefundError, Bool]:
69
- found <- tx_bind(tx_read(cases(scope), key))
67
+ def append_plan[s](key: String, seen: Int, exchange: Exchange) -> Txn[s, RefundError, Bool]:
68
+ found <- tx_bind(tx_read(cases, key))
70
69
  match found:
71
70
  case Some(Case(reference, version, refund, record, None)):
72
71
  if eq_Int(version, seen):
73
- _ <- tx_bind(tx_write(cases(scope), key, Case(reference, version.add(1), refund, record.concat([exchange]), None)))
72
+ _ <- tx_bind(tx_write(cases, key, Case(reference, version.add(1), refund, record.concat([exchange]), None)))
74
73
  tx_pure(True)
75
74
  else: tx_pure(False)
76
75
  case _: tx_pure(False)
77
76
 
78
- def decide_plan[s](scope: Scope[s], key: String, seen: Int, decision: Decision) -> Txn[s, RefundError, Bool]:
79
- found <- tx_bind(tx_read(cases(scope), key))
77
+ def decide_plan[s](key: String, seen: Int, decision: Decision) -> Txn[s, RefundError, Bool]:
78
+ found <- tx_bind(tx_read(cases, key))
80
79
  match found:
81
80
  case Some(Case(reference, version, refund, record, None)):
82
81
  if eq_Int(version, seen):
83
- _ <- tx_bind(tx_write(cases(scope), key, Case(reference, version.add(1), refund, record, Some(decision))))
82
+ _ <- tx_bind(tx_write(cases, key, Case(reference, version.add(1), refund, record, Some(decision))))
84
83
  tx_pure(True)
85
84
  else: tx_pure(False)
86
85
  case _: tx_pure(False)
@@ -139,20 +138,20 @@ def request_refund(db: Db, p: Principal, refund: Refund) -> IO[Reply[RefundError
139
138
  # carries on with the stored refund; a different one for the same order
140
139
  # is turned away.
141
140
  def round(_: Unit, _: Int) -> IO[IterState[Unit, Reply[RefundError, Decision]]]:
142
- found <- flat_map(transaction(db, scope -> read_plan(scope, key)))
141
+ found <- flat_map(transaction(db, _ -> read_plan(key)))
143
142
  match found:
144
143
  case Committed(Some(Case(reference, seen, stored, record, decided))):
145
144
  if same_refund(stored, refund):
146
145
  match (decided, replay(stored, record)):
147
146
  case (Some(decision), _): pure(Finished(Done(decision)))
148
147
  case (None, Some(Ruled(decision))):
149
- saved <- flat_map(transaction(db, scope -> decide_plan(scope, key, seen, decision)))
148
+ saved <- flat_map(transaction(db, _ -> decide_plan(key, seen, decision)))
150
149
  match saved:
151
150
  case Committed(True): pure(Finished(Done(decision)))
152
151
  case _: pure(Continue(()))
153
152
  case (None, Some(Perform(request))):
154
153
  answer <- flat_map(perform(reference, length_List(record), request))
155
- _ <- flat_map(transaction(db, scope -> append_plan(scope, key, seen, Exchange(request, answer))))
154
+ _ <- flat_map(transaction(db, _ -> append_plan(key, seen, Exchange(request, answer))))
156
155
  pure(Continue(()))
157
156
  case (None, None): pure(Finished(Turned("this refund's record cannot be read back", Unreadable)))
158
157
  else: pure(Finished(Turned("this order already has a refund request for another amount or reason", AlreadyRequested)))
@@ -160,7 +159,11 @@ def request_refund(db: Db, p: Principal, refund: Refund) -> IO[Reply[RefundError
160
159
  # The store was busy: read again next round.
161
160
  case _: pure(Continue(()))
162
161
  def decide():
163
- opened <- flat_map(transaction(db, scope -> open_plan(scope, key, refund)))
162
+ def open_case():
163
+ scope <- transaction(db)
164
+ reference <- tx_bind(tx_new_token(scope))
165
+ open_plan(key, refund, reference)
166
+ opened <- flat_map(open_case())
164
167
  match opened:
165
168
  case Committed(_):
166
169
  ended <- flat_map(round((), 0))
@@ -177,7 +180,7 @@ def request_refund(db: Db, p: Principal, refund: Refund) -> IO[Reply[RefundError
177
180
 
178
181
  def refund_status(db: Db, p: Principal, input: StatusRequest) -> IO[Reply[RefundError, RefundView]]:
179
182
  StatusRequest(order) = input
180
- found <- flat_map(transaction(db, scope -> read_plan(scope, owner_key(p, order))))
183
+ found <- flat_map(transaction(db, _ -> read_plan(owner_key(p, order))))
181
184
  pure(replied(found,
182
185
  c -> (
183
186
  match c:
@@ -14,7 +14,7 @@ from Yichus/IO import IO, flat_map, pure, fold_until
14
14
  from Yichus/Access import Principal, AccessRule, AccessSpec, OwnerScoped, owner_key
15
15
  from Yichus/Data import Db
16
16
  from Yichus/Service import Reply, Done, Invalid, NotFound, Refused as Turned, TryAgain, handler, route, service_def, ReadPerm, WritePerm, CreatePerm
17
- from Yichus/Transaction import Scope, TxTable, Txn, Committed, transaction, replied, tx_table, tx_bind, tx_pure, tx_read, tx_write, tx_insert, tx_new_token
17
+ from Yichus/Transaction import TxTable, Txn, Committed, transaction, replied, stored_table, tx_bind, tx_pure, tx_read, tx_write, tx_insert, tx_new_token
18
18
  from Yichus/OpenRouter import ProviderPolicy, ModelRequest, ModelResult, Completed, Refused, RetryableFailure, PermanentFailure, TimedOut, openrouter_call
19
19
  from Yichus/Json import JsonObject, JsonString, JsonArray, JsonBool, Parsed, InvalidJson, Rendered, RenderError, Decoded, DecodeError, parse_json, render_json, decode_bind, required_field, as_bool
20
20
  from Zafu/Control/IterState import IterState, Continue, Done as Finished
@@ -47,34 +47,33 @@ access_rules = AccessSpec([
47
47
  AccessRule("refunds", OwnerScoped),
48
48
  ])
49
49
 
50
- def cases[s](scope: Scope[s]) -> TxTable[s, Case]: tx_table(scope, "refunds")
50
+ cases: TxTable[s, Case] = stored_table("refunds")
51
51
 
52
52
  # Open the order's refund, unless it already has one.
53
- def open_plan[s](scope: Scope[s], key: String, refund: Refund) -> Txn[s, RefundError, Unit]:
54
- reference <- tx_bind(tx_new_token(scope))
55
- tx_insert(cases(scope), key, Case(reference, 0, refund, [], None))
53
+ def open_plan[s](key: String, refund: Refund, reference: String) -> Txn[s, RefundError, Unit]:
54
+ tx_insert(cases, key, Case(reference, 0, refund, [], None))
56
55
 
57
- def read_plan[s](scope: Scope[s], key: String) -> Txn[s, RefundError, Option[Case]]:
58
- tx_read(cases(scope), key)
56
+ def read_plan[s](key: String) -> Txn[s, RefundError, Option[Case]]:
57
+ tx_read(cases, key)
59
58
 
60
59
  # Append an exchange to an undecided case, or decide it, only if its
61
60
  # version is still the one the round read; either raises the version.
62
- def append_plan[s](scope: Scope[s], key: String, seen: Int, exchange: Exchange) -> Txn[s, RefundError, Bool]:
63
- found <- tx_bind(tx_read(cases(scope), key))
61
+ def append_plan[s](key: String, seen: Int, exchange: Exchange) -> Txn[s, RefundError, Bool]:
62
+ found <- tx_bind(tx_read(cases, key))
64
63
  match found:
65
64
  case Some(Case(reference, version, refund, record, None)):
66
65
  if eq_Int(version, seen):
67
- _ <- tx_bind(tx_write(cases(scope), key, Case(reference, version.add(1), refund, record.concat([exchange]), None)))
66
+ _ <- tx_bind(tx_write(cases, key, Case(reference, version.add(1), refund, record.concat([exchange]), None)))
68
67
  tx_pure(True)
69
68
  else: tx_pure(False)
70
69
  case _: tx_pure(False)
71
70
 
72
- def decide_plan[s](scope: Scope[s], key: String, seen: Int, decision: Decision) -> Txn[s, RefundError, Bool]:
73
- found <- tx_bind(tx_read(cases(scope), key))
71
+ def decide_plan[s](key: String, seen: Int, decision: Decision) -> Txn[s, RefundError, Bool]:
72
+ found <- tx_bind(tx_read(cases, key))
74
73
  match found:
75
74
  case Some(Case(reference, version, refund, record, None)):
76
75
  if eq_Int(version, seen):
77
- _ <- tx_bind(tx_write(cases(scope), key, Case(reference, version.add(1), refund, record, Some(decision))))
76
+ _ <- tx_bind(tx_write(cases, key, Case(reference, version.add(1), refund, record, Some(decision))))
78
77
  tx_pure(True)
79
78
  else: tx_pure(False)
80
79
  case _: tx_pure(False)
@@ -137,20 +136,20 @@ def request_refund(db: Db, p: Principal, refund: Refund) -> IO[Reply[RefundError
137
136
  # carries on with the stored refund; a different one for the same order
138
137
  # is turned away.
139
138
  def round(_: Unit, _: Int) -> IO[IterState[Unit, Reply[RefundError, Decision]]]:
140
- found <- flat_map(transaction(db, scope -> read_plan(scope, key)))
139
+ found <- flat_map(transaction(db, _ -> read_plan(key)))
141
140
  match found:
142
141
  case Committed(Some(Case(reference, seen, stored, record, decided))):
143
142
  if same_refund(stored, refund):
144
143
  match (decided, replay(stored, record)):
145
144
  case (Some(decision), _): pure(Finished(Done(decision)))
146
145
  case (None, Some(Ruled(decision))):
147
- saved <- flat_map(transaction(db, scope -> decide_plan(scope, key, seen, decision)))
146
+ saved <- flat_map(transaction(db, _ -> decide_plan(key, seen, decision)))
148
147
  match saved:
149
148
  case Committed(True): pure(Finished(Done(decision)))
150
149
  case _: pure(Continue(()))
151
150
  case (None, Some(Perform(request))):
152
151
  answer <- flat_map(perform(reference, length_List(record), request))
153
- _ <- flat_map(transaction(db, scope -> append_plan(scope, key, seen, Exchange(request, answer))))
152
+ _ <- flat_map(transaction(db, _ -> append_plan(key, seen, Exchange(request, answer))))
154
153
  pure(Continue(()))
155
154
  case (None, None): pure(Finished(Turned("this refund's record cannot be read back", Unreadable)))
156
155
  else: pure(Finished(Turned("this order already has a refund request for another amount or reason", AlreadyRequested)))
@@ -158,7 +157,11 @@ def request_refund(db: Db, p: Principal, refund: Refund) -> IO[Reply[RefundError
158
157
  # The store was busy: read again next round.
159
158
  case _: pure(Continue(()))
160
159
  def decide():
161
- opened <- flat_map(transaction(db, scope -> open_plan(scope, key, refund)))
160
+ def open_case():
161
+ scope <- transaction(db)
162
+ reference <- tx_bind(tx_new_token(scope))
163
+ open_plan(key, refund, reference)
164
+ opened <- flat_map(open_case())
162
165
  match opened:
163
166
  case Committed(_):
164
167
  ended <- flat_map(fold_until(range(rounds), (), round))
@@ -175,7 +178,7 @@ def request_refund(db: Db, p: Principal, refund: Refund) -> IO[Reply[RefundError
175
178
 
176
179
  def refund_status(db: Db, p: Principal, input: StatusRequest) -> IO[Reply[RefundError, RefundView]]:
177
180
  StatusRequest(order) = input
178
- found <- flat_map(transaction(db, scope -> read_plan(scope, owner_key(p, order))))
181
+ found <- flat_map(transaction(db, _ -> read_plan(owner_key(p, order))))
179
182
  pure(replied(found,
180
183
  c -> (
181
184
  match c:
@@ -27,7 +27,7 @@ from Yichus/Service import (
27
27
  )
28
28
  from Yichus/Transaction import (
29
29
  Scope, TxTable, Txn, Outcome,
30
- tx_table, tx_pure, tx_bind, tx_abort, tx_read, tx_write, tx_insert,
30
+ stored_table, tx_pure, tx_bind, tx_abort, tx_read, tx_write, tx_insert,
31
31
  tx_database_time_millis, transaction
32
32
  )
33
33
 
@@ -159,11 +159,9 @@ access_rules = AccessSpec([
159
159
  AccessRule("withdrawal_receipts", OwnerScoped),
160
160
  ])
161
161
 
162
- def accounts_table[s](scope: Scope[s]) -> TxTable[s, Account]:
163
- tx_table(scope, "accounts")
162
+ accounts_table: TxTable[s, Account] = stored_table("accounts")
164
163
 
165
- def receipts_table[s](scope: Scope[s]) -> TxTable[s, Receipt]:
166
- tx_table(scope, "withdrawal_receipts")
164
+ receipts_table: TxTable[s, Receipt] = stored_table("withdrawal_receipts")
167
165
 
168
166
  def account_rows(db: Db) -> Table[Account]:
169
167
  table(db, "accounts")
@@ -178,12 +176,11 @@ def receipt_rows(db: Db) -> Table[Receipt]:
178
176
  # ---------------------------------------------------------------------------
179
177
 
180
178
  def move_balance[s](
181
- scope: Scope[s],
182
179
  principal: Principal,
183
180
  delta: Int
184
181
  ) -> Txn[s, LedgerError, Movement]:
185
182
  Principal(uid, _) = principal
186
- row <- tx_bind(tx_read(accounts_table(scope), uid))
183
+ row <- tx_bind(tx_read(accounts_table, uid))
187
184
  match row:
188
185
  case None: tx_abort(UnknownAccount(uid))
189
186
  case Some(Account(id, owner, balance)):
@@ -191,7 +188,7 @@ def move_balance[s](
191
188
  if int_lt(next, 0):
192
189
  tx_abort(InsufficientFunds(balance, sub(0, delta)))
193
190
  else:
194
- _ <- tx_bind(tx_write(accounts_table(scope), uid, Account(id, owner, next)))
191
+ _ <- tx_bind(tx_write(accounts_table, uid, Account(id, owner, next)))
195
192
  tx_pure(Movement(balance, next))
196
193
 
197
194
  # ---------------------------------------------------------------------------
@@ -207,7 +204,7 @@ def withdraw_plan[s](
207
204
  case Some(error): tx_abort(error)
208
205
  case None:
209
206
  key = owner_key(principal, command_local_key(input))
210
- previous <- tx_bind(tx_read(receipts_table(scope), key))
207
+ previous <- tx_bind(tx_read(receipts_table, key))
211
208
  match previous:
212
209
  case Some(receipt): tx_pure(receipt)
213
210
  case None:
@@ -215,17 +212,16 @@ def withdraw_plan[s](
215
212
  if int_lt(now, 0): tx_abort(InvalidDatabaseTime(now))
216
213
  else:
217
214
  WithdrawInput(_, amount) = input
218
- moved <- tx_bind(move_balance(scope, principal, sub(0, amount)))
215
+ moved <- tx_bind(move_balance(principal, sub(0, amount)))
219
216
  Movement(_, after) = moved
220
217
  receipt = Receipt(input, after, now)
221
- _ <- tx_bind(tx_insert(receipts_table(scope), key, receipt))
218
+ _ <- tx_bind(tx_insert(receipts_table, key, receipt))
222
219
  # The receipt filed in the ledger and the receipt in the
223
220
  # response are one value, so the customer's copy and the
224
221
  # bank's copy cannot say different things.
225
222
  tx_pure(receipt)
226
223
 
227
224
  def deposit_plan[s](
228
- scope: Scope[s],
229
225
  principal: Principal,
230
226
  input: DepositInput
231
227
  ) -> Txn[s, LedgerError, Movement]:
@@ -233,7 +229,7 @@ def deposit_plan[s](
233
229
  case Some(error): tx_abort(error)
234
230
  case None:
235
231
  DepositInput(_, amount) = input
236
- move_balance(scope, principal, amount)
232
+ move_balance(principal, amount)
237
233
 
238
234
  # ---------------------------------------------------------------------------
239
235
  # Handlers. The engine injects Db and Principal; nothing here
@@ -253,8 +249,8 @@ def deposit(
253
249
  principal: Principal,
254
250
  input: DepositInput
255
251
  ) -> IO[Outcome[LedgerError, Movement]]:
256
- scope <- transaction(db)
257
- deposit_plan(scope, principal, input)
252
+ _ <- transaction(db)
253
+ deposit_plan(principal, input)
258
254
 
259
255
  def withdrawal_status(
260
256
  db: Db,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yichus",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Local Bosatsu compilation, Yichus analysis, and generated apps using the WebMCP engine",
5
5
  "license": "MIT",
6
6
  "homepage": "https://yich.us",
@@ -20,7 +20,7 @@
20
20
  "dependencies": {
21
21
  "zod": "^4.3.6",
22
22
  "@modelcontextprotocol/sdk": "^1.26.0",
23
- "@yichus/api-mcp": "0.1.3"
23
+ "@yichus/api-mcp": "0.1.4"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@types/node": "^22.0.0",