@hyperscale0/hsx 1.0.338 → 1.0.340

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.
Files changed (43) hide show
  1. package/dist/src/ast.d.ts +1 -0
  2. package/dist/src/ast.d.ts.map +1 -1
  3. package/dist/src/ast.js.map +1 -1
  4. package/dist/src/attachment-economics.d.ts +19 -0
  5. package/dist/src/attachment-economics.d.ts.map +1 -1
  6. package/dist/src/attachment-economics.js +101 -3
  7. package/dist/src/attachment-economics.js.map +1 -1
  8. package/dist/src/binding-contract.d.ts.map +1 -1
  9. package/dist/src/binding-contract.js +10 -4
  10. package/dist/src/binding-contract.js.map +1 -1
  11. package/dist/src/compile.d.ts.map +1 -1
  12. package/dist/src/compile.js +36 -8
  13. package/dist/src/compile.js.map +1 -1
  14. package/dist/src/examples-bundle.js +1 -1
  15. package/dist/src/examples-bundle.js.map +1 -1
  16. package/dist/src/headers.d.ts.map +1 -1
  17. package/dist/src/headers.js +3 -1
  18. package/dist/src/headers.js.map +1 -1
  19. package/dist/src/parse.js +13 -1
  20. package/dist/src/parse.js.map +1 -1
  21. package/dist/src/std-bundle.js +6 -6
  22. package/dist/src/std-bundle.js.map +1 -1
  23. package/dist/src/template-guide.d.ts.map +1 -1
  24. package/dist/src/template-guide.js +2 -1
  25. package/dist/src/template-guide.js.map +1 -1
  26. package/docs/README.md +5 -2
  27. package/examples/camera-rental.hsx +4 -2
  28. package/package.json +4 -4
  29. package/src/ast.ts +7 -1
  30. package/src/attachment-economics.ts +127 -2
  31. package/src/binding-contract.ts +13 -6
  32. package/src/compile.ts +63 -9
  33. package/src/examples-bundle.ts +1 -1
  34. package/src/headers.ts +6 -1
  35. package/src/parse.ts +16 -1
  36. package/src/std-bundle.ts +6 -6
  37. package/src/template-guide.ts +2 -1
  38. package/std/collections.hsx +4 -4
  39. package/std/financing.hsx +461 -266
  40. package/std/insurance.hsx +21 -5
  41. package/std/lending.hsx +13 -9
  42. package/std/money.hsx +88 -32
  43. package/std/reporting.hsx +6 -6
package/std/insurance.hsx CHANGED
@@ -16,6 +16,9 @@ header insurance
16
16
  // Cover.cancel invokes refund only on paid slices, before each startsAt. It returns
17
17
  // insurerPremium from insurer and commission from broker to holder. Active or
18
18
  // expired periods are excluded; insufficient recipient funds refuse the reversal.
19
+ // Each refund is two transfers, so cancel refunds 126 paid slices per operation.
20
+ // A cover with more reads cancelling, and its cancel_step clock refunds the next
21
+ // 126 a second later until none remain; then it reads cancelled.
19
22
  // Slice refund is parent-owned, not a public alias. Expose cover.cancel instead.
20
23
  // Claims require active cover. Expose claim.create, approve, deny and pay as needed.
21
24
  // Inspector approves OR denies from submitted. After approval only pay or clock
@@ -25,21 +28,34 @@ header insurance
25
28
 
26
29
  // Cost: each slice collection or refund declares two transfers. Claim approval
27
30
  // reserves once; pay posts once and expiry voids once. Cover cancellation adds
28
- // bounded slice invocations. Run hsx cost for composed counts, not provider prices.
31
+ // at most 252 transfers per step. Run hsx cost for composed counts, not provider prices.
29
32
 
30
33
  instrument cover(holder: party, adapter: text, commission: percent = 0%, broker: party = programOperator, covers: ref?, premium: money = runtime, claim_limit: money = runtime) {
31
34
  summary: "Recurring cover bound to an outside insurer across an ADL boundary."
32
- fields { covers: ref<covers>?, holder: account of holder, insurer: account(adapter(adapter), cash, "premium"), expiresAt: date, claimLimit: money = claim_limit }
33
- lifecycle { states: [pending, active, cancelled, expired], initial: pending }
35
+ fields { covers: ref<covers>?, holder: account of holder, insurer: account(adapter(adapter), cash, "premium"), expiresAt: date, claimLimit: money = claim_limit, cancelAt: date?, slicesLeft: integer? }
36
+ lifecycle { states: [pending, active, cancelling, cancelled, expired], initial: pending }
34
37
  action create { actor: { party: holder } }
35
38
  action activate {
36
39
  from: pending, to: active, actor: { party: programOperator }
37
40
  subject { adapter: adapter }
38
41
  }
39
42
  action cancel {
40
- from: active, to: cancelled, actor: { party: holder }
41
- invoke: [{ selection: { instrument: slice, reference: "cover", anchor: self.id, states: [paid], limit: 366 }, action: refund, input: {} }]
43
+ from: active, to: cancelling, actor: { party: holder }
44
+ invoke: [{ reference: self.id, action: cancel_batch, input: {} }]
42
45
  }
46
+ action cancel_step { from: cancelling, to: preserve, actor: clock, due: { at: self.cancelAt }, invoke: [{ reference: self.id, action: cancel_batch, input: {} }] }
47
+ action cancel_batch {
48
+ from: cancelling, to: preserve, actor: { parent: current() }
49
+ calculate: [{ target: cancelAt, op: shift, date: { field: self.now }, milliseconds: { literal: 1000 }, direction: after }]
50
+ invoke: [{ selection: { instrument: slice, reference: "cover", anchor: self.id, states: [paid], limit: 126, batch: true, order: [startsAt] }, action: refund, input: {} }]
51
+ invoke: [{ reference: self.id, action: cancel_check, input: {} }]
52
+ }
53
+ action cancel_check {
54
+ from: cancelling, to: preserve, actor: { parent: current() }
55
+ calculate: [{ target: slicesLeft, op: aggregate, selection: { instrument: slice, reference: "cover", anchor: self.id, states: [paid], limit: 1, batch: true }, measure: count }]
56
+ invoke: [{ reference: self.id, action: finish_cancel, guard: { kind: compare, left: { field: self.slicesLeft }, operator: "==", right: { literal: 0 } }, input: {} }]
57
+ }
58
+ action finish_cancel { from: cancelling, to: cancelled, actor: { parent: current() } }
43
59
  action expire { from: active, to: expired, actor: clock, due: { at: self.expiresAt } }
44
60
  records {
45
61
  slice: {
package/std/lending.hsx CHANGED
@@ -26,6 +26,9 @@ header lending
26
26
  // Cost: round.create declares one owned account. Close transfers its balance
27
27
  // and invokes commitments; failure invokes their refunds. Distribution adds moves
28
28
  // and bounded share invocations. Run hsx cost for counts after policy selection.
29
+ // A round takes at most 248 commitments, so failure refunds them in 248 ledger
30
+ // transfers and a cash distribution pays every share in 253, the most one
31
+ // ledger request takes.
29
32
 
30
33
  instrument round(borrower: party, plan: ref<financing.installments> = object(financing.installments), operator: party = programOperator, minimum_ticket: money = 100 SAR, investor_cap: percent = 20%, commitments: ref<lending.commitment> = object(lending.commitment)) {
31
34
  summary: "A threshold funding round; failed rounds return each original wallet contribution."
@@ -38,14 +41,14 @@ instrument round(borrower: party, plan: ref<financing.installments> = object(fin
38
41
  action create { actor: { party: operator }, requires unique "funding_plan" on [self.plan]; requires self.borrower == self.plan.borrower }
39
42
  action close {
40
43
  from: open, to: funded, actor: { party: operator }
41
- requires sum "amount" of { instrument: commitments, reference: "round", anchor: self.id, states: [committed], limit: 366 } == self.price
44
+ requires sum "amount" of { instrument: commitments, reference: "round", anchor: self.id, states: [committed], limit: 248 } == self.price
42
45
  requires self.held.balance == self.price
43
46
  moves self.price from self.held to self.plan.capital economics { purpose: principal, sourceParty: programOperator }
44
- invoke: [{ selection: { instrument: commitments, reference: "round", anchor: self.id, states: [committed], limit: 366 }, action: fund, input: {} }]
47
+ invoke: [{ selection: { instrument: commitments, reference: "round", anchor: self.id, states: [committed], limit: 248 }, action: fund, input: {} }]
45
48
  }
46
49
  action fail {
47
50
  from: open, to: failed, actor: clock, due: { at: self.closesAt }
48
- invoke: [{ selection: { instrument: commitments, reference: "round", anchor: self.id, states: [committed], limit: 366 }, action: refund, input: {} }]
51
+ invoke: [{ selection: { instrument: commitments, reference: "round", anchor: self.id, states: [committed], limit: 248 }, action: refund, input: {} }]
49
52
  }
50
53
  }
51
54
 
@@ -53,8 +56,9 @@ instrument commitment(round: ref<lending.round>, wallet: ref<wallet.balance>, in
53
56
  summary: "One investor's original-wallet contribution, capped cumulatively within the round."
54
57
  fields { round: ref<round>, wallet: ref<wallet>, investor: account of investor, amount: money, loss: account(investor, claim, "loss") }
55
58
  lifecycle { states: [committed, funded, withdrawn, refunded], initial: committed }
56
- invariants sum "amount" of { instrument: all(lending.commitment), reference: "round", anchor: self.round, states: [committed, funded], limit: 366, where: { investor: { field: self.investor } } } <= self.round.maximumTicket
57
- invariants sum "amount" of { instrument: all(lending.commitment), reference: "round", anchor: self.round, states: [committed, funded], limit: 366 } <= self.round.price
59
+ invariants sum "amount" of { instrument: all(lending.commitment), reference: "round", anchor: self.round, states: [committed, funded], limit: 248, where: { investor: { field: self.investor } } } <= self.round.maximumTicket
60
+ invariants sum "amount" of { instrument: all(lending.commitment), reference: "round", anchor: self.round, states: [committed, funded], limit: 248 } <= self.round.price
61
+ invariants count of { instrument: all(lending.commitment), reference: "round", anchor: self.round, states: [committed, funded], limit: 248 } <= 248
58
62
  action create {
59
63
  actor: { party: investor }
60
64
  requires self.round in [open]
@@ -108,9 +112,9 @@ instrument distribution(round: ref<lending.round>, receipt: ref, commitments: re
108
112
  action distribute_cash { allowZero: true,
109
113
  from: cash_ready, to: distributed, actor: { party: operator }
110
114
  requires self.source in [recorded]; requires unique "settlement_consumption" on [self.source.plan, self.source.identity]
111
- requires sum "weight" of { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit: 366 } == self.round.price
115
+ requires sum "weight" of { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit: 248 } == self.round.price
112
116
  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 economics { purpose: internal, sourceParty: programOperator }; moves self.vat from self.held to programTax economics { purpose: pass_through, sourceParty: programOperator }
113
- invoke: [{ reference: self.source, action: distribute, input: {} }, { selection: { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit: 366 }, action: cash, input: {} }, { reference: self.id, action: residual_cash, input: {} }]
117
+ invoke: [{ reference: self.source, action: distribute, input: {} }, { selection: { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit: 248 }, action: cash, input: {} }, { reference: self.id, action: residual_cash, input: {} }]
114
118
  }
115
119
  action residual_cash { allowZero: true,
116
120
  from: distributed, to: distributed, actor: { parent: current() }
@@ -119,9 +123,9 @@ instrument distribution(round: ref<lending.round>, receipt: ref, commitments: re
119
123
  action distribute_loss { allowZero: true,
120
124
  from: loss_ready, to: distributed, actor: { party: operator }
121
125
  requires self.source in [recorded]; requires unique "settlement_consumption" on [self.source.plan, self.source.identity]
122
- requires sum "weight" of { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit: 366 } == self.round.price
126
+ requires sum "weight" of { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit: 248 } == self.round.price
123
127
  moves self.net from self.source.lossAccount to self.losses
124
- invoke: [{ reference: self.source, action: distribute, input: {} }, { selection: { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit: 366 }, action: loss, input: {} }, { reference: self.id, action: residual_loss, input: {} }]
128
+ invoke: [{ reference: self.source, action: distribute, input: {} }, { selection: { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit: 248 }, action: loss, input: {} }, { reference: self.id, action: residual_loss, input: {} }]
125
129
  }
126
130
  action residual_loss { allowZero: true,
127
131
  from: distributed, to: distributed, actor: { parent: current() }
package/std/money.hsx CHANGED
@@ -13,17 +13,21 @@ header money
13
13
  // Bind released_by: payer for an escrow-style hold that only the payer releases.
14
14
  // Only the payee refunds. A hold has no dispute or damage decision.
15
15
  // The payee proposes a schedule and only the payer activates or cancels it; the
16
- // payee stops it. Without every, create makes count occurrences on the listed
17
- // dates and they sum to amount. With every (1 month, 2 weeks), each occurrence
18
- // charges amount, the first at startsAt, and paying one creates the next, so only
19
- // one piece waits ahead. Months step from startsAt and clamp to the month's end.
20
- // create refuses a startsAt in the past. An authored count ends it after count
21
- // pieces; without one it runs until stopped or cancelled. Each due occurrence pays
22
- // only while the schedule is active, and one clock drain charges at most 126
23
- // pieces; the next drain continues a plan that fell further behind.
16
+ // payee stops it. Without every, count is required and create makes count
17
+ // occurrences on the listed dates that sum to amount. With every (1 month, 2
18
+ // weeks), each occurrence charges amount, the first at startsAt, and paying one
19
+ // creates the next, so only one piece waits ahead. Months step from startsAt and
20
+ // clamp to the month's end. create refuses a startsAt in the past. An authored
21
+ // count ends it after count pieces; without one it runs until stopped or
22
+ // cancelled. The schedule keeps count as a field only when it is set, so reads
23
+ // name a piece "2 of 12" or, open-ended, "#2". Each due occurrence pays
24
+ // only while the schedule is active, and one clock drain posts at most the 253
25
+ // ledger transfers one request takes, 253 one-transfer pieces; the next drain
26
+ // continues a plan that fell further behind.
24
27
  // A pool accepts whole contributions only up to its remaining target and at most
25
- // 366 paid records. Expose pay to release the exact target. Clock failure refunds
26
- // original contributors; excess acceptance and partial contribution are absent.
28
+ // 253 paid records, so a failure refunds every contributor in one ledger request.
29
+ // Expose pay to release the exact target. Clock failure refunds original
30
+ // contributors; excess acceptance and partial contribution are absent.
27
31
  // A swap reserves both legs before exchange or voids reservations on expiry.
28
32
  // Payout instruct reserves across the selected adapter; confirm/reject require
29
33
  // matching terminal instruction evidence. An acknowledgement cannot settle it.
@@ -32,15 +36,30 @@ header money
32
36
  // most 366 billed events across every metered attachment on the mandate may sum
33
37
  // their payer debit, buyer fee and tax included, to its limit. The payer revokes
34
38
  // it to stop billing. Only the payee cancels its own unbilled usage.
35
- // The payer creates a late fee against its dueAt, which rename can bind to the
36
- // record's own due field. Once dueAt plus grace plus one period has passed, the
37
- // clock charges amount from payer to payee for each whole period, and the last
38
- // charge takes only what is left under cap; then the fee is capped. The payee runs
39
- // stop when the obligation is met. Stop keeps what was owed: each charge due at or
40
- // before it still posts, and the fee settles until the last of those posts, then
41
- // stops. A period still running at the stop is not charged. A charge the payer cannot fund refuses as clock work and charging waits
42
- // for its retry; the payee runs waive to forgive what is still owed. Grace is at
43
- // least one second; there are no percentage or interest-style fees.
39
+ // The company starts a late fee against its dueAt, so late tracking never waits
40
+ // on the payer; rename can bind dueAt to the record's own due field. Once dueAt
41
+ // plus grace plus one period has passed, the clock charges amount from payer to
42
+ // payee for each whole period, and the last charge takes only what is left under
43
+ // cap; then the fee is capped. Charge k is due at dueAt plus grace plus k
44
+ // periods. create copies dueAt. When any action on the record changes the bound
45
+ // field, follow runs on the fee in the same request and reads the field as it
46
+ // stands; the payee's reschedule runs follow directly. Follow keeps the charges
47
+ // already posted and moves the rest later, never earlier, because fees run from
48
+ // the dueAt set at create; past ones post on the next clock run. The clock never
49
+ // runs twice at one instant, so follow also needs the next charge after the last
50
+ // posted one. A move that breaks either rule refuses reschedule, while another
51
+ // action's change leaves the fee where it was and still runs. Period counts
52
+ // whole seconds. The payee runs stop when the obligation is met. Stop keeps what
53
+ // was owed: each charge due at or before it still posts, and the fee settles
54
+ // until the last of those posts, then stops. A period still running at the stop
55
+ // is not charged. A charge the payer cannot fund refuses as clock work and
56
+ // charging waits for its retry; the payee runs waive to forgive what is still
57
+ // owed. Grace is at least one second; there are no percentage or interest-style
58
+ // fees. Bind a hold's late_fee to the late fee on its object and refund also
59
+ // runs stop on that fee while it is running, so returning the deposit ends both.
60
+ // The link sits on refund because every good return refunds, and a fee already
61
+ // capped or stopped is skipped rather than blocking the refund. The hold links
62
+ // the fee at create, so the fee is agreed first, with the same payer and payee.
44
63
 
45
64
  // Cost: transfer.pay declares one transfer. Hold.create declares one owned account;
46
65
  // fund, release and refund each declare one transfer. Schedules and pools add bounded
@@ -59,11 +78,19 @@ instrument transfer(payer: party, payee: party, amount: money = runtime) {
59
78
  action cancel { from: pending, to: cancelled, actor: { party: payer } }
60
79
  }
61
80
 
62
- instrument hold(payer: party, payee: party, amount: money = runtime, released_by: party = payee) {
81
+ instrument hold(payer: party, payee: party, amount: money = runtime, released_by: party = payee, late_fee: ref<money.late_fee>?) {
63
82
  summary: "Hold the declared amount until release or refund."
64
- fields { payer: account of payer, payee: account of payee, amount: money = amount, price: money = self.amount, held: account of self }
83
+ fields {
84
+ payer: account of payer, payee: account of payee, amount: money = amount, price: money = self.amount, held: account of self
85
+ when late_fee is set { lateFee: ref<late_fee> }
86
+ }
65
87
  lifecycle { states: [pending, funded, released, refunded, cancelled], initial: pending }
66
- action create { }
88
+ action create {
89
+ when late_fee is set {
90
+ requires self.lateFee.payer == self.payer
91
+ requires self.lateFee.payee == self.payee
92
+ }
93
+ }
67
94
  action fund {
68
95
  from: pending, to: funded, actor: { party: payer }
69
96
  moves self.amount from payer to self.held economics { purpose: principal, sourceParty: payer }
@@ -79,6 +106,7 @@ instrument hold(payer: party, payee: party, amount: money = runtime, released_by
79
106
  action refund {
80
107
  from: funded, to: refunded, actor: { party: payee }
81
108
  moves self.amount from self.held to payer economics { purpose: principal, sourceParty: programOperator }
109
+ when late_fee is set { invoke: [{ selection: { instrument: late_fee, reference: "payer", anchor: self.payer, states: [running], limit: 1, where: { id: { field: self.lateFee } } }, action: stop, input: {} }] }
82
110
  }
83
111
  action cancel { from: pending, to: cancelled }
84
112
  }
@@ -92,12 +120,22 @@ instrument split(payer: party, amount: money = runtime, shares: split = { progra
92
120
  action cancel { from: pending, to: cancelled }
93
121
  }
94
122
 
95
- instrument schedule(payer: party, payee: party, amount: money = runtime, count: integer(1, 366) = 12, every: period?) {
96
- summary: "Pieces due on listed dates that sum to the total, or one amount each period from a start date."
123
+ instrument schedule(payer: party, payee: party, amount: money = runtime, count: integer(1, 366)?, every: period?) {
124
+ summary: "Pieces due on listed dates that sum to the total, or one amount each period from a start date until stopped, or for count periods."
125
+ dependencies {
126
+ count {
127
+ selector: every, is: unset
128
+ message: "Attachment `{attachment}` charges on a list of dates, so it needs `count`, the number of dates."
129
+ fix: "Add `count: 3` for three dated pieces, or add `every: 1 month` to charge each month until stopped or cancelled."
130
+ }
131
+ }
97
132
  fields {
98
133
  amount: money = amount
99
134
  when every is unset { count: integer = count, dates: list(date, count) }
100
- when every is set { startsAt: date }
135
+ when every is set {
136
+ startsAt: date
137
+ when count is set { count: integer = count }
138
+ }
101
139
  }
102
140
  lifecycle { states: [draft, active, complete, cancelled], initial: draft }
103
141
  action create {
@@ -167,13 +205,13 @@ instrument pool(payer: party, payee: party, target: money, closes: date) {
167
205
  action fail {
168
206
  from: open, to: failed, actor: clock, due: { at: self.closesAt }
169
207
  requires self.held.balance < self.target
170
- invoke: [{ selection: { instrument: contribution, reference: "pool", anchor: self.id, states: [paid], limit: 366 }, action: refund, input: {} }]
208
+ invoke: [{ selection: { instrument: contribution, reference: "pool", anchor: self.id, states: [paid], limit: 253 }, action: refund, input: {} }]
171
209
  }
172
210
  records {
173
211
  contribution: {
174
212
  fields { pool: ref<parent>, payer: account of payer, amount: money, remaining: money? }
175
213
  lifecycle { states: [pending, paid, refunded], initial: pending }
176
- invariants count of { instrument: current(), reference: "pool", anchor: self.pool, states: [paid], limit: 366 } <= 366
214
+ invariants count of { instrument: current(), reference: "pool", anchor: self.pool, states: [paid], limit: 253 } <= 253
177
215
  action create { requires self.pool in [open] }
178
216
  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 economics { purpose: principal, sourceParty: payer } }
179
217
  action refund { from: paid, to: refunded, actor: { parent: parent }, moves self.amount from self.pool.held to self.payer economics { purpose: principal, sourceParty: programOperator } }
@@ -239,24 +277,42 @@ instrument late_fee(payer: party, payee: party, amount: money, cap: money, perio
239
277
  fields {
240
278
  payer: account of payer, payee: account of payee, amount: money = amount, cap: money = cap, ceiling: money = multiply(self.amount, 366)
241
279
  dueAt: date, graceEndsAt: date?, firstAt: date?, nextAt: date?, followingAt: date?, charge: money?, left: money?, remaining: money?, stoppedAt: date?
280
+ stride: integer?, strideAt: date?, elapsed: integer?, lastAt: date?
242
281
  }
243
282
  lifecycle { states: [running, settling, capped, stopped], initial: running }
244
283
  action create {
245
- actor: { party: payer }
284
+ actor: { party: programOperator }
246
285
  requires self.amount > "0"
247
286
  requires self.cap >= self.amount
248
287
  requires self.cap <= self.ceiling
249
- calculate: [{ target: graceEndsAt, op: shift, date: { field: self.dueAt }, milliseconds: { literal: grace }, direction: after }, { target: firstAt, op: shift, date: { field: self.graceEndsAt }, milliseconds: { literal: period }, direction: after }]
250
- set: { nextAt: { field: self.firstAt }, left: { field: self.cap } }
288
+ calculate: [{ target: graceEndsAt, op: shift, date: { field: self.dueAt }, milliseconds: { literal: grace }, direction: after }, { target: firstAt, op: shift, date: { field: self.graceEndsAt }, milliseconds: { literal: period }, direction: after }, { target: stride, op: divide, amount: { literal: period }, divisor: { literal: 1000 }, rounding: floor }, { target: strideAt, op: step, date: { field: self.graceEndsAt }, period: { literal: "PT1S" }, times: { field: self.stride } }, { target: elapsed, op: sum, values: [{ literal: 0 }] }]
289
+ requires self.strideAt == self.firstAt
290
+ set: { nextAt: { field: self.firstAt }, left: { field: self.cap }, lastAt: { literal: "1970-01-01T00:00:00.000Z" } }
251
291
  }
252
292
  action charge {
253
293
  summary: "Charge late fee"
254
294
  from: running, to: running, actor: clock, due: { at: self.nextAt }
255
- calculate: [{ target: charge, op: minimum, values: [{ field: self.amount }, { field: self.left }] }, { target: remaining, op: subtract, base: { field: self.left }, subtract: [{ field: self.charge }] }, { target: followingAt, op: shift, date: { field: self.nextAt }, milliseconds: { literal: period }, direction: after }]
256
- set: { left: { field: self.remaining }, nextAt: { field: self.followingAt } }
295
+ calculate: [{ target: charge, op: minimum, values: [{ field: self.amount }, { field: self.left }] }, { target: remaining, op: subtract, base: { field: self.left }, subtract: [{ field: self.charge }] }, { target: followingAt, op: shift, date: { field: self.nextAt }, milliseconds: { literal: period }, direction: after }, { target: elapsed, op: sum, values: [{ field: self.elapsed }, { field: self.stride }] }]
296
+ set: { left: { field: self.remaining }, nextAt: { field: self.followingAt }, lastAt: { field: self.nextAt } }
257
297
  moves self.charge from payer to payee economics { recipient: payee, company: { purpose: earning, sourceParty: payer }, participant: { purpose: pass_through, sourceParty: payer } }
258
298
  invoke: [{ selection: { instrument: current(), reference: "payer", anchor: self.payer, states: [running], limit: 1, where: { id: { field: self.id }, left: { literal: "0" } } }, action: finish, input: {} }]
259
299
  }
300
+ action reschedule {
301
+ summary: "Move the due time"
302
+ from: running, to: running, actor: { party: payee }
303
+ subject { dueAt: date }
304
+ invoke: [{ reference: self.id, action: follow, input: {} }]
305
+ }
306
+ action follow {
307
+ summary: "Follow the due time"
308
+ from: running, to: running, follows: dueAt
309
+ subject { dueAt: date }
310
+ // Fees run from the dueAt set at create, so the date only moves later.
311
+ requires subject.dueAt >= self.dueAt
312
+ calculate: [{ target: graceEndsAt, op: shift, date: { field: subject.dueAt }, milliseconds: { literal: grace }, direction: after }, { target: firstAt, op: shift, date: { field: self.graceEndsAt }, milliseconds: { literal: period }, direction: after }, { target: followingAt, op: step, date: { field: self.firstAt }, period: { literal: "PT1S" }, times: { field: self.elapsed } }]
313
+ requires self.followingAt > self.lastAt
314
+ set: { dueAt: { field: subject.dueAt }, nextAt: { field: self.followingAt } }
315
+ }
260
316
  action collect {
261
317
  summary: "Collect owed late fee"
262
318
  from: settling, to: settling, actor: clock, due: { at: self.nextAt }
package/std/reporting.hsx CHANGED
@@ -119,19 +119,19 @@ instrument portfolio(on: ref<financing.installments>[], default_days: integer(1,
119
119
  },
120
120
  {
121
121
  name: "principal",
122
- field: "principalReceivable.balance",
122
+ field: "openPrincipal",
123
123
  type: { kind: "money", currency: "SAR" },
124
124
  required: true
125
125
  },
126
126
  {
127
127
  name: "profit",
128
- field: "profitReceivable.balance",
128
+ field: "openProfit",
129
129
  type: { kind: "money", currency: "SAR" },
130
130
  required: true
131
131
  },
132
132
  {
133
133
  name: "earnedProfit",
134
- field: "profitEarned.balance",
134
+ field: "earnedProfit",
135
135
  type: { kind: "money", currency: "SAR" },
136
136
  required: true
137
137
  },
@@ -468,19 +468,19 @@ instrument portfolio(on: ref<financing.installments>[], default_days: integer(1,
468
468
  },
469
469
  {
470
470
  name: "principal",
471
- field: "principalReceivable.balance",
471
+ field: "openPrincipal",
472
472
  type: { kind: "money", currency: "SAR" },
473
473
  required: true
474
474
  },
475
475
  {
476
476
  name: "profit",
477
- field: "profitReceivable.balance",
477
+ field: "openProfit",
478
478
  type: { kind: "money", currency: "SAR" },
479
479
  required: true
480
480
  },
481
481
  {
482
482
  name: "earnedProfit",
483
- field: "profitEarned.balance",
483
+ field: "earnedProfit",
484
484
  type: { kind: "money", currency: "SAR" },
485
485
  required: true
486
486
  },