@hyperscale0/hsx 1.0.265 → 1.0.284

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/std/booking.hsx CHANGED
@@ -7,7 +7,8 @@ header booking
7
7
  // Expose create, pay_deposit and pay_balance for the booking path, and
8
8
  // cancel_early, cancel_middle and cancel_late for the customer. Create captures
9
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
10
+ // that do not add up to the price. Both installments must be positive. Creation
11
+ // and deposit payment close at the balance cutoff. The balance is payable only after the deposit
11
12
  // and before balance_due ahead of departure, so a cancelled booking never offers
12
13
  // it. cancel_early runs until early_until before departure and returns everything
13
14
  // held. cancel_middle runs from then until late_from; cancel_late runs from then
@@ -22,6 +23,7 @@ header booking
22
23
  // composed counts.
23
24
 
24
25
  instrument reservation(customer: party = party(person), operator: party = programOperator, early_until: duration = 30d, late_from: duration = 15d, balance_due: duration = 15d, middle_forfeit: percent = 0%, late_forfeit: percent = 0%) {
26
+ constraints { late_from: less_than(early_until) }
25
27
  revisioned: true
26
28
  summary: "Take a deposit and a balance for a dated booking, with cancellation windows before departure."
27
29
  fields {
@@ -38,11 +40,14 @@ instrument reservation(customer: party = party(person), operator: party = progra
38
40
  action create {
39
41
  subject { departure: date, price: money, deposit: money, balance: money }
40
42
  actor: { party: customer }
41
- requires self.departure > self.now
43
+ requires self.balanceAt > self.now
44
+ requires self.departure > self.balanceAt
45
+ requires self.deposit > "0"
46
+ requires self.balance > "0"
42
47
  requires self.split == self.price
43
48
  }
44
49
  action pay_deposit {
45
- from: reserved, to: deposit_paid, actor: { party: customer }
50
+ from: reserved, to: deposit_paid, actor: { party: customer }, deadline: { at: self.balanceAt }
46
51
  moves self.deposit from customer to self.held economics { purpose: principal, sourceParty: customer }
47
52
  }
48
53
  action pay_balance {
@@ -30,7 +30,7 @@ instrument case(on: ref<financing.installments>[], agency: party, capital: party
30
30
  requires unique "overdue_referral" on [self.slice]
31
31
  }
32
32
  action assign { from: opened, to: assigned, actor: { party: capital } }
33
- 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: {} }] }
33
+ action recover { 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: {} }] }
34
34
  action pay_fee { allowZero: true, from: recovered, to: recovered, actor: { parent: current() }, moves self.agencyFee from self.plan.capital to agency economics { recipient: agency, company: { recipient: capital, company: { purpose: internal, sourceParty: capital }, participant: { purpose: earning, sourceParty: capital } }, participant: { recipient: capital, company: { purpose: participant_payout, sourceParty: capital }, participant: { purpose: pass_through, sourceParty: capital } } } }
35
35
  action recall { from: [opened, assigned], to: recalled, actor: { party: capital } }
36
36
  action return_uncollected { from: assigned, to: returned_uncollected, actor: { party: agency } }
package/std/escrow.hsx CHANGED
@@ -8,30 +8,17 @@ header escrow
8
8
  // subject.price; acceptance pays payee less the configured fee, not payer.
9
9
  // Expose dispute, verify_return and refund separately for the return path.
10
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 financing plan funds the hold in any state
13
- // but cancelled, both refunds refuse, so a written-off plan never hands lender
14
- // principal to the payer. Only unwind returns the principal to capital and
15
- // cancels the plan; after that, refund returns the rest. Before delivery the
16
- // Product operator runs the plan's unwind_undelivered instead, which invokes
17
- // unwind_financed here to return the rest to the payer and close the hold.
18
- // For a paid plan, the plan's unwind_paid runs unwind_financed from funded after
19
- // deliverBy, and return_hold or unwind_paid runs it from return_verified,
20
- // depending on the plan's returned_goods.
21
- // Only an installments plan can run confirm and unwind_financed. In a program
22
- // without financing neither action exists, so every hold reaches funded through
23
- // fund, which sets fundedAt. The plan's confirm leaves fundedAt unset, so
24
- // refund_undelivered never comes due on a financed hold and no schedule offers
25
- // it. Confirm sets deliverBy, deliver_within after confirmation, and the plan's
26
- // unwind_paid waits for it so the payee keeps the whole delivery window.
11
+ // Refunds return the held balance to the payer. A composition can supply funding
12
+ // controllers and the states in which they block direct refunds. Only those
13
+ // controllers may confirm outside funding or finish its coordinated refund.
27
14
  // Expose refund_undelivered to let the payer take the whole held price back once
28
15
  // deliver_within has passed since funding without delivery. It is a payer action
29
16
  // that becomes available at that time; nothing runs it automatically.
30
17
  // The Product operator settles any disputed hold: settle_release pays the payee
31
18
  // as acceptance would, and settle_return moves it to return_verified. Without a
32
- // live plan, settle_return also runs settle_refund to return the whole balance
33
- // to the payer. A financed return continues on the plan's returned-goods path,
34
- // and once that plan is cancelled the operator's settle_refund returns the rest.
19
+ // active funding controller, settle_return also runs settle_refund to return
20
+ // the whole balance to the payer. Otherwise the controller coordinates the
21
+ // return before the operator's settle_refund returns the rest.
35
22
  // Acceptance expiry enters disputed, never released. Neither verify_return nor
36
23
  // refund becomes public merely because dispute is exposed. Clock actions remain
37
24
  // internal. Expose cancel for a pending hold, including a checkout-funded hold;
@@ -42,12 +29,12 @@ header escrow
42
29
  // transfer; accept expands the configured fee and tax into moves. Run hsx cost
43
30
  // on the composed program for instruction counts. The executor supplies prices.
44
31
 
45
- 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 }) {
32
+ 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 }, funding: policy = { controllers: [], reference: "funds", blocking_states: [active] }) {
46
33
  revisioned: true
47
34
  summary: "Hold the subject price through delivery, acceptance and verified return."
48
35
  fields {
49
36
  payer: account of payer, payee: account of payee
50
- held: account of self, fundedAt: date?, deliverBy: date?, deliveredAt: date?, openPlans: integer?
37
+ held: account of self, fundedAt: date?, deliverBy: date?, deliveredAt: date?, blockingFunding: integer?
51
38
  price: money = subject.price
52
39
  }
53
40
  lifecycle { states: [pending, funded, delivered, disputed, return_verified, released, refunded, cancelled], initial: pending }
@@ -61,7 +48,7 @@ instrument hold(payer: party = party(person), payee: party = programOperator, ac
61
48
  moves self.price from payer to self.held economics { purpose: principal, sourceParty: payer }
62
49
  }
63
50
  action confirm {
64
- from: pending, to: funded, actor: { parent: all(financing.installments) }
51
+ from: pending, to: funded, actor: { parent: funding.controllers }
65
52
  requires self.held.balance == self.price
66
53
  calculate: [{ target: deliverBy, op: shift, date: { field: self.now }, milliseconds: { literal: deliver_within }, direction: after }]
67
54
  }
@@ -95,27 +82,27 @@ instrument hold(payer: party = party(person), payee: party = programOperator, ac
95
82
  action settle_return {
96
83
  from: disputed, to: return_verified, actor: { party: programOperator }
97
84
  input { ruling: text }
98
- calculate: [{ target: openPlans, op: aggregate, selection: { instrument: all(financing.installments), reference: "funds", anchor: self.id, states: [quoted, signed, active, paid, written_off], limit: 1 }, measure: count }]
99
- invoke: [{ reference: self.id, action: settle_refund, guard: { kind: compare, left: { field: self.openPlans }, operator: "==", right: { literal: 0 } }, input: {} }]
85
+ calculate: [{ target: blockingFunding, op: aggregate, selection: { instrument: funding.controllers, reference: funding.reference, anchor: self.id, states: funding.blocking_states, limit: 1 }, measure: count }]
86
+ invoke: [{ reference: self.id, action: settle_refund, guard: { kind: compare, left: { field: self.blockingFunding }, operator: "==", right: { literal: 0 } }, input: {} }]
100
87
  }
101
88
  action settle_refund { allowZero: true,
102
89
  from: return_verified, to: refunded, actor: { party: programOperator }
103
- requires count of { instrument: all(financing.installments), reference: "funds", anchor: self.id, states: [quoted, signed, active, paid, written_off], limit: 1 } == 0
90
+ requires count of { instrument: funding.controllers, reference: funding.reference, anchor: self.id, states: funding.blocking_states, limit: 1 } == 0
104
91
  moves self.held.balance from self.held to payer economics { purpose: principal, sourceParty: programOperator }
105
92
  }
106
93
  action refund {
107
94
  from: [funded, dispute.refund_after], to: refunded, actor: { party: payee }
108
- requires count of { instrument: all(financing.installments), reference: "funds", anchor: self.id, states: [quoted, signed, active, paid, written_off], limit: 1 } == 0
95
+ requires count of { instrument: funding.controllers, reference: funding.reference, anchor: self.id, states: funding.blocking_states, limit: 1 } == 0
109
96
  moves self.held.balance from self.held to payer economics { purpose: principal, sourceParty: programOperator }
110
97
  }
111
98
  action refund_undelivered {
112
99
  from: funded, to: refunded, actor: { party: payer }
113
100
  due: { at: self.fundedAt, offset: deliver_within }
114
- requires count of { instrument: all(financing.installments), reference: "funds", anchor: self.id, states: [quoted, signed, active, paid, written_off], limit: 1 } == 0
101
+ requires count of { instrument: funding.controllers, reference: funding.reference, anchor: self.id, states: funding.blocking_states, limit: 1 } == 0
115
102
  moves self.price from self.held to payer economics { purpose: principal, sourceParty: programOperator }
116
103
  }
117
- action unwind_financed { allowZero: true,
118
- from: [funded, return_verified], to: refunded, actor: { parent: all(financing.installments) }
104
+ action finish_refund { allowZero: true,
105
+ from: [funded, return_verified], to: refunded, actor: { parent: funding.controllers }
119
106
  moves self.held.balance from self.held to payer economics { purpose: principal, sourceParty: programOperator }
120
107
  }
121
108
  action cancel { allowZero: true,
package/std/financing.hsx CHANGED
@@ -10,13 +10,9 @@ header financing
10
10
  // flat_total reads it as one charge on principal for the whole term (murabaha,
11
11
  // fixed-fee pay-later). 12,000 at 18% over 12 months costs 13,201.95 amortizing
12
12
  // and 14,160 flat_total.
13
- // Expose create and sign on installments, then collect_down_payment as checkout
14
- // for disburse_to: funds. Checkout requires a signed plan and pending escrow;
15
- // it tops up only the missing down payment, without advancing lender principal.
16
- // Disburse still collects any missing down payment, activates slices, and confirms
17
- // the fully funded hold. Existing escrow credit counts once toward the down payment.
18
- // Disburse first credits an active marketplace.reservation on the same hold,
19
- // so list the plan in its converters; the deposit then counts as escrow credit.
13
+ // Expose create and sign on installments. A funding composition owns checkout,
14
+ // destination funding and returns when disburse_to is funds. The repayment plan
15
+ // owns principal, profit, limits and debt cancellation.
20
16
  // disburse_to: seller pays principal to the seller party at activation (pay later
21
17
  // with the merchant paid on day one). Capital may refund any paid payment to
22
18
  // the borrower. Payments stay refundable while the plan is active. The
@@ -70,9 +66,9 @@ header financing
70
66
  // settle returned goods too.
71
67
  // Borrower- and seller-directed disbursement exclude collect_down_payment. A down_payment
72
68
  // there reduces principal but does not collect cash. Price is subject metadata,
73
- // not the borrower limit. The escrow payer and price must match the plan.
74
- // Expose escrow cancel to return checkout cash if financing is abandoned; plan
75
- // void alone cancels the agreement and does not refund its separate escrow.
69
+ // not the borrower limit. The funding composition checks its payer and price.
70
+ // Cancelling a plan cancels its debt agreement. Its funding composition owns
71
+ // returning any cash collected before activation.
76
72
  // Approve borrower and portfolio limits before disbursement. Create builds the
77
73
  // dated slice records from future, strictly increasing dates; disbursement
78
74
  // refuses once a slice is due. Payments need a separate payment record and pay action.
@@ -93,12 +89,12 @@ header financing
93
89
  // bounded invocations across slices and assessments. Month and charge limits affect
94
90
  // the bound. Run hsx cost on the selected policies; profit is not a platform tariff.
95
91
 
96
- instrument installments(months: integer(1, 366), max_history: integer(2, 366) = 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, refund_within: duration = 30d, returned_goods: enum(keep_profit, refund_all) = keep_profit, 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)) {
92
+ instrument installments(months: integer(1, 366), max_history: integer(2, 366) = 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, refund_within: duration = 30d, returned_goods: enum(keep_profit, refund_all) = keep_profit, allow_overdue: enum(allowed, blocked) = allowed, assessed_fines: enum(carry, require_waiver) = carry, down_payment: percent = 0%, funds: ref?, 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)) {
97
93
  dependencies {
98
94
  funds {
99
95
  selector: disburse_to, is: funds
100
96
  message: "Attachment `{attachment}` disburses into a hold, but `funds` is not bound."
101
- 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."
97
+ fix: "Bind `funds: checkout` to a funding composition. Choose `disburse_to: seller` to pay a seller on activation, or `disburse_to: borrower` only if the borrower should receive the money directly."
102
98
  }
103
99
  seller {
104
100
  selector: disburse_to, is: seller
@@ -113,7 +109,7 @@ instrument installments(months: integer(1, 366), max_history: integer(2, 366) =
113
109
  funds: ref<funds>?, borrower: account of borrower, settlementIdentity: text, borrowerLimit: ref<limits>, portfolioLimit: ref<portfolio>
114
110
  months: integer = months, dates: list(date, months), historyLimit: integer = max_history, paymentLimit: integer = subtract(max_history, 1)
115
111
  allocatedPayable: account(capital, cash, "allocatedProfitPayable"), capital: account(capital, cash, "capital"), profitIncome: account(capital, cash, "profitIncome"), loss: account(capital, claim, "loss")
116
- downPaymentRemaining: money?, downPaymentCredit: money?, principalOutstanding: money?, principalRepaid: money?, principalReturned: money?, profitReturned: money?, lossRecorded: money?, refundWindow: date?, refundUntil: date?
112
+ principalOutstanding: money?, principalRepaid: money?, principalReturned: money?, profitReturned: money?, lossRecorded: money?, refundWindow: date?, refundUntil: date?
117
113
  openSlices: integer?, openCharges: money?, openCosts: money?, payoffRefunds: integer?
118
114
  price: money = subject.price
119
115
  downPayment: money = rate(self.price, down_payment)
@@ -129,7 +125,7 @@ instrument installments(months: integer(1, 366), max_history: integer(2, 366) =
129
125
  actor: { party: borrower }
130
126
  requires self.borrowerLimit.borrower == self.borrower
131
127
  when pricing is flat_total { calculate: [{ target: flatProfit, op: rate, base: { field: self.principal }, bps: { literal: profit_rate }, rounding: floor }] }
132
- when disburse_to is funds { requires self.borrower == self.funds.payer; requires self.price == self.funds.price }
128
+ when disburse_to is funds { invoke: [{ reference: self.funds, action: check, input: { plan: { field: self.id } } }] }
133
129
  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: {} }]
134
130
  }
135
131
  action sign {
@@ -140,32 +136,16 @@ instrument installments(months: integer(1, 366), max_history: integer(2, 366) =
140
136
  from: signed, to: signed, actor: { party: borrower }
141
137
  when disburse_to is borrower { requires true == false }
142
138
  when disburse_to is seller { requires true == false }
143
- when disburse_to is funds {
144
- requires self.funds in [pending]
145
- 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 }] }]
146
- moves self.downPaymentRemaining from self.borrower to self.funds.held economics { purpose: principal, sourceParty: borrower }
147
- }
139
+ when disburse_to is funds { invoke: [{ reference: self.funds, action: collect, input: { plan: { field: self.id } } }] }
148
140
  }
149
141
  action disburse { allowZero: true,
150
142
  from: signed, to: active, actor: { party: capital }
151
143
  requires self.borrowerLimit in [approved]
152
144
  requires self.portfolioLimit in [approved]
153
- when disburse_to is funds {
154
- requires self.funds in [pending]
155
- 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: {} }]
156
- }
145
+ when disburse_to is funds { invoke: [{ reference: self.funds, action: disburse, input: { plan: { field: self.id } } }] }
157
146
  when disburse_to is borrower { moves self.principal from self.capital to self.borrower economics { purpose: principal, sourceParty: capital } }
158
147
  when disburse_to is seller { moves self.principal from self.capital to seller economics { purpose: principal, sourceParty: capital } }
159
148
  invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [draft], limit: 366, order: [position] }, action: activate, input: {} }]
160
- when disburse_to is funds { invoke: [{ reference: self.funds, action: confirm, input: {} }] }
161
- }
162
- action fund_down_payment { allowZero: true,
163
- from: active, to: active, actor: { parent: current() }
164
- when disburse_to is funds {
165
- 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 }] }]
166
- moves self.downPaymentRemaining from self.funds.payer to self.funds.held economics { purpose: principal, sourceParty: borrower }
167
- moves self.principal from self.capital to self.funds.held economics { purpose: principal, sourceParty: capital }
168
- }
169
149
  }
170
150
  action complete {
171
151
  from: active, to: paid
@@ -201,7 +181,7 @@ instrument installments(months: integer(1, 366), max_history: integer(2, 366) =
201
181
  set: { refundUntil: { field: self.refundWindow } }
202
182
  due: { at: input.overdueSlice.lossEligibleAt }
203
183
  requires input.overdueSlice.plan == self.id; requires input.overdueSlice.principalReceivable.balance > "0"
204
- when disburse_to is funds { requires self.funds in [released] }
184
+ when disburse_to is funds { invoke: [{ reference: self.funds, action: write_off, input: { plan: { field: self.id } } }] }
205
185
  invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366, order: [position] }, action: write_off, input: {} }, { instrument: settlement, action: create, input: { plan: { field: self.id }, identity: { field: self.settlementIdentity }, principal: { literal: "0" }, profit: { literal: "0" }, loss: { field: self.lossRecorded }, kind: { literal: "loss" } } }]
206
186
  }
207
187
  action record_payment {
@@ -226,7 +206,6 @@ instrument installments(months: integer(1, 366), max_history: integer(2, 366) =
226
206
  ]
227
207
  set: { refundUntil: { field: self.refundWindow } }
228
208
  when disburse_to is borrower { requires true == false }
229
- when disburse_to is funds { requires self.funds in [return_verified] }
230
209
  when returned_goods is refund_all {
231
210
  requires count of { instrument: payment, reference: "plan", anchor: self.id, states: [paid], limit: 366 } == 0
232
211
  requires count of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [collected], limit: 366 } == 0
@@ -234,7 +213,7 @@ instrument installments(months: integer(1, 366), max_history: integer(2, 366) =
234
213
  }
235
214
  requires count of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [assessed], limit: 366 } == 0
236
215
  requires count of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [assessed], limit: 366 } == 0
237
- when disburse_to is funds { moves self.principalOutstanding from self.funds.held to self.capital economics { purpose: principal, sourceParty: capital } }
216
+ when disburse_to is funds { invoke: [{ reference: self.funds, action: refund, input: { plan: { field: self.id }, principal: { field: self.principalOutstanding } } }] }
238
217
  when disburse_to is seller {
239
218
  moves self.principalOutstanding from seller to self.capital economics { purpose: principal, sourceParty: seller }
240
219
  moves self.principalRepaid from seller to self.borrower economics { purpose: principal, sourceParty: seller }
@@ -247,14 +226,12 @@ instrument installments(months: integer(1, 366), max_history: integer(2, 366) =
247
226
  set: { refundUntil: { field: self.refundWindow } }
248
227
  when disburse_to is borrower { requires true == false }
249
228
  when disburse_to is seller { requires true == false }
250
- when disburse_to is funds { requires self.funds in [funded] }
251
229
  requires count of { instrument: payment, reference: "plan", anchor: self.id, states: [paid], limit: 366 } == 0
252
230
  requires count of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [collected], limit: 366 } == 0
253
231
  requires count of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [collected], limit: 366 } == 0
254
- when disburse_to is funds { moves self.principal from self.funds.held to self.capital economics { purpose: principal, sourceParty: capital } }
255
232
  invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366, order: [position] }, action: cancel, input: {} }]
256
233
  invoke: [{ selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [proposed, assessed], limit: 366 }, action: cancel_undelivered, input: {} }, { selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [proposed, assessed], limit: 366 }, action: cancel_undelivered, input: {} }]
257
- when disburse_to is funds { invoke: [{ reference: self.funds, action: unwind_financed, input: {} }] }
234
+ when disburse_to is funds { invoke: [{ reference: self.funds, action: cancel_undelivered, input: { plan: { field: self.id } } }] }
258
235
  }
259
236
  action return_hold {
260
237
  from: paid, to: paid, actor: { party: programOperator }
@@ -263,24 +240,21 @@ instrument installments(months: integer(1, 366), max_history: integer(2, 366) =
263
240
  when disburse_to is seller { requires true == false }
264
241
  when returned_goods is refund_all { requires true == false }
265
242
  when disburse_to is funds {
266
- requires self.funds in [return_verified]
267
- invoke: [{ reference: self.funds, action: unwind_financed, input: {} }]
243
+ invoke: [{ reference: self.funds, action: return_paid, input: { plan: { field: self.id } } }]
268
244
  }
269
245
  }
270
- action unwind_paid { expansionLimit: 8192,
246
+ action unwind_paid {
271
247
  from: paid, to: cancelled, actor: { party: programOperator }
272
248
  when disburse_to is borrower { requires true == false }
273
249
  when disburse_to is seller { requires true == false }
274
- when disburse_to is funds {
275
- when returned_goods is keep_profit { requires self.funds in [funded] }
276
- when returned_goods is refund_all { requires self.funds in [funded, return_verified] }
277
- requires self.funds.deliverBy < self.now
278
- }
279
250
  invoke: [{ selection: { instrument: settlement, reference: "plan", anchor: self.id, states: [recorded], limit: max_history }, action: unwind, input: {} }, { selection: { instrument: profit_piece, reference: "plan", anchor: self.id, states: [paid], limit: max_history, where: { payment.status: { literal: "refunded" } } }, action: refund, input: {} }, { selection: { instrument: payment, reference: "plan", anchor: self.id, states: [paid], limit: max_history }, action: unwind, input: {} }, { reference: self.id, action: unwind_payoff_profit, input: {} }]
280
251
  invoke: [{ selection: { instrument: all(financing.late_charge.receipt), reference: "plan", anchor: self.id, states: [paid], limit: max_history }, action: refund, input: {} }, { selection: { instrument: all(financing.late_charge.cost.receipt), reference: "plan", anchor: self.id, states: [paid], limit: max_history }, action: refund, input: {} }, { selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [collected], limit: max_history }, action: unwind_payoff, input: {} }, { selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [collected], limit: max_history }, action: unwind_payoff, input: {} }]
281
252
  invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due, paid], limit: 366, order: [position] }, action: cancel, input: {} }]
282
253
  invoke: [{ selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [proposed, assessed, collected], limit: max_history }, action: cancel_undelivered, input: {} }, { selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [proposed, assessed, collected], limit: max_history }, action: cancel_undelivered, input: {} }]
283
- when disburse_to is funds { invoke: [{ reference: self.funds, action: unwind_financed, input: {} }] }
254
+ when disburse_to is funds {
255
+ when returned_goods is keep_profit { invoke: [{ reference: self.funds, action: unwind_paid_undelivered, input: { plan: { field: self.id } } }] }
256
+ when returned_goods is refund_all { invoke: [{ reference: self.funds, action: unwind_paid, input: { plan: { field: self.id } } }] }
257
+ }
284
258
  }
285
259
  action unwind_payoff_profit {
286
260
  from: cancelled, to: cancelled, actor: { parent: current() }
@@ -593,7 +567,6 @@ instrument installments(months: integer(1, 366), max_history: integer(2, 366) =
593
567
  lifecycle { states: [pending, paid, refunded], initial: pending }
594
568
  action create { input { plan: ref<parent>, amount: money, paymentIdentity: text } requires self.plan in [active]; requires unique "payment_identity" on [self.plan, self.paymentIdentity] }
595
569
  action pay {
596
- expansionLimit: 8192
597
570
  from: pending, to: paid, actor: { party: borrower }
598
571
  calculate: [{ target: outstandingPrincipal, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, measure: { sum: "principalReceivable.balance" } }]
599
572
  when profit_earned is on_payment { calculate: [{ target: outstandingProfit, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, measure: { sum: "profitReceivable.balance" } }] }
package/std/money.hsx CHANGED
@@ -147,24 +147,22 @@ instrument swap(first: party, second: party, first_amount: money, second_amount:
147
147
  action expire_both { from: both_ready, to: cancelled, actor: clock, due: { at: self.expiresAt }, moves void self.firstReceipt; moves void self.secondReceipt }
148
148
  }
149
149
 
150
- instrument payout(payer: party, payee: party, adapter: text, max_age: duration, amount: money = runtime) {
151
- summary: "Reserve funds until matching terminal boundary evidence permits posting."
152
- fields { amount: money = amount, receipt: text? }
150
+ instrument payout(payer: party, payee: party, adapter: text, amount: money = runtime) {
151
+ summary: "Pay the recipient when the provider confirms settlement."
152
+ fields { amount: money = amount }
153
153
  lifecycle { states: [pending, instructed, settled, rejected], initial: pending }
154
154
  action create {}
155
155
  action instruct {
156
156
  from: pending, to: instructed, actor: { party: payer }
157
- moves reserve self.amount from payer to payee capture "receipt" boundary adapter
157
+ moves reserve self.amount from payer to payee boundary adapter
158
158
  }
159
159
  action confirm {
160
160
  from: instructed, to: settled
161
- requires evidence self.id family boundary check outcome result confirmed maxAge max_age instruction self.receipt
162
- moves post self.receipt
161
+ moves post instruct
163
162
  }
164
163
  action reject {
165
164
  from: instructed, to: rejected
166
- requires evidence self.id family boundary check outcome result rejected maxAge max_age instruction self.receipt
167
- moves void self.receipt
165
+ moves void instruct
168
166
  }
169
167
  }
170
168
 
@@ -0,0 +1,89 @@
1
+ header purchase
2
+
3
+ // A financed purchase connects a repayment plan, a delivery hold and optional
4
+ // marketplace reservations. The hold blocks refunds while checkout is active.
5
+ // Debt cancellation returns outstanding capital before the buyer's own cash.
6
+ instrument checkout(plans: ref<financing.installments>[], funds: ref<escrow.hold>, borrower: party, capital: party = programOperator) {
7
+ summary: "Fund a purchase and return lender capital before refunding the buyer."
8
+ fields {
9
+ funds: ref<funds>, borrower: account of borrower
10
+ downPaymentCredit: money?, downPaymentRemaining: money?
11
+ }
12
+ lifecycle { states: [pending, active, closed], initial: pending }
13
+ action create { actor: { party: borrower } }
14
+ action check {
15
+ from: pending, to: pending, actor: { parent: plans }
16
+ input { plan: ref<plans> }
17
+ requires input.plan.funds == self.id
18
+ requires input.plan.borrower == self.borrower
19
+ requires input.plan.borrower == self.funds.payer
20
+ requires input.plan.price == self.funds.price
21
+ }
22
+ action collect { allowZero: true,
23
+ from: pending, to: pending, actor: { parent: plans }
24
+ input { plan: ref<plans> }
25
+ requires input.plan.funds == self.id
26
+ requires self.funds in [pending]
27
+ calculate: [{ target: downPaymentCredit, op: minimum, values: [{ field: self.funds.held.balance }, { field: input.plan.downPayment }] }, { target: downPaymentRemaining, op: subtract, base: { field: input.plan.downPayment }, subtract: [{ field: self.downPaymentCredit }] }]
28
+ moves self.downPaymentRemaining from self.borrower to self.funds.held economics { purpose: principal, sourceParty: borrower }
29
+ }
30
+ action disburse {
31
+ from: pending, to: active, actor: { parent: plans }
32
+ input { plan: ref<plans> }
33
+ requires input.plan.funds == self.id
34
+ requires self.funds in [pending]
35
+ invoke: [{ selection: { instrument: all(marketplace.reservation), reference: "funds", anchor: self.funds, states: [active], limit: 1 }, action: credit, input: { funds: { field: self.funds }, contribution: { field: input.plan.downPayment } } }, { reference: self.id, action: fund, input: { plan: { field: input.plan } } }]
36
+ }
37
+ action fund { allowZero: true,
38
+ from: active, to: active, actor: { parent: current() }
39
+ input { plan: ref<plans> }
40
+ calculate: [{ target: downPaymentCredit, op: minimum, values: [{ field: self.funds.held.balance }, { field: input.plan.downPayment }] }, { target: downPaymentRemaining, op: subtract, base: { field: input.plan.downPayment }, subtract: [{ field: self.downPaymentCredit }] }]
41
+ moves self.downPaymentRemaining from self.borrower to self.funds.held economics { purpose: principal, sourceParty: borrower }
42
+ moves input.plan.principal from input.plan.capital to self.funds.held economics { purpose: principal, sourceParty: capital }
43
+ invoke: [{ reference: self.funds, action: confirm, input: {} }]
44
+ }
45
+ action write_off {
46
+ from: active, to: active, actor: { parent: plans }
47
+ input { plan: ref<plans> }
48
+ requires input.plan.funds == self.id
49
+ requires self.funds in [released]
50
+ }
51
+ action refund { allowZero: true,
52
+ from: active, to: closed, actor: { parent: plans }
53
+ input { plan: ref<plans>, principal: money }
54
+ requires input.plan.funds == self.id
55
+ requires self.funds in [return_verified]
56
+ moves input.principal from self.funds.held to input.plan.capital economics { purpose: principal, sourceParty: capital }
57
+ }
58
+ action cancel_undelivered { allowZero: true,
59
+ from: active, to: closed, actor: { parent: plans }
60
+ input { plan: ref<plans> }
61
+ requires input.plan.funds == self.id
62
+ requires self.funds in [funded]
63
+ moves input.plan.principal from self.funds.held to input.plan.capital economics { purpose: principal, sourceParty: capital }
64
+ invoke: [{ reference: self.funds, action: finish_refund, input: {} }]
65
+ }
66
+ action return_paid {
67
+ from: active, to: closed, actor: { parent: plans }
68
+ input { plan: ref<plans> }
69
+ requires input.plan.funds == self.id
70
+ requires self.funds in [return_verified]
71
+ invoke: [{ reference: self.funds, action: finish_refund, input: {} }]
72
+ }
73
+ action unwind_paid_undelivered {
74
+ from: active, to: closed, actor: { parent: plans }
75
+ input { plan: ref<plans> }
76
+ requires input.plan.funds == self.id
77
+ requires self.funds in [funded]
78
+ requires self.funds.deliverBy < self.now
79
+ invoke: [{ reference: self.funds, action: finish_refund, input: {} }]
80
+ }
81
+ action unwind_paid {
82
+ from: active, to: closed, actor: { parent: plans }
83
+ input { plan: ref<plans> }
84
+ requires input.plan.funds == self.id
85
+ requires self.funds in [funded, return_verified]
86
+ requires self.funds.deliverBy < self.now
87
+ invoke: [{ reference: self.funds, action: finish_refund, input: {} }]
88
+ }
89
+ }
package/std/reporting.hsx CHANGED
@@ -12,7 +12,7 @@ header reporting
12
12
  // That zero instruction count does not price report capture, rendering, storage or
13
13
  // release. The host owns those charges and their execution.
14
14
 
15
- instrument portfolio(on: ref<financing.installments>[], default_days: integer(1, 3650) = 90, retention_years: integer(1, 100) = 10, aging_first_days: integer(1, 3650) = 30, aging_second_days: integer(1, 3650) = 60, aging_third_days: integer(1, 3650) = 90, ratio_scale: integer(1, 1000000) = 10000, ratio_rounding: enum(floor, halfUp) = floor, lock_wait_ms: integer(1, 2000) = 2000, capture_ms: integer(1, 10000) = 10000, max_rows: integer(1, 100000) = 10000, max_join_rows: integer(1, 1000000) = 100000, max_bytes: integer(1024, 16777216) = 8388608) {
15
+ instrument portfolio(on: ref<financing.installments>[], default_days: integer(1, 3650) = 90, retention_years: integer(1, 100) = 10, aging_first_days: integer(1, 3650) = 30, aging_second_days: integer(1, 3650) = 60, aging_third_days: integer(1, 3650) = 90, ratio_scale: integer(1, 1000000) = 10000, ratio_rounding: enum(floor, halfUp) = floor) {
16
16
  summary: "Declared financing reports evaluated over frozen Product facts."
17
17
  fields {}
18
18
  lifecycle { states: [declared], initial: declared }
@@ -333,11 +333,6 @@ instrument portfolio(on: ref<financing.installments>[], default_days: integer(1,
333
333
  { left: "scheduleCount", right: "capturedScheduleCount" }
334
334
  ],
335
335
  unavailableFacts: [ ],
336
- lockTimeoutMs: lock_wait_ms,
337
- captureTimeoutMs: capture_ms,
338
- maxRows: max_rows,
339
- maxJoinRows: max_join_rows,
340
- maxBytes: max_bytes
341
336
  },
342
337
  output: {
343
338
  profile: "internal.v1",
@@ -679,11 +674,6 @@ instrument portfolio(on: ref<financing.installments>[], default_days: integer(1,
679
674
  { left: "scheduleCount", right: "capturedScheduleCount" }
680
675
  ],
681
676
  unavailableFacts: [ ],
682
- lockTimeoutMs: lock_wait_ms,
683
- captureTimeoutMs: capture_ms,
684
- maxRows: max_rows,
685
- maxJoinRows: max_join_rows,
686
- maxBytes: max_bytes
687
677
  },
688
678
  output: {
689
679
  profile: "internal.v1",