@hyperscale0/hsx 4.2.0 → 5.0.0

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.
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
- export const HSX_VERSION = "4.2.0";
1
+ export const HSX_VERSION = "5.0.0";
2
2
  export const HSX_TARGET_UDL_VERSION = 4;
@@ -13,7 +13,7 @@ instrument case(on: ref<financing.installments>[], agency: party, capital: party
13
13
  }
14
14
  action assign { from: opened, to: assigned, actor: { party: capital } }
15
15
  action recover { expansionLimit: 8192, from: assigned, to: recovered, actor: { party: agency }, requires self.payment.plan == self.plan, invoke: [{ reference: self.payment, action: pay, input: {} }, { reference: self.id, action: pay_fee, input: {} }] }
16
- action pay_fee { from: recovered, to: recovered, actor: { parent: current() }, moves self.agencyFee from self.plan.capital to agency }
16
+ action pay_fee { allowZero: true, from: recovered, to: recovered, actor: { parent: current() }, moves self.agencyFee from self.plan.capital to agency }
17
17
  action recall { from: [opened, assigned], to: recalled, actor: { party: capital } }
18
18
  action return_uncollected { from: assigned, to: returned_uncollected, actor: { party: agency } }
19
19
  }
package/std/escrow.hsx CHANGED
@@ -26,7 +26,7 @@ instrument hold(payer: party = party(person), payee: party = programOperator, ac
26
26
  from: funded, to: delivered, actor: { party: payee }
27
27
  set: { deliveredAt: { field: self.now } }
28
28
  }
29
- action accept {
29
+ action accept { allowZero: true, requires self.price > "0";
30
30
  from: delivered, to: released, actor: { party: payer }
31
31
  input { acceptanceReference: text }
32
32
  deadline: { at: self.deliveredAt, offset: accept_within }
package/std/financing.hsx CHANGED
@@ -1,6 +1,13 @@
1
1
  header financing
2
2
 
3
3
  instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3, profit: percent, disburse_to: enum(funds, borrower) = funds, profit_earned: enum(on_payment, by_schedule, at_disbursement) = on_payment, apply: enum(fines_profit_principal, principal_profit, pro_rata) = fines_profit_principal, payoff_rebate: percent = 100%, write_off_after: duration = 90d, max_extension: duration = 90d, max_amendments: integer(0, 366) = 2, waiver_limit: percent = 100%, amendment_expiry: duration = 7d, payoff_quote_expiry: duration = 1h, allow_overdue: enum(allowed, blocked) = allowed, assessed_fines: enum(carry, require_waiver) = carry, down_payment: percent = 0%, funds: ref<escrow.hold>?, borrower: party = party(person), capital: party = programOperator, share: percent = 0%, limits: ref<financing.limits> = object(financing.limits), portfolio: ref<financing.portfolio_limit> = object(financing.portfolio_limit)) {
4
+ dependencies {
5
+ funds {
6
+ selector: disburse_to, is: funds
7
+ message: "Attachment `{attachment}` disburses into a hold, but `funds` is not bound."
8
+ fix: "Bind `funds: sale` to an escrow attachment. Choose `disburse_to: borrower` only if the borrower should receive the money directly."
9
+ }
10
+ }
4
11
  revisioned: true
5
12
  familyRevision: 1
6
13
  summary: "Funding, profit recognition and payment order chosen by the program."
@@ -29,7 +36,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
29
36
  from: quoted, to: signed, actor: { party: borrower }, input { signedOffer: text, affordabilityReference: text }
30
37
  requires count of { instrument: slice, reference: "plan", anchor: self.id, states: [draft], limit: 366 } == self.months
31
38
  }
32
- action disburse {
39
+ action disburse { allowZero: true,
33
40
  from: signed, to: active, actor: { party: capital }
34
41
  requires self.borrowerLimit in [approved]
35
42
  requires self.portfolioLimit in [approved]
@@ -41,7 +48,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
41
48
  invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [draft], limit: 366, order: [position] }, action: activate, input: {} }]
42
49
  when disburse_to is funds { invoke: [{ reference: self.funds, action: confirm, input: {} }] }
43
50
  }
44
- action fund_down_payment {
51
+ action fund_down_payment { allowZero: true,
45
52
  from: active, to: active, actor: { parent: current() }
46
53
  when disburse_to is funds {
47
54
  calculate: [{ target: downPaymentCredit, op: minimum, values: [{ field: self.funds.held.balance }, { field: self.downPayment }] }, { target: downPaymentRemaining, op: subtract, base: { field: self.downPayment }, subtract: [{ field: self.downPaymentCredit }] }]
@@ -186,14 +193,14 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
186
193
  fields { plan: ref<parent>, identity: text, principal: money, profit: money, loss: money, kind: enum(cash, loss), share: money?, retainedProfit: money?
187
194
  allocatedPayable: account(capital, cash, "allocatedProfitPayable"), capital: account(capital, cash, "capital"), profitIncome: account(capital, cash, "profitIncome"), lossAccount: account(capital, claim, "loss") }
188
195
  lifecycle { states: [recorded, refunded], initial: recorded }
189
- action create {
196
+ action create { allowZero: true,
190
197
  actor: { parent: parent }
191
198
  input { plan: ref<parent>, identity: text, principal: money, profit: money, loss: money, kind: enum(cash, loss) }
192
199
  requires unique "settlement_identity" on [self.plan, self.identity]
193
200
  calculate: [{ target: "share", op: "rate", base: { field: self.profit }, bps: { literal: share }, rounding: "floor" }, { target: retainedProfit, op: "subtract", base: { field: self.profit }, subtract: [{ field: self.share }] }]
194
201
  moves self.share from self.profitIncome to self.allocatedPayable
195
202
  }
196
- action refund {
203
+ action refund { allowZero: true,
197
204
  from: recorded, to: refunded, actor: { parent: payment }
198
205
  moves self.share from self.allocatedPayable to self.profitIncome
199
206
  }
@@ -259,7 +266,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
259
266
  calculate: [{ target: nextDueAt, op: at, list: input.amendment.dates, position: { field: self.position } }]
260
267
  requires self.nextDueAt == self.dueAt
261
268
  }
262
- action activate {
269
+ action activate { allowZero: true,
263
270
  from: draft, to: pending, actor: { parent: parent }
264
271
  moves self.principal from self.debt to self.principalReceivable
265
272
  moves self.profit from self.debt to self.profitReceivable
@@ -324,7 +331,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
324
331
  requires self.principalReceivable.balance == "0"; requires self.profitReceivable.balance == "0"; requires self.profitEarned.balance == "0"
325
332
  }
326
333
  action restore { from: [pending, due, paid], to: pending, actor: { parent: payment } }
327
- action payoff {
334
+ action payoff { allowZero: true,
328
335
  from: [pending, due], to: paid, actor: { parent: parent }, input { identity: text }
329
336
  set: { payoffIdentity: { field: input.identity } }
330
337
  calculate: [{ target: payoffRebate, op: rate, base: { field: self.profitReceivable.balance }, bps: { literal: payoff_rebate }, rounding: floor }, { target: payoffProfitTotal, op: sum, values: [{ field: self.profitReceivable.balance }, { field: self.profitEarned.balance }] }, { target: payoffProfit, op: subtract, base: { field: self.payoffProfitTotal }, subtract: [{ field: self.payoffRebate }] }]
@@ -334,7 +341,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
334
341
  moves self.profitReceivable.balance from self.profitReceivable to self.debt
335
342
  moves self.profitEarned.balance from self.profitEarned to self.debt
336
343
  }
337
- action write_off {
344
+ action write_off { allowZero: true,
338
345
  from: [pending, due], to: written_off, actor: { parent: parent }
339
346
  moves self.principalReceivable.balance from self.principalReceivable to self.loss; moves self.profitReceivable.balance from self.profitReceivable to self.debt; moves self.profitEarned.balance from self.profitEarned to self.loss
340
347
  }
@@ -344,7 +351,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
344
351
  lifecycle { states: [pending, recognised], initial: pending }
345
352
  action create { actor: { parent: slice }, input { slice: ref<slice> }, requires unique "profit_recognition" on [self.slice] }
346
353
  action reschedule { from: pending, to: pending, actor: { parent: slice } }
347
- action mature { from: pending, to: recognised, actor: clock, due: { at: self.dueAt }, moves self.slice.profitReceivable.balance from self.slice.profitReceivable to self.slice.profitEarned }
354
+ action mature { allowZero: true, from: pending, to: recognised, actor: clock, due: { at: self.dueAt }, moves self.slice.profitReceivable.balance from self.slice.profitReceivable to self.slice.profitEarned }
348
355
  }
349
356
  payment: {
350
357
  revisioned: true
@@ -398,14 +405,14 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
398
405
  principal_piece: {
399
406
  fields { slice: ref<slice>, payment: ref<payment>, limit: money, amount: money? }
400
407
  lifecycle { states: [paid, refunded], initial: paid }
401
- action create {
408
+ action create { allowZero: true,
402
409
  calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.slice.principalReceivable.balance }, { field: self.limit }] }]
403
410
  actor: { parent: slice }
404
411
  input { slice: ref<slice>, payment: ref<payment>, limit: money }
405
412
  requires unique "principal_receipt" on [self.slice, self.payment]
406
413
  moves self.amount from self.payment.held to self.slice.plan.capital; moves self.amount from self.slice.principalReceivable to self.slice.debt
407
414
  }
408
- action refund {
415
+ action refund { allowZero: true,
409
416
  from: paid, to: refunded, actor: { parent: payment }
410
417
  moves self.amount from self.slice.plan.capital to self.slice.plan.borrower; moves self.amount from self.slice.debt to self.slice.principalReceivable
411
418
  }
@@ -413,7 +420,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
413
420
  profit_piece: {
414
421
  fields { slice: ref<slice>, payment: ref<payment>, limit: money, amount: money? }
415
422
  lifecycle { states: [paid, refunded], initial: paid }
416
- action create {
423
+ action create { allowZero: true,
417
424
  calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.limit }] }]
418
425
  actor: { parent: slice }
419
426
  input { slice: ref<slice>, payment: ref<payment>, limit: money }
@@ -423,7 +430,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
423
430
  when profit_earned is by_schedule { moves self.amount from self.slice.profitEarned to self.slice.debt }
424
431
  when profit_earned is at_disbursement { moves self.amount from self.slice.profitEarned to self.slice.debt }
425
432
  }
426
- action refund {
433
+ action refund { allowZero: true,
427
434
  from: paid, to: refunded, actor: { parent: payment }
428
435
  moves self.amount from self.slice.plan.profitIncome to self.slice.plan.borrower
429
436
  when profit_earned is on_payment { moves self.amount from self.slice.debt to self.slice.profitReceivable }
@@ -435,6 +442,13 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
435
442
  }
436
443
 
437
444
  instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d, fine: money = 50 SAR, cap: money = 25 SAR, fines_to: party = programOperator, costs_to: party = programOperator, borrower: party = party(person)) {
445
+ parameterDiagnostics {
446
+ fine {
447
+ accepts: "a fixed amount"
448
+ percentage: "a percentage of overdue debt"
449
+ fix: "Use a fixed amount only if that is the intended policy. A percentage charge needs an authored rate calculation; do not approximate it with a cash amount."
450
+ }
451
+ }
438
452
  revisioned: true
439
453
  familyRevision: 1
440
454
  summary: "Separate approved fine and evidenced recovery claims on an overdue installment."
@@ -444,7 +458,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
444
458
  }
445
459
  lifecycle { states: [proposed, assessed, collected, waived, refunded], initial: proposed }
446
460
  action create { calculate: [{ target: overdueAt, op: shift, date: { field: self.slice.dueAt }, milliseconds: { literal: grace }, direction: after }], requires unique "late_assessment" on [self.slice, self.overdueAt]; requires self.borrower == self.slice.plan.borrower; requires self.plan == self.slice.plan }
447
- action assess {
461
+ action assess { allowZero: true,
448
462
  from: proposed, to: assessed, actor: clock, due: { at: self.overdueAt }
449
463
  requires self.slice.principalReceivable.balance > "0"
450
464
  moves self.fine from self.debt to self.receivable
@@ -455,7 +469,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
455
469
  requires input.payment.plan == self.plan
456
470
  invoke: [{ instrument: receipt, action: create, input: { assessment: { field: self.id }, payment: { field: input.payment } } }, { selection: { instrument: current(), reference: "plan", anchor: self.plan, states: [assessed], limit: 1, where: { id: { field: self.id }, receivable.balance: { literal: "0" } } }, action: finish, input: {} }]
457
471
  }
458
- action payoff {
472
+ action payoff { allowZero: true,
459
473
  from: [assessed, collected], to: collected, actor: { parent: on }
460
474
  requires self.plan in [paid]
461
475
  moves self.receivable.balance from self.plan.borrower to fines_to
@@ -468,7 +482,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
468
482
  requires input.amendment.plan == self.plan; requires input.amendment in [applied]
469
483
  invoke: [{ instrument: waiver, action: create, input: { assessment: { field: self.id }, amendment: { field: input.amendment } } }]
470
484
  }
471
- action waive { from: [assessed, collected], to: waived, actor: { parent: waiver_request }, input { reason: text, evidence: text }, moves self.receivable.balance from self.receivable to self.debt }
485
+ action waive { allowZero: true, from: [assessed, collected], to: waived, actor: { parent: waiver_request }, input { reason: text, evidence: text }, moves self.receivable.balance from self.receivable to self.debt }
472
486
  action refund {
473
487
  from: [assessed, collected], to: assessed, actor: { party: fines_to }, input { payment: ref<on.payment> }
474
488
  requires input.payment in [paid, refunded]
@@ -489,7 +503,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
489
503
  waiver: {
490
504
  fields { assessment: ref<parent>, amendment: ref<on.amendment>, amount: money? }
491
505
  lifecycle { states: [recorded], initial: recorded }
492
- action create {
506
+ action create { allowZero: true,
493
507
  actor: { parent: parent }, input { assessment: ref<parent>, amendment: ref<on.amendment> }
494
508
  calculate: [{ target: amount, op: rate, base: { field: self.assessment.receivable.balance }, bps: { field: self.amendment.waiverPercent }, rounding: floor }]
495
509
  requires unique "amendment_waiver" on [self.assessment, self.amendment]
@@ -499,8 +513,8 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
499
513
  receipt: {
500
514
  fields { assessment: ref<parent>, payment: ref<on.payment>, amount: money? }
501
515
  lifecycle { states: [paid, refunded], initial: paid }
502
- action create { calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.assessment.receivable.balance }] }], actor: { parent: parent }, input { assessment: ref<parent>, payment: ref<on.payment> }, requires input.payment.plan == self.assessment.plan; requires unique "fine_receipt" on [self.assessment, self.payment]; moves self.amount from self.payment.held to fines_to; moves self.amount from self.assessment.receivable to self.assessment.debt }
503
- action refund { from: paid, to: refunded, actor: { parent: parent }, moves self.amount from fines_to to self.payment.plan.borrower; moves self.amount from self.assessment.debt to self.assessment.receivable }
516
+ action create { allowZero: true, calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.assessment.receivable.balance }] }], actor: { parent: parent }, input { assessment: ref<parent>, payment: ref<on.payment> }, requires input.payment.plan == self.assessment.plan; requires unique "fine_receipt" on [self.assessment, self.payment]; moves self.amount from self.payment.held to fines_to; moves self.amount from self.assessment.receivable to self.assessment.debt }
517
+ action refund { allowZero: true, from: paid, to: refunded, actor: { parent: parent }, moves self.amount from fines_to to self.payment.plan.borrower; moves self.amount from self.assessment.debt to self.assessment.receivable }
504
518
  }
505
519
 
506
520
  cost: {
@@ -522,7 +536,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
522
536
  requires input.payment.plan == self.plan
523
537
  invoke: [{ instrument: receipt, action: create, input: { assessment: { field: self.id }, payment: { field: input.payment } } }, { selection: { instrument: current(), reference: "plan", anchor: self.plan, states: [assessed], limit: 1, where: { id: { field: self.id }, receivable.balance: { literal: "0" } } }, action: finish, input: {} }]
524
538
  }
525
- action payoff {
539
+ action payoff { allowZero: true,
526
540
  from: [assessed, collected], to: collected, actor: { parent: on }
527
541
  requires self.plan in [paid]
528
542
  moves self.receivable.balance from self.plan.borrower to costs_to
@@ -535,7 +549,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
535
549
  requires input.amendment.plan == self.plan; requires input.amendment in [applied]
536
550
  invoke: [{ instrument: waiver, action: create, input: { assessment: { field: self.id }, amendment: { field: input.amendment } } }]
537
551
  }
538
- action waive { from: [assessed, collected], to: waived, actor: { parent: waiver_request }, input { reason: text, evidence: text }, moves self.receivable.balance from self.receivable to self.debt }
552
+ action waive { allowZero: true, from: [assessed, collected], to: waived, actor: { parent: waiver_request }, input { reason: text, evidence: text }, moves self.receivable.balance from self.receivable to self.debt }
539
553
  action refund {
540
554
  from: [assessed, collected], to: assessed, actor: { party: costs_to }, input { payment: ref<on.payment> }
541
555
  requires input.payment in [paid, refunded]
@@ -556,7 +570,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
556
570
  waiver: {
557
571
  fields { assessment: ref<parent>, amendment: ref<on.amendment>, amount: money? }
558
572
  lifecycle { states: [recorded], initial: recorded }
559
- action create {
573
+ action create { allowZero: true,
560
574
  actor: { parent: parent }, input { assessment: ref<parent>, amendment: ref<on.amendment> }
561
575
  calculate: [{ target: amount, op: rate, base: { field: self.assessment.receivable.balance }, bps: { field: self.amendment.waiverPercent }, rounding: floor }]
562
576
  requires unique "amendment_waiver" on [self.assessment, self.amendment]
@@ -566,8 +580,8 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
566
580
  receipt: {
567
581
  fields { assessment: ref<parent>, payment: ref<on.payment>, amount: money? }
568
582
  lifecycle { states: [paid, refunded], initial: paid }
569
- action create { calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.assessment.receivable.balance }] }], actor: { parent: parent }, input { assessment: ref<parent>, payment: ref<on.payment> }, requires input.payment.plan == self.assessment.plan; requires unique "cost_receipt" on [self.assessment, self.payment]; moves self.amount from self.payment.held to costs_to; moves self.amount from self.assessment.receivable to self.assessment.debt }
570
- action refund { from: paid, to: refunded, actor: { parent: parent }, moves self.amount from costs_to to self.payment.plan.borrower; moves self.amount from self.assessment.debt to self.assessment.receivable }
583
+ action create { allowZero: true, calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.assessment.receivable.balance }] }], actor: { parent: parent }, input { assessment: ref<parent>, payment: ref<on.payment> }, requires input.payment.plan == self.assessment.plan; requires unique "cost_receipt" on [self.assessment, self.payment]; moves self.amount from self.payment.held to costs_to; moves self.amount from self.assessment.receivable to self.assessment.debt }
584
+ action refund { allowZero: true, from: paid, to: refunded, actor: { parent: parent }, moves self.amount from costs_to to self.payment.plan.borrower; moves self.amount from self.assessment.debt to self.assessment.receivable }
571
585
  }
572
586
  }
573
587
  }
package/std/lending.hsx CHANGED
@@ -76,23 +76,23 @@ instrument distribution(round: ref<lending.round>, receipt: ref, commitments: re
76
76
  requires self.mode == "loss"; requires self.source.kind == "loss"; requires self.source in [recorded]
77
77
  calculate: [{ target: "net", op: "sum", values: [{ field: self.source.loss }] }]
78
78
  }
79
- action distribute_cash {
79
+ action distribute_cash { allowZero: true,
80
80
  from: cash_ready, to: distributed, actor: { party: operator }
81
81
  requires sum "weight" of { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit: 366 } == self.round.price
82
82
  moves self.principal from self.source.capital to self.held; moves self.profit from self.source.profitIncome to self.held; moves self.charge from self.held to programOperator; moves self.vat from self.held to programTax
83
83
  invoke: [{ selection: { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit: 366 }, action: cash, input: {} }, { reference: self.id, action: residual_cash, input: {} }]
84
84
  }
85
- action residual_cash {
85
+ action residual_cash { allowZero: true,
86
86
  from: distributed, to: distributed, actor: { parent: current() }
87
87
  moves self.held.balance from self.held to residual_to
88
88
  }
89
- action distribute_loss {
89
+ action distribute_loss { allowZero: true,
90
90
  from: loss_ready, to: distributed, actor: { party: operator }
91
91
  requires sum "weight" of { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit: 366 } == self.round.price
92
92
  moves self.net from self.source.lossAccount to self.losses
93
93
  invoke: [{ selection: { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit: 366 }, action: loss, input: {} }, { reference: self.id, action: residual_loss, input: {} }]
94
94
  }
95
- action residual_loss {
95
+ action residual_loss { allowZero: true,
96
96
  from: distributed, to: distributed, actor: { parent: current() }
97
97
  moves self.losses.balance from self.losses to self.residualLoss
98
98
  }
@@ -105,11 +105,11 @@ instrument distribution(round: ref<lending.round>, receipt: ref, commitments: re
105
105
  }
106
106
  lifecycle { states: [pending, paid], initial: pending }
107
107
  action create { requires self.distribution in [cash_ready, loss_ready]; requires self.commitment in [funded]; requires unique "distribution_weight" on [self.distribution, self.commitment]; requires self.commitment.round == self.distribution.round }
108
- action cash {
108
+ action cash { allowZero: true,
109
109
  from: pending, to: paid, actor: { parent: parent }
110
110
  moves self.gross from self.distribution.held to self.commitment.wallet.held
111
111
  }
112
- action loss { from: pending, to: paid, actor: { parent: parent }, moves self.gross from self.distribution.losses to self.commitment.loss }
112
+ action loss { allowZero: true, from: pending, to: paid, actor: { parent: parent }, moves self.gross from self.distribution.losses to self.commitment.loss }
113
113
  }
114
114
  }
115
115
  }
@@ -71,7 +71,7 @@ instrument reservation(listing: ref<marketplace.listing>, funds: ref<escrow.hold
71
71
  from: active, to: active, actor: { party: buyer }, deadline: { at: self.expiresAt }
72
72
  invoke: [{ reference: self.funds, action: fund, input: {} }]
73
73
  }
74
- action credit {
74
+ action credit { allowZero: true,
75
75
  from: active, to: converted, actor: { parent: converters }, deadline: { at: self.expiresAt }
76
76
  input { funds: ref<funds>, contribution: money }
77
77
  requires input.funds == self.funds; requires input.funds in [pending, funded]; requires input.funds.payer == self.buyer; requires input.funds.price == self.price
@@ -81,7 +81,7 @@ instrument reservation(listing: ref<marketplace.listing>, funds: ref<escrow.hold
81
81
  moves self.credit from self.held to input.funds.held
82
82
  moves self.refund from self.held to self.buyer
83
83
  }
84
- action cancel {
84
+ action cancel { allowZero: true,
85
85
  from: active, to: cancelled, actor: { party: buyer }, input { reason: text }
86
86
  calculate: [{ target: refund, op: rate, base: { field: self.held.balance }, bps: { literal: cancellation_refund }, rounding: floor }, { target: retained, op: subtract, base: { field: self.held.balance }, subtract: [{ field: self.refund }] }]
87
87
  set: { reason: { field: input.reason }, dispositionAt: { field: self.now } }
@@ -98,7 +98,7 @@ instrument reservation(listing: ref<marketplace.listing>, funds: ref<escrow.hold
98
98
  moves self.held.balance from self.held to self.buyer
99
99
  }
100
100
  action withdraw { from: proposed, to: cancelled, actor: { party: buyer } }
101
- action expire {
101
+ action expire { allowZero: true,
102
102
  from: active, to: expired, actor: clock, due: { at: self.expiresAt }
103
103
  calculate: [{ target: refund, op: rate, base: { field: self.held.balance }, bps: { literal: expiry_refund }, rounding: floor }, { target: retained, op: subtract, base: { field: self.held.balance }, subtract: [{ field: self.refund }] }]
104
104
  set: { dispositionAt: { field: self.now } }
package/std/money.hsx CHANGED
@@ -4,7 +4,7 @@ instrument transfer(payer: party, payee: party, amount: money = runtime) {
4
4
  summary: "One transfer between the bound parties."
5
5
  fields { amount: money = amount }
6
6
  lifecycle { states: [pending, paid, cancelled], initial: pending }
7
- action create { actor: { party: payer } }
7
+ action create { actor: { party: payer }, requires self.amount > "0" }
8
8
  action pay {
9
9
  from: pending, to: paid, actor: { party: payer }
10
10
  moves self.amount from payer to payee
@@ -41,7 +41,7 @@ instrument split(payer: party, amount: money = runtime, shares: split = { progra
41
41
  fields { amount: money = amount }
42
42
  lifecycle { states: [pending, paid, cancelled], initial: pending }
43
43
  action create {}
44
- action pay { from: pending, to: paid, actor: { party: payer }, moves self.amount from payer shares shares }
44
+ action pay { allowZero: true, requires self.amount > "0"; from: pending, to: paid, actor: { party: payer }, moves self.amount from payer shares shares }
45
45
  action cancel { from: pending, to: cancelled }
46
46
  }
47
47
 
@@ -72,7 +72,7 @@ instrument schedule(payer: party, payee: party, amount: money = runtime, count:
72
72
  }
73
73
  lifecycle { states: [pending, paid, cancelled], initial: pending }
74
74
  action create { requires self.schedule in [draft]; requires unique "scheduled_position" on [self.schedule, self.position] }
75
- action pay { from: pending, to: paid, actor: clock, due: { at: self.dueAt }, requires self.schedule in [active], moves self.amount from payer to payee }
75
+ action pay { allowZero: true, from: pending, to: paid, actor: clock, due: { at: self.dueAt }, requires self.schedule in [active], moves self.amount from payer to payee }
76
76
  action cancel { from: pending, to: cancelled, requires self.schedule in [cancelled] }
77
77
  }
78
78
  }
@@ -95,10 +95,10 @@ instrument pool(payer: party, payee: party, target: money, closes: date) {
95
95
  }
96
96
  records {
97
97
  contribution: {
98
- fields { pool: ref<parent>, payer: account of payer, amount: money }
98
+ fields { pool: ref<parent>, payer: account of payer, amount: money, remaining: money? }
99
99
  lifecycle { states: [pending, paid, refunded], initial: pending }
100
100
  action create { requires self.pool in [open] }
101
- action pay { from: pending, to: paid, actor: { party: payer }, deadline: { at: self.pool.closesAt }, requires self.pool in [open], moves self.amount from self.payer to self.pool.held }
101
+ action pay { from: pending, to: paid, actor: { party: payer }, deadline: { at: self.pool.closesAt }, requires self.pool in [open], calculate: [{ target: remaining, op: subtract, base: { field: self.pool.target }, subtract: [{ field: self.pool.held.balance }] }], requires self.amount <= self.remaining, moves self.amount from self.payer to self.pool.held }
102
102
  action refund { from: paid, to: refunded, actor: { parent: parent }, moves self.amount from self.pool.held to self.payer }
103
103
  }
104
104
  }
@@ -146,6 +146,6 @@ instrument metered(payer: party, payee: party, unit_price: money, units: integer
146
146
  fields { usageIdentity: text, unitPrice: money = unit_price, units: integer = units, amount: money = multiply(self.unitPrice, self.units) }
147
147
  lifecycle { states: [recorded, billed, cancelled], initial: recorded }
148
148
  action create { requires unique "usage_event" on [self.usageIdentity] }
149
- action bill { from: recorded, to: billed, actor: { party: payee }, moves self.amount from payer to payee fee fee }
149
+ action bill { allowZero: true, requires self.amount > "0"; from: recorded, to: billed, actor: { party: payee }, moves self.amount from payer to payee fee fee }
150
150
  action cancel { from: recorded, to: cancelled }
151
151
  }
package/std/savings.hsx CHANGED
@@ -1,6 +1,6 @@
1
1
  header savings
2
2
 
3
- instrument circle(contribution: money, members: integer(1, 366), starts: date, memberships: ref<savings.membership> = object(savings.membership)) {
3
+ instrument circle(contribution: money, members: integer(1, 60), starts: date, memberships: ref<savings.membership> = object(savings.membership)) {
4
4
  summary: "A fixed group contributes on its explicit calendar and takes the pot in order."
5
5
  fields { contribution: money = contribution, members: integer = members, startsAt: date = starts, dates: list(date, members), pot: money = multiply(self.contribution, self.members), held: account of self }
6
6
  lifecycle { states: [forming, active, closed, disbanded], initial: forming }
package/std/travel.hsx CHANGED
@@ -33,17 +33,17 @@ instrument booking(package: ref<travel.package>, buyer: party, supplier: party,
33
33
  action create {
34
34
  requires self.package in [published]
35
35
  }
36
- action deposit {
36
+ action deposit { allowZero: true, requires self.price > "0";
37
37
  from: created, to: deposit_paid, actor: { party: buyer }, deadline: { at: self.balanceAt }
38
38
  requires count of { instrument: child(cover, slice), reference: "covers", anchor: self.id, states: [pending], limit: 366 } >= 1
39
39
  moves self.deposit from buyer to self.held
40
40
  invoke: [{ selection: { instrument: child(cover, slice), reference: "covers", anchor: self.id, states: [pending], limit: 366 }, action: collect, input: {} }]
41
41
  }
42
- action balance {
42
+ action balance { allowZero: true, requires self.price > "0";
43
43
  from: deposit_paid, to: paid, actor: { party: buyer }, deadline: { at: self.balanceAt }
44
44
  moves self.balance from buyer to self.held
45
45
  }
46
- action confirm {
46
+ action confirm { allowZero: true, requires self.price > "0";
47
47
  from: paid, to: completed, actor: { party: operator }
48
48
  due: { at: self.package.departure }, deadline: { at: self.confirmBy }
49
49
  moves self.supplierCost from self.held to supplier
@@ -54,11 +54,11 @@ instrument booking(package: ref<travel.package>, buyer: party, supplier: party,
54
54
  from: paid, to: refunded, actor: clock, due: { at: self.confirmBy }
55
55
  moves self.price from self.held to buyer
56
56
  }
57
- action cancel_early {
57
+ action cancel_early { allowZero: true, requires self.price > "0";
58
58
  from: [deposit_paid, paid], to: cancelled, actor: { party: buyer }, deadline: { at: self.earlyAt }
59
59
  moves self.held.balance from self.held to buyer
60
60
  }
61
- action cancel_middle {
61
+ action cancel_middle { allowZero: true, requires self.price > "0";
62
62
  from: [deposit_paid, paid], to: cancelled, actor: { party: buyer }, due: { at: self.earlyAt }, deadline: { at: self.lateAt }
63
63
  calculate: [
64
64
  { target: refund, op: "subtract", base: { field: self.held.balance }, subtract: [{ field: self.deposit }] }
@@ -71,7 +71,7 @@ instrument booking(package: ref<travel.package>, buyer: party, supplier: party,
71
71
  moves self.retainedTax from self.held to programTax
72
72
  moves self.retainedNet from self.held to programOperator
73
73
  }
74
- action cancel_late {
74
+ action cancel_late { allowZero: true, requires self.price > "0";
75
75
  from: [deposit_paid, paid], to: cancelled, actor: { party: buyer }, due: { at: self.lateAt }
76
76
  calculate: [
77
77
  { target: retainedMargin, op: "subtract", base: { field: self.held.balance }, subtract: [{ field: self.latePenalty }] }
@@ -82,7 +82,7 @@ instrument booking(package: ref<travel.package>, buyer: party, supplier: party,
82
82
  moves self.retainedTax from self.held to programTax
83
83
  moves self.retainedNet from self.held to programOperator
84
84
  }
85
- action default_balance {
85
+ action default_balance { allowZero: true, requires self.price > "0";
86
86
  from: deposit_paid, to: cancelled, actor: clock, due: { at: self.balanceAt }
87
87
  calculate: [
88
88
  { target: retainedMargin, op: "subtract", base: { field: self.held.balance }, subtract: [{ field: self.latePenalty }] }