@hyperscale0/hsx 1.0.125 → 1.0.128

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.
@@ -0,0 +1,75 @@
1
+ header booking
2
+
3
+ // Economics: Deposit and balance payments and every refund are principal. Completion
4
+ // and any forfeit are earnings for the company, or pass the customer's cash through
5
+ // to a bound outside operator.
6
+
7
+ // Expose create, pay_deposit and pay_balance for the booking path, and
8
+ // cancel_early, cancel_middle and cancel_late for the customer. Create captures
9
+ // subject departure, price, deposit and balance, and refuses a deposit and balance
10
+ // that do not add up to the price. The balance is payable only after the deposit
11
+ // and before balance_due ahead of departure, so a cancelled booking never offers
12
+ // it. cancel_early runs until early_until before departure and returns everything
13
+ // held. cancel_middle runs from then until late_from; cancel_late runs from then
14
+ // until departure, even when the clock's complete runs late. They keep
15
+ // middle_forfeit or late_forfeit of the held money and return the rest. Both
16
+ // forfeits default to 0%, so nothing is kept unless the author sets them. At
17
+ // departure the clock completes a paid booking and pays operator, and lapses an
18
+ // unpaid one on cancel_late's terms, so nothing stays held after departure.
19
+
20
+ // Cost: create declares one owned account. Each payment declares one transfer;
21
+ // cancellation and completion move the held balance. Run hsx cost for the
22
+ // composed counts.
23
+
24
+ instrument reservation(customer: party = party(person), operator: party = programOperator, early_until: duration = 30d, late_from: duration = 15d, balance_due: duration = 14d, middle_forfeit: percent = 0%, late_forfeit: percent = 0%) {
25
+ revisioned: true
26
+ summary: "Take a deposit and a balance for a dated booking, with cancellation windows before departure."
27
+ fields {
28
+ customer: account of customer, operator: account of operator, held: account of self
29
+ departure: date, price: money = subject.price
30
+ deposit: money = subject.deposit, balance: money = subject.balance
31
+ split: money = sum(self.deposit, self.balance)
32
+ balanceAt: date = before(self.departure, balance_due)
33
+ earlyAt: date = before(self.departure, early_until)
34
+ lateAt: date = before(self.departure, late_from)
35
+ refund: money?, forfeit: money?
36
+ }
37
+ lifecycle { states: [reserved, deposit_paid, paid, completed, cancelled], initial: reserved }
38
+ action create {
39
+ subject { departure: date, price: money, deposit: money, balance: money }
40
+ actor: { party: customer }
41
+ requires self.departure > self.now
42
+ requires self.split == self.price
43
+ }
44
+ action pay_deposit {
45
+ from: reserved, to: deposit_paid, actor: { party: customer }
46
+ moves self.deposit from customer to self.held economics { purpose: principal, sourceParty: customer }
47
+ }
48
+ action pay_balance {
49
+ from: deposit_paid, to: paid, actor: { party: customer }, deadline: { at: self.balanceAt }
50
+ moves self.balance from customer to self.held economics { purpose: principal, sourceParty: customer }
51
+ }
52
+ action complete {
53
+ from: paid, to: completed, actor: clock, due: { at: self.departure }
54
+ moves self.held.balance from self.held to operator economics { recipient: operator, company: { purpose: earning, sourceParty: customer }, participant: { purpose: pass_through, sourceParty: programOperator } }
55
+ }
56
+ action cancel_early { allowZero: true,
57
+ from: [reserved, deposit_paid, paid], to: cancelled, actor: { party: customer }, deadline: { at: self.earlyAt }
58
+ moves self.held.balance from self.held to customer economics { purpose: principal, sourceParty: programOperator }
59
+ }
60
+ action cancel_middle { allowZero: true,
61
+ from: [reserved, deposit_paid, paid], to: cancelled, actor: { party: customer }, due: { at: self.earlyAt }, deadline: { at: self.lateAt }
62
+ calculate: [{ target: forfeit, op: rate, base: { field: self.held.balance }, bps: { literal: middle_forfeit }, rounding: floor }, { target: refund, op: subtract, base: { field: self.held.balance }, subtract: [{ field: self.forfeit }] }]
63
+ moves self.refund from self.held to customer economics { purpose: principal, sourceParty: programOperator }; moves self.forfeit from self.held to operator economics { recipient: operator, company: { purpose: earning, sourceParty: customer }, participant: { purpose: pass_through, sourceParty: programOperator } }
64
+ }
65
+ action cancel_late { allowZero: true,
66
+ from: [reserved, deposit_paid, paid], to: cancelled, actor: { party: customer }, due: { at: self.lateAt }, deadline: { at: self.departure }
67
+ calculate: [{ target: forfeit, op: rate, base: { field: self.held.balance }, bps: { literal: late_forfeit }, rounding: floor }, { target: refund, op: subtract, base: { field: self.held.balance }, subtract: [{ field: self.forfeit }] }]
68
+ moves self.refund from self.held to customer economics { purpose: principal, sourceParty: programOperator }; moves self.forfeit from self.held to operator economics { recipient: operator, company: { purpose: earning, sourceParty: customer }, participant: { purpose: pass_through, sourceParty: programOperator } }
69
+ }
70
+ action lapse { allowZero: true,
71
+ from: [reserved, deposit_paid], to: cancelled, actor: clock, due: { at: self.departure }
72
+ calculate: [{ target: forfeit, op: rate, base: { field: self.held.balance }, bps: { literal: late_forfeit }, rounding: floor }, { target: refund, op: subtract, base: { field: self.held.balance }, subtract: [{ field: self.forfeit }] }]
73
+ moves self.refund from self.held to customer economics { purpose: principal, sourceParty: programOperator }; moves self.forfeit from self.held to operator economics { recipient: operator, company: { purpose: earning, sourceParty: customer }, participant: { purpose: pass_through, sourceParty: programOperator } }
74
+ }
75
+ }
package/std/escrow.hsx CHANGED
@@ -1,13 +1,21 @@
1
1
  header escrow
2
2
 
3
3
  // Economics: Funding and unspent returns are principal. Acceptance realizes earnings for
4
- // the company or a payout to a bound outside seller; tax is pass-through.
4
+ // the company; the payer's cash passes through to a bound outside seller, less the
5
+ // fee the company earns. Tax is pass-through.
5
6
 
6
7
  // Expose create, fund, deliver and accept for the purchase path. Create captures
7
8
  // subject.price; acceptance pays payee less the configured fee, not payer.
8
9
  // Expose dispute, verify_return and refund separately for the return path.
9
- // Payer disputes delivered goods; payee verifies return evidence from disputed;
10
- // payee refunds the entire held price from return_verified by default.
10
+ // Payer disputes delivered goods; payee verifies return evidence from disputed.
11
+ // Payee refunds the whole held balance before delivery, or from return_verified
12
+ // by default after a dispute. While a signed, active or paid financing plan funds
13
+ // the hold, both refunds refuse: the plan's unwind returns the lender's principal
14
+ // first, then refund returns the rest. Unwind needs return_verified, so a
15
+ // financed hold cannot be refunded before delivery.
16
+ // Expose refund_undelivered to let the payer take the whole held price back once
17
+ // deliver_within has passed since funding without delivery. It is a payer action
18
+ // that becomes available at that time; nothing runs it automatically.
11
19
  // Acceptance expiry enters disputed, never released. Neither verify_return nor
12
20
  // refund becomes public merely because dispute is exposed. Clock actions remain
13
21
  // internal. Expose cancel for a pending hold, including a checkout-funded hold;
@@ -18,12 +26,12 @@ header escrow
18
26
  // transfer; accept expands the configured fee and tax into moves. Run hsx cost
19
27
  // on the composed program for instruction counts. The executor supplies prices.
20
28
 
21
- instrument hold(payer: party = party(person), payee: party = programOperator, accept_within: duration = 48h, fee: fee = { seller: 0%, tax: 0% }, dispute: policy = { refund_after: return_verified }) {
29
+ instrument hold(payer: party = party(person), payee: party = programOperator, accept_within: duration = 48h, deliver_within: duration = 14d, fee: fee = { seller: 0%, tax: 0% }, dispute: policy = { refund_after: return_verified }) {
22
30
  revisioned: true
23
31
  summary: "Hold the subject price through delivery, acceptance and verified return."
24
32
  fields {
25
33
  payer: account of payer, payee: account of payee
26
- held: account of self, deliveredAt: date?
34
+ held: account of self, fundedAt: date?, deliveredAt: date?
27
35
  price: money = subject.price
28
36
  }
29
37
  lifecycle { states: [pending, funded, delivered, disputed, return_verified, released, refunded, cancelled], initial: pending }
@@ -34,11 +42,13 @@ instrument hold(payer: party = party(person), payee: party = programOperator, ac
34
42
  action fund {
35
43
  from: pending, to: funded, actor: { party: payer }
36
44
  subject { price: money }
45
+ set: { fundedAt: { field: self.now } }
37
46
  moves self.price from payer to self.held economics { purpose: principal, sourceParty: payer }
38
47
  }
39
48
  action confirm {
40
49
  from: pending, to: funded
41
50
  requires self.held.balance == self.price
51
+ set: { fundedAt: { field: self.now } }
42
52
  }
43
53
  action deliver {
44
54
  from: funded, to: delivered, actor: { party: payee }
@@ -48,7 +58,7 @@ instrument hold(payer: party = party(person), payee: party = programOperator, ac
48
58
  from: delivered, to: released, actor: { party: payer }
49
59
  input { acceptanceReference: text }
50
60
  deadline: { at: self.deliveredAt, offset: accept_within }
51
- moves self.price from self.held to payee fee fee economics { payee: { recipient: payee, company: { purpose: earning, sourceParty: payer }, participant: { purpose: participant_payout, sourceParty: programOperator } }, fee: { purpose: earning, sourceParty: payer }, tax: { purpose: pass_through, sourceParty: programOperator } }
61
+ moves self.price from self.held to payee fee fee economics { payee: { recipient: payee, company: { purpose: earning, sourceParty: payer }, participant: { purpose: pass_through, sourceParty: programOperator } }, fee: { purpose: earning, sourceParty: payer }, tax: { purpose: pass_through, sourceParty: programOperator } }
52
62
  }
53
63
  action expire_acceptance {
54
64
  from: delivered, to: disputed, actor: clock
@@ -63,7 +73,14 @@ instrument hold(payer: party = party(person), payee: party = programOperator, ac
63
73
  input { returnEvidence: text }
64
74
  }
65
75
  action refund {
66
- from: dispute.refund_after, to: refunded, actor: { party: payee }
76
+ from: [funded, dispute.refund_after], to: refunded, actor: { party: payee }
77
+ requires count of { instrument: all(financing.installments), reference: "funds", anchor: self.id, states: [signed, active, paid], limit: 1 } == 0
78
+ moves self.held.balance from self.held to payer economics { purpose: principal, sourceParty: programOperator }
79
+ }
80
+ action refund_undelivered {
81
+ from: funded, to: refunded, actor: { party: payer }
82
+ due: { at: self.fundedAt, offset: deliver_within }
83
+ requires count of { instrument: all(financing.installments), reference: "funds", anchor: self.id, states: [signed, active, paid], limit: 1 } == 0
67
84
  moves self.price from self.held to payer economics { purpose: principal, sourceParty: programOperator }
68
85
  }
69
86
  action cancel { allowZero: true,
package/std/financing.hsx CHANGED
@@ -2,9 +2,14 @@ header financing
2
2
 
3
3
  // Economics: Principal advances and repayments are excluded from earnings. Claims, waivers,
4
4
  // write-offs and allocations are internal. Paid profit and company charges are
5
- // earnings; receipt refunds reverse captured cash. Outside recovery recipients
6
- // receive payouts from held company cash, or pass-through direct customer cash.
5
+ // earnings; receipt refunds reverse captured cash. Borrower cash paid to outside
6
+ // capital or recovery recipients passes through, whether held first or paid direct.
7
7
 
8
+ // Pricing is required. amortizing reads profit_rate as an annual rate on the
9
+ // declining balance: equal monthly instalments, the last absorbing rounding.
10
+ // flat_total reads it as one charge on principal for the whole term (murabaha,
11
+ // fixed-fee pay-later). 12,000 at 18% over 12 months costs 13,201.95 amortizing
12
+ // and 14,160 flat_total.
8
13
  // Expose create and sign on installments, then collect_down_payment as checkout
9
14
  // for disburse_to: funds. Checkout requires a signed plan and pending escrow;
10
15
  // it tops up only the missing down payment, without advancing lender principal.
@@ -12,13 +17,22 @@ header financing
12
17
  // the fully funded hold. Existing escrow credit counts once toward the down payment.
13
18
  // Disburse first credits an active marketplace.reservation on the same hold,
14
19
  // so list the plan in its converters; the deposit then counts as escrow credit.
15
- // Borrower-directed disbursement excludes collect_down_payment. A down_payment
20
+ // disburse_to: seller pays principal to the seller party at activation (pay later
21
+ // with the merchant paid on day one). For a return, capital refunds each paid
22
+ // payment to the borrower, then runs unwind: it takes principal back to capital and
23
+ // cancels open slices with their claims. Seller mode debits the seller; funds mode
24
+ // needs the hold in return_verified and debits the hold, after which the payee's
25
+ // escrow refund returns only the down payment. Escrow refund refuses while a live
26
+ // plan funds the hold. Unwind needs no assessed or collected late charges; waive
27
+ // them first.
28
+ // Borrower- and seller-directed disbursement exclude collect_down_payment. A down_payment
16
29
  // there reduces principal but does not collect cash. Price is subject metadata,
17
30
  // not the borrower limit. The escrow payer and price must match the plan.
18
31
  // Expose escrow cancel to return checkout cash if financing is abandoned; plan
19
32
  // void alone cancels the agreement and does not refund its separate escrow.
20
33
  // Approve borrower and portfolio limits before disbursement. Create builds the
21
- // dated slice records; payments need a separate payment record and pay action.
34
+ // dated slice records from future, strictly increasing dates; disbursement
35
+ // refuses once a slice is due. Payments need a separate payment record and pay action.
22
36
  // Expose parent payoff separately and create a current, unexpired payoff_quote.
23
37
  // Expose late_charge.create as propose_assessment before the clock can assess;
24
38
  // provide matching plan, slice and borrower. Fine is fixed money, not a percentage.
@@ -33,12 +47,17 @@ header financing
33
47
  // bounded invocations across slices and assessments. Month and charge limits affect
34
48
  // the bound. Run hsx cost on the selected policies; profit is not a platform tariff.
35
49
 
36
- 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)) {
50
+ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_total), profit_rate: percent, charge_limit: integer(1, 3) = 3, disburse_to: enum(funds, borrower, seller) = funds, seller: party?, 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)) {
37
51
  dependencies {
38
52
  funds {
39
53
  selector: disburse_to, is: funds
40
54
  message: "Attachment `{attachment}` disburses into a hold, but `funds` is not bound."
41
- fix: "Bind `funds: sale` to an escrow attachment. Choose `disburse_to: borrower` only if the borrower should receive the money directly."
55
+ fix: "Bind `funds: sale` to an escrow attachment. Choose `disburse_to: seller` to pay a seller on activation, or `disburse_to: borrower` only if the borrower should receive the money directly."
56
+ }
57
+ seller {
58
+ selector: disburse_to, is: seller
59
+ message: "Attachment `{attachment}` pays a seller, but `seller` is not bound."
60
+ fix: "Bind `seller: merchant` to the declared business that receives the principal."
42
61
  }
43
62
  }
44
63
  revisioned: true
@@ -52,8 +71,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
52
71
  price: money = subject.price
53
72
  downPayment: money = rate(self.price, down_payment)
54
73
  principal: money = subtract(self.price, self.downPayment)
55
- profit: money = rate(self.principal, profit)
56
- total: money = sum(self.principal, self.profit)
74
+ flatProfit: money?
57
75
  }
58
76
  lifecycle { states: [quoted, signed, active, paid, written_off, cancelled], initial: quoted }
59
77
  invariants count of { instrument: all(financing.installments), reference: "borrowerLimit", anchor: self.borrowerLimit, states: [active], limit: 366 } <= self.borrowerLimit.activePlans
@@ -62,8 +80,9 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
62
80
  action create {
63
81
  subject { price: money }
64
82
  requires self.borrowerLimit.borrower == self.borrower
83
+ when pricing is flat_total { calculate: [{ target: flatProfit, op: rate, base: { field: self.principal }, bps: { literal: profit_rate }, rounding: floor }] }
65
84
  when disburse_to is funds { requires self.borrower == self.funds.payer; requires self.price == self.funds.price }
66
- invoke: [{ range: { count: { literal: months }, maximum: months, bind: position }, instrument: slice, action: create, input: { plan: { field: self.id }, borrowerLimit: { field: self.borrowerLimit }, portfolioLimit: { field: self.portfolioLimit }, position: { field: position } } }]
85
+ invoke: [{ range: { count: { literal: months }, maximum: months, bind: position }, instrument: slice, action: create, input: { plan: { field: self.id }, borrowerLimit: { field: self.borrowerLimit }, portfolioLimit: { field: self.portfolioLimit }, position: { field: position } } }, { selection: { instrument: slice, reference: "plan", anchor: self.id, states: [draft], limit: months, where: { first: { literal: 0 } } }, action: check_order, input: {} }]
67
86
  }
68
87
  action sign {
69
88
  from: quoted, to: signed, actor: { party: borrower }, input { signedOffer: text, affordabilityReference: text }
@@ -72,6 +91,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
72
91
  action collect_down_payment { allowZero: true,
73
92
  from: signed, to: signed, actor: { party: borrower }
74
93
  when disburse_to is borrower { requires true == false }
94
+ when disburse_to is seller { requires true == false }
75
95
  when disburse_to is funds {
76
96
  requires self.funds in [pending]
77
97
  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 }] }]
@@ -87,6 +107,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
87
107
  invoke: [{ selection: { instrument: all(marketplace.reservation), reference: "funds", anchor: self.funds, states: [active], limit: 1 }, action: credit, input: { funds: { field: self.funds }, contribution: { field: self.downPayment } } }, { reference: self.id, action: fund_down_payment, input: {} }]
88
108
  }
89
109
  when disburse_to is borrower { moves self.principal from self.capital to self.borrower economics { purpose: principal, sourceParty: capital } }
110
+ when disburse_to is seller { moves self.principal from self.capital to seller economics { purpose: principal, sourceParty: capital } }
90
111
  invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [draft], limit: 366, order: [position] }, action: activate, input: {} }]
91
112
  when disburse_to is funds { invoke: [{ reference: self.funds, action: confirm, input: {} }] }
92
113
  }
@@ -136,6 +157,17 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
136
157
  }
137
158
  action cancel { from: quoted, to: cancelled }
138
159
  action void { from: signed, to: cancelled, actor: { party: borrower } }
160
+ action unwind { allowZero: true,
161
+ from: active, to: cancelled, actor: { party: capital }
162
+ when disburse_to is borrower { requires true == false }
163
+ when disburse_to is funds { requires self.funds in [return_verified] }
164
+ requires count of { instrument: payment, reference: "plan", anchor: self.id, states: [paid], limit: 366 } == 0
165
+ requires count of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 } == 0
166
+ requires count of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 } == 0
167
+ when disburse_to is funds { moves self.principal from self.funds.held to self.capital economics { purpose: principal, sourceParty: capital } }
168
+ when disburse_to is seller { moves self.principal from seller to self.capital economics { purpose: principal, sourceParty: seller } }
169
+ invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366, order: [position] }, action: cancel, input: {} }]
170
+ }
139
171
  records {
140
172
  write_off_request: {
141
173
  fields { plan: ref<parent>, overdueSlice: ref<slice> }
@@ -251,31 +283,48 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
251
283
  revisioned: true
252
284
  fields {
253
285
  plan: ref<parent>, borrowerLimit: ref<limits>, portfolioLimit: ref<portfolio>, position: integer(1, months)
254
- originalDueAt: date = at(self.plan.dates, self.position), dueAt: date?, nextDueAt: date?, previousDueAt: date?, previousPosition: integer?, previousDate: date?, extensionLimit: date = after(self.originalDueAt, max_extension), lossEligibleAt: date?
255
- principalBase: money = divide(self.plan.principal, months)
256
- principalAllocated: money = multiply(self.principalBase, months)
257
- principalRemainder: money = subtract(self.plan.principal, self.principalAllocated)
258
- profitBase: money = divide(self.plan.profit, months)
259
- profitAllocated: money = multiply(self.profitBase, months)
260
- profitRemainder: money = subtract(self.plan.profit, self.profitAllocated)
286
+ originalDueAt: date = at(self.plan.dates, self.position), dueAt: date?, nextDueAt: date?, previousDueAt: date?, prior: integer = subtract(self.position, 1), previousPosition: integer = sum(self.prior, self.first), previousDate: date = at(self.plan.dates, self.previousPosition), extensionLimit: date = after(self.originalDueAt, max_extension), lossEligibleAt: date?
261
287
  first: integer = divide(1, self.position)
262
- principalAdjustment: money = multiply(self.principalRemainder, self.first)
263
- profitAdjustment: money = multiply(self.profitRemainder, self.first)
264
- principal: money = sum(self.principalBase, self.principalAdjustment)
265
- profit: money = sum(self.profitBase, self.profitAdjustment)
288
+ principalBase: money?, principalAllocated: money?, principalRemainder: money?, principalAdjustment: money?
289
+ profitBase: money?, profitAllocated: money?, profitRemainder: money?, profitAdjustment: money?
290
+ principal: money, profit: money, instalment: money
266
291
  principalReceivable: account(self, claim), profitReceivable: account(self, claim), profitEarned: account(self, claim)
267
292
  payoffIdentity: text?, payoffRebate: money?, payoffProfitTotal: money?, payoffProfit: money?
268
- paymentBudget: money?, principalPayment: money?, principalBasePayment: money?, principalRatio: money?, profitPayment: money?, profitBudget: money?, collectibleProfit: money?, collectibleTotal: money?, ratioDenominator: integer = sum(10000, profit)
293
+ paymentBudget: money?, principalPayment: money?, principalBasePayment: money?, principalRatio: money?, profitPayment: money?, profitBudget: money?, collectibleProfit: money?, collectibleTotal: money?, ratioDenominator: integer = sum(10000, profit_rate)
269
294
  debt: account(borrower, claim, contra, "debt"), loss: account(capital, claim, "loss")
270
295
  }
271
- lifecycle { states: [draft, pending, due, paid, written_off], initial: draft }
296
+ lifecycle { states: [draft, pending, due, paid, written_off, cancelled], initial: draft }
272
297
  action create {
273
298
  actor: { parent: parent }
274
299
  input { plan: ref<parent>, borrowerLimit: ref<limits>, portfolioLimit: ref<portfolio>, position: integer(1, months) }
275
300
  set: { dueAt: { field: self.originalDueAt } }
301
+ when pricing is flat_total {
302
+ calculate: [
303
+ { target: principalBase, op: divide, amount: { field: self.plan.principal }, divisor: { literal: months }, rounding: floor }
304
+ { target: principalAllocated, op: multiply, amount: { field: self.principalBase }, units: { literal: months } }
305
+ { target: principalRemainder, op: subtract, base: { field: self.plan.principal }, subtract: [{ field: self.principalAllocated }] }
306
+ { target: principalAdjustment, op: multiply, amount: { field: self.principalRemainder }, units: { field: self.first } }
307
+ { target: principal, op: sum, values: [{ field: self.principalBase }, { field: self.principalAdjustment }] }
308
+ { target: profitBase, op: divide, amount: { field: self.plan.flatProfit }, divisor: { literal: months }, rounding: floor }
309
+ { target: profitAllocated, op: multiply, amount: { field: self.profitBase }, units: { literal: months } }
310
+ { target: profitRemainder, op: subtract, base: { field: self.plan.flatProfit }, subtract: [{ field: self.profitAllocated }] }
311
+ { target: profitAdjustment, op: multiply, amount: { field: self.profitRemainder }, units: { field: self.first } }
312
+ { target: profit, op: sum, values: [{ field: self.profitBase }, { field: self.profitAdjustment }] }
313
+ { target: instalment, op: sum, values: [{ field: self.principal }, { field: self.profit }] }
314
+ ]
315
+ }
316
+ when pricing is amortizing {
317
+ calculate: [
318
+ { target: principal, op: annuity, amount: { field: self.plan.principal }, rate: { literal: profit_rate }, periods: { literal: months }, position: { field: self.position }, part: principal }
319
+ { target: profit, op: annuity, amount: { field: self.plan.principal }, rate: { literal: profit_rate }, periods: { literal: months }, position: { field: self.position }, part: interest }
320
+ { target: instalment, op: sum, values: [{ field: self.principal }, { field: self.profit }] }
321
+ ]
322
+ }
276
323
  invoke: [{ reference: self.id, action: refresh_loss_date, input: {} }]
277
324
  requires self.plan in [quoted]; requires unique "signed_position" on [self.plan, self.position]; requires self.borrowerLimit == self.plan.borrowerLimit; requires self.portfolioLimit == self.plan.portfolioLimit
325
+ requires self.originalDueAt > self.now; requires self.originalDueAt >= self.previousDate
278
326
  }
327
+ action check_order { from: draft, to: draft, actor: { parent: parent }, requires self.originalDueAt > self.previousDate }
279
328
  action quote_pending {
280
329
  from: pending, to: pending, actor: { parent: payoff_quote }, input { quote: ref<payoff_quote> }
281
330
  requires input.quote.plan == self.plan
@@ -310,6 +359,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
310
359
  }
311
360
  action activate { allowZero: true,
312
361
  from: draft, to: pending, actor: { parent: parent }
362
+ requires self.dueAt > self.now
313
363
  moves self.principal from self.debt to self.principalReceivable economics { purpose: internal, sourceParty: borrower }
314
364
  moves self.profit from self.debt to self.profitReceivable economics { purpose: internal, sourceParty: borrower }
315
365
  when profit_earned is at_disbursement { moves self.profit from self.profitReceivable to self.profitEarned economics { purpose: internal, sourceParty: programOperator } }
@@ -356,7 +406,10 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
356
406
  calculate: [
357
407
  { target: collectibleTotal, op: sum, values: [{ field: self.principalReceivable.balance }, { field: self.collectibleProfit }] }
358
408
  { target: paymentBudget, op: minimum, values: [{ field: input.payment.held.balance }, { field: self.collectibleTotal }] }
359
- { target: principalRatio, op: ratio, amount: { field: self.paymentBudget }, numerator: { literal: 10000 }, denominator: { field: self.ratioDenominator }, rounding: floor }
409
+ ]
410
+ when pricing is flat_total { calculate: [{ target: principalRatio, op: ratio, amount: { field: self.paymentBudget }, numerator: { literal: 10000 }, denominator: { field: self.ratioDenominator }, rounding: floor }] }
411
+ when pricing is amortizing { calculate: [{ target: principalRatio, op: ratio, amount: { field: self.paymentBudget }, numerator: { field: self.principal }, denominator: { field: self.instalment }, rounding: floor }] }
412
+ calculate: [
360
413
  { target: principalBasePayment, op: minimum, values: [{ field: self.principalRatio }, { field: self.principalReceivable.balance }] }
361
414
  { target: profitBudget, op: subtract, base: { field: self.paymentBudget }, subtract: [{ field: self.principalBasePayment }] }
362
415
  { target: profitPayment, op: minimum, values: [{ field: self.profitBudget }, { field: self.collectibleProfit }] }
@@ -387,6 +440,10 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
387
440
  from: [pending, due], to: written_off, actor: { parent: parent }
388
441
  moves self.principalReceivable.balance from self.principalReceivable to self.loss economics { purpose: internal, sourceParty: programOperator }; moves self.profitReceivable.balance from self.profitReceivable to self.debt economics { purpose: internal, sourceParty: programOperator }; moves self.profitEarned.balance from self.profitEarned to self.loss economics { purpose: internal, sourceParty: programOperator }
389
442
  }
443
+ action cancel { allowZero: true,
444
+ from: [pending, due], to: cancelled, actor: { parent: parent }
445
+ moves self.principalReceivable.balance from self.principalReceivable to self.debt economics { purpose: internal, sourceParty: programOperator }; moves self.profitReceivable.balance from self.profitReceivable to self.debt economics { purpose: internal, sourceParty: programOperator }; moves self.profitEarned.balance from self.profitEarned to self.debt economics { purpose: internal, sourceParty: programOperator }
446
+ }
390
447
  }
391
448
  recognition: {
392
449
  fields { slice: ref<slice>, dueAt: date = after(self.slice.dueAt, 0) }
@@ -467,7 +524,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
467
524
  actor: { parent: slice }
468
525
  input { slice: ref<slice>, payment: ref<payment>, limit: money }
469
526
  requires unique "profit_receipt" on [self.slice, self.payment]
470
- moves self.amount from self.payment.held to self.slice.plan.profitIncome capture "cashReceipt" economics { recipient: capital, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: participant_payout, sourceParty: programOperator } }
527
+ moves self.amount from self.payment.held to self.slice.plan.profitIncome capture "cashReceipt" economics { recipient: capital, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: programOperator } }
471
528
  when profit_earned is on_payment { moves self.amount from self.slice.profitReceivable to self.slice.debt economics { purpose: internal, sourceParty: programOperator } }
472
529
  when profit_earned is by_schedule { moves self.amount from self.slice.profitEarned to self.slice.debt economics { purpose: internal, sourceParty: programOperator } }
473
530
  when profit_earned is at_disbursement { moves self.amount from self.slice.profitEarned to self.slice.debt economics { purpose: internal, sourceParty: programOperator } }
@@ -555,7 +612,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
555
612
  receipt: {
556
613
  fields { cashReceipt: text?, assessment: ref<parent>, payment: ref<on.payment>, amount: money? }
557
614
  lifecycle { states: [paid, refunded], initial: paid }
558
- 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 capture "cashReceipt" economics { recipient: fines_to, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: participant_payout, sourceParty: programOperator } }; moves self.amount from self.assessment.receivable to self.assessment.debt economics { purpose: internal, sourceParty: programOperator } }
615
+ 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 capture "cashReceipt" economics { recipient: fines_to, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: programOperator } }; moves self.amount from self.assessment.receivable to self.assessment.debt economics { purpose: internal, sourceParty: programOperator } }
559
616
  action refund { allowZero: true, from: paid, to: refunded, actor: { parent: parent }, moves self.amount from fines_to to self.payment.plan.borrower economics { recipient: fines_to, company: { purpose: earning, sourceParty: fines_to, reversalOf: self.cashReceipt }, participant: unclassified }; moves self.amount from self.assessment.debt to self.assessment.receivable economics { purpose: internal, sourceParty: borrower } }
560
617
  }
561
618
 
@@ -622,7 +679,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
622
679
  receipt: {
623
680
  fields { cashReceipt: text?, assessment: ref<parent>, payment: ref<on.payment>, amount: money? }
624
681
  lifecycle { states: [paid, refunded], initial: paid }
625
- 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 capture "cashReceipt" economics { recipient: costs_to, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: participant_payout, sourceParty: programOperator } }; moves self.amount from self.assessment.receivable to self.assessment.debt economics { purpose: internal, sourceParty: programOperator } }
682
+ 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 capture "cashReceipt" economics { recipient: costs_to, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: programOperator } }; moves self.amount from self.assessment.receivable to self.assessment.debt economics { purpose: internal, sourceParty: programOperator } }
626
683
  action refund { allowZero: true, from: paid, to: refunded, actor: { parent: parent }, moves self.amount from costs_to to self.payment.plan.borrower economics { recipient: costs_to, company: { purpose: earning, sourceParty: costs_to, reversalOf: self.cashReceipt }, participant: unclassified }; moves self.amount from self.assessment.debt to self.assessment.receivable economics { purpose: internal, sourceParty: borrower } }
627
684
  }
628
685
  }
package/std/insurance.hsx CHANGED
@@ -4,7 +4,11 @@ header insurance
4
4
  // Outside broker commissions are pass-through. Adapter-owned premium and claim
5
5
  // accounts lack an authored source party, so their moves remain unclassified.
6
6
 
7
- // Expose cover.create, activate and cancel separately. Activation binds adapter;
7
+ // Expose cover.create, activate and cancel separately. The adapter names a
8
+ // registered boundary adapter (conformance_boundary in sandbox). covers is an
9
+ // optional link to a sibling attachment; the attached object is already the
10
+ // insured thing. claim_limit is the sum insured: approved and paid claims on one
11
+ // cover never total more. Activation binds adapter;
8
12
  // each slice needs a separately created unique period and an active cover to collect.
9
13
  // Collection debits holder for insurerPremium to the insurer and commission to
10
14
  // broker as two moves. Broker defaults to programOperator; bind a declared business
@@ -23,9 +27,9 @@ header insurance
23
27
  // reserves once; pay posts once and expiry voids once. Cover cancellation adds
24
28
  // bounded slice invocations. Run hsx cost for composed counts, not provider prices.
25
29
 
26
- instrument cover(holder: party, adapter: text, commission: percent = 0%, broker: party = programOperator, covers: ref, premium: money = runtime) {
30
+ instrument cover(holder: party, adapter: text, commission: percent = 0%, broker: party = programOperator, covers: ref?, premium: money = runtime, claim_limit: money = runtime) {
27
31
  summary: "Recurring cover bound to an outside insurer across an ADL boundary."
28
- fields { covers: ref<covers>, holder: account of holder, insurer: account(adapter(adapter), cash, "premium"), expiresAt: date }
32
+ fields { covers: ref<covers>?, holder: account of holder, insurer: account(adapter(adapter), cash, "premium"), expiresAt: date, claimLimit: money = claim_limit }
29
33
  lifecycle { states: [pending, active, cancelled, expired], initial: pending }
30
34
  action create {}
31
35
  action activate {
@@ -41,13 +45,12 @@ instrument cover(holder: party, adapter: text, commission: percent = 0%, broker:
41
45
  slice: {
42
46
  summary: "A whole premium period, collected once and refundable only before it starts."
43
47
  fields {
44
- cover: ref<parent>, covers: ref<covers>, premium: money = premium, startsAt: date, endsAt: date
48
+ cover: ref<parent>, premium: money = premium, startsAt: date, endsAt: date
45
49
  commission: money = rate(self.premium, commission)
46
50
  insurerPremium: money = subtract(self.premium, self.commission), commissionReceipt: text?
47
51
  }
48
52
  lifecycle { states: [pending, paid, active, expired, refunded], initial: pending }
49
53
  action create {
50
- requires self.covers == self.cover.covers
51
54
  requires self.startsAt < self.endsAt
52
55
  requires unique "premium_period" on [self.cover, self.startsAt, self.endsAt]
53
56
  }
@@ -76,6 +79,7 @@ instrument claim(cover: ref<insurance.cover>, inspector: party) {
76
79
  summary: "A claim decision reserves one claim payment before settlement."
77
80
  fields { cover: ref<cover>, amount: money, expiresAt: date, evidence: text, reservation: text? }
78
81
  lifecycle { states: [submitted, approved, paid, denied, expired], initial: submitted }
82
+ invariants sum "amount" of { instrument: current(), reference: "cover", anchor: self.cover, states: [approved, paid], limit: 366 } <= self.cover.claimLimit
79
83
  action create { requires self.cover in [active] }
80
84
  action approve {
81
85
  from: submitted, to: approved
@@ -1,7 +1,7 @@
1
1
  header marketplace
2
2
 
3
3
  // Economics: Deposit funding, escrow credit and returns are principal. Retention is
4
- // earning for the company or a payout to a bound outside recipient.
4
+ // earning for the company or passes the buyer's cash through to a bound outside recipient.
5
5
 
6
6
  // Listing, order and reservation are separate agreements. Expose their create
7
7
  // and decision actions individually. Order fulfillment does not move escrow cash.
@@ -100,7 +100,7 @@ instrument reservation(listing: ref<marketplace.listing>, funds: ref<escrow.hold
100
100
  from: active, to: cancelled, actor: { party: buyer }, input { reason: text }
101
101
  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 }] }]
102
102
  set: { reason: { field: input.reason }, dispositionAt: { field: self.now } }
103
- moves self.refund from self.held to self.buyer economics { purpose: principal, sourceParty: programOperator }; moves self.retained from self.held to retained_to economics { recipient: retained_to, company: { purpose: earning, sourceParty: buyer }, participant: { purpose: participant_payout, sourceParty: programOperator } }
103
+ moves self.refund from self.held to self.buyer economics { purpose: principal, sourceParty: programOperator }; moves self.retained from self.held to retained_to economics { recipient: retained_to, company: { purpose: earning, sourceParty: buyer }, participant: { purpose: pass_through, sourceParty: programOperator } }
104
104
  }
105
105
  action seller_cancel {
106
106
  from: active, to: cancelled, actor: { party: seller }, input { reason: text }
@@ -117,7 +117,7 @@ instrument reservation(listing: ref<marketplace.listing>, funds: ref<escrow.hold
117
117
  from: active, to: expired, actor: clock, due: { at: self.expiresAt }
118
118
  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 }] }]
119
119
  set: { dispositionAt: { field: self.now } }
120
- moves self.refund from self.held to self.buyer economics { purpose: principal, sourceParty: programOperator }; moves self.retained from self.held to retained_to economics { recipient: retained_to, company: { purpose: earning, sourceParty: buyer }, participant: { purpose: participant_payout, sourceParty: programOperator } }
120
+ moves self.refund from self.held to self.buyer economics { purpose: principal, sourceParty: programOperator }; moves self.retained from self.held to retained_to economics { recipient: retained_to, company: { purpose: earning, sourceParty: buyer }, participant: { purpose: pass_through, sourceParty: programOperator } }
121
121
  }
122
122
  action expire_proposal { from: proposed, to: expired, actor: clock, due: { at: self.proposalExpiresAt } }
123
123
  }
package/std/money.hsx CHANGED
@@ -1,7 +1,7 @@
1
1
  header money
2
2
 
3
3
  // Economics: Held funding and returns are principal. Release to the company realizes
4
- // earnings; release to another participant is a payout. Generic transfers, splits,
4
+ // earnings; release to another participant passes the payer's cash through. Generic transfers, splits,
5
5
  // schedules, swaps, boundary payouts and metered fees retain no assumed purpose.
6
6
 
7
7
  // Expose each attachment's create and caller actions explicitly. A transfer or
@@ -48,7 +48,7 @@ instrument hold(payer: party, payee: party, amount: money = runtime) {
48
48
  }
49
49
  action release {
50
50
  from: funded, to: released, actor: { party: payer }
51
- moves self.amount from self.held to payee economics { recipient: payee, company: { purpose: earning, sourceParty: payer }, participant: { purpose: participant_payout, sourceParty: programOperator } }
51
+ moves self.amount from self.held to payee economics { recipient: payee, company: { purpose: earning, sourceParty: payer }, participant: { purpose: pass_through, sourceParty: programOperator } }
52
52
  }
53
53
  action refund {
54
54
  from: funded, to: refunded, actor: { party: payee }
@@ -107,7 +107,7 @@ instrument pool(payer: party, payee: party, target: money, closes: date) {
107
107
  action pay {
108
108
  from: open, to: paid, actor: { party: payee }
109
109
  requires self.held.balance == self.target
110
- moves self.target from self.held to payee economics { recipient: payee, company: { purpose: earning, sourceParty: payer }, participant: { purpose: participant_payout, sourceParty: programOperator } }
110
+ moves self.target from self.held to payee economics { recipient: payee, company: { purpose: earning, sourceParty: payer }, participant: { purpose: pass_through, sourceParty: programOperator } }
111
111
  }
112
112
  action fail {
113
113
  from: open, to: failed, actor: clock, due: { at: self.closesAt }
package/std/wallet.hsx CHANGED
@@ -1,6 +1,7 @@
1
1
  header wallet
2
2
 
3
- // Economics: Topups and withdrawals are excluded prepaid credit. Spending remains
3
+ // Economics: Topups and withdrawals are excluded prepaid credit; a top-up fee is
4
+ // Company earning. Spending remains
4
5
  // unclassified because the bound payee alone does not state the purchase purpose.
5
6
 
6
7
  // Expose balance.create, activate, topup and withdraw as needed. Holder moves
@@ -12,20 +13,30 @@ header wallet
12
13
  // Existing reservations may still post after the wallet freezes. There is no
13
14
  // daily aggregate limit, partial spend or card-network settlement in this header.
14
15
 
15
- // Cost: balance.create declares one owned account; topup and withdraw each
16
- // declare one transfer. Spend reserve, pay and cancel each declare one transfer
16
+ // Top-up fee: topup_fee takes a floored percentage of each top-up for the Company;
17
+ // the wallet receives the rest. At 1%, a 100.00 top-up credits 99.00 and earns 1.00.
18
+ // The default 0% moves nothing to the Company.
19
+
20
+ // Cost: balance.create declares one owned account; topup declares two transfers
21
+ // and withdraw one. Spend reserve, pay and cancel each declare one transfer
17
22
  // instruction. These are counts, not the executor tariff.
18
23
 
19
- instrument balance(holder: party, spend_limit: money = 100000 SAR) {
24
+ instrument balance(holder: party, spend_limit: money = 100000 SAR, topup_fee: percent = 0%) {
20
25
  summary: "A holder's reusable balance with an immutable per-spend ceiling."
21
- fields { holder: account of holder, held: account of self, spendLimit: money = spend_limit }
26
+ fields { holder: account of holder, held: account of self, spendLimit: money = spend_limit, topupFee: money?, topupCredit: money? }
22
27
  lifecycle { states: [pending, active, frozen, closed], initial: pending }
23
28
  action create { }
24
29
  action activate { from: pending, to: active, actor: { party: holder } }
25
30
  action topup {
31
+ allowZero: true, requires input.amount > "0"
26
32
  from: active, to: active, actor: { party: holder }
27
33
  input { amount: money }
28
- moves input.amount from holder to self.held economics { purpose: prepaid_credit, sourceParty: holder }
34
+ calculate: [
35
+ { target: topupFee, op: rate, base: { field: input.amount }, bps: { literal: topup_fee }, rounding: floor }
36
+ { target: topupCredit, op: subtract, base: { field: input.amount }, subtract: [{ field: self.topupFee }] }
37
+ ]
38
+ moves self.topupCredit from holder to self.held economics { purpose: prepaid_credit, sourceParty: holder }
39
+ moves self.topupFee from holder to programOperator economics { purpose: earning, sourceParty: holder }
29
40
  }
30
41
  action withdraw {
31
42
  from: active, to: active, actor: { party: holder }