@hyperscale0/hsx 1.0.130 → 1.0.132

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/lending.hsx CHANGED
@@ -5,12 +5,17 @@ header lending
5
5
  // principal/profit distributions and unbound source owners remain unclassified.
6
6
 
7
7
  // Round target is plan.principal from runtime subject.price, not its credit limit.
8
- // Expose round.create and close plus commitment.create and withdraw. A commitment
8
+ // The operator opens the round and closes it before closesAt. Expose
9
+ // round.create and close plus commitment.create and withdraw. A commitment
9
10
  // debits the bound investor's original active wallet before closesAt. Investor cap
10
- // aggregates by investor across wallets. Failure refunds those original wallets.
11
+ // aggregates by investor across wallets. A round still open at closesAt fails,
12
+ // even when fully committed, and refunds those original wallets.
11
13
  // Closing needs the exact target; it funds plan.capital and does not disburse the
12
14
  // loan. Expose plan.disburse separately after signing and limit approval.
13
- // Distribution.create needs a recorded, unconsumed settlement matching the plan.
15
+ // Distribution.create needs a recorded settlement matching the plan. Only the
16
+ // payout consumes it: distribute_cash or distribute_loss claims the settlement
17
+ // and marks it distributed, so the plan's unwind_paid keeps that profit, while an
18
+ // abandoned distribution leaves the settlement refundable.
14
19
  // Prepare cash or loss first, then create shares against funded commitments of
15
20
  // that round. Distribution needs pending share weights equal to the round target.
16
21
  // Cash fee is a percentage of retained profit, VAT a percentage of that fee.
@@ -30,7 +35,7 @@ instrument round(borrower: party, plan: ref<financing.installments> = object(fin
30
35
  held: account of self
31
36
  }
32
37
  lifecycle { states: [open, funded, failed], initial: open }
33
- action create { requires unique "funding_plan" on [self.plan]; requires self.borrower == self.plan.borrower }
38
+ action create { actor: { party: operator }, requires unique "funding_plan" on [self.plan]; requires self.borrower == self.plan.borrower }
34
39
  action close {
35
40
  from: open, to: funded, actor: { party: operator }
36
41
  requires sum "amount" of { instrument: commitments, reference: "round", anchor: self.id, states: [committed], limit: 366 } == self.price
@@ -40,7 +45,6 @@ instrument round(borrower: party, plan: ref<financing.installments> = object(fin
40
45
  }
41
46
  action fail {
42
47
  from: open, to: failed, actor: clock, due: { at: self.closesAt }
43
- requires self.held.balance < self.price
44
48
  invoke: [{ selection: { instrument: commitments, reference: "round", anchor: self.id, states: [committed], limit: 366 }, action: refund, input: {} }]
45
49
  }
46
50
  }
@@ -81,7 +85,9 @@ instrument distribution(round: ref<lending.round>, receipt: ref, commitments: re
81
85
  held: account of self, losses: account(self, claim), residualLoss: account(residual_to, claim, "loss")
82
86
  }
83
87
  lifecycle { states: [pending, cash_ready, loss_ready, distributed], initial: pending }
84
- action create { requires self.round in [funded]; requires self.source in [recorded]; requires self.source.plan == self.round.plan; requires unique "settlement_consumption" on [self.source.plan, self.source.identity] }
88
+ action create {
89
+ requires self.round in [funded]; requires self.source in [recorded]; requires self.source.plan == self.round.plan
90
+ }
85
91
  action prepare_cash {
86
92
  from: pending, to: cash_ready
87
93
  requires self.mode == "cash"; requires self.source.kind == "cash"; requires self.source in [recorded]
@@ -101,9 +107,10 @@ instrument distribution(round: ref<lending.round>, receipt: ref, commitments: re
101
107
  }
102
108
  action distribute_cash { allowZero: true,
103
109
  from: cash_ready, to: distributed, actor: { party: operator }
110
+ requires self.source in [recorded]; requires unique "settlement_consumption" on [self.source.plan, self.source.identity]
104
111
  requires sum "weight" of { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit: 366 } == self.round.price
105
112
  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 }
106
- invoke: [{ selection: { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit: 366 }, action: cash, input: {} }, { reference: self.id, action: residual_cash, input: {} }]
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: {} }]
107
114
  }
108
115
  action residual_cash { allowZero: true,
109
116
  from: distributed, to: distributed, actor: { parent: current() }
@@ -111,9 +118,10 @@ instrument distribution(round: ref<lending.round>, receipt: ref, commitments: re
111
118
  }
112
119
  action distribute_loss { allowZero: true,
113
120
  from: loss_ready, to: distributed, actor: { party: operator }
121
+ requires self.source in [recorded]; requires unique "settlement_consumption" on [self.source.plan, self.source.identity]
114
122
  requires sum "weight" of { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit: 366 } == self.round.price
115
123
  moves self.net from self.source.lossAccount to self.losses
116
- invoke: [{ selection: { instrument: share, reference: "distribution", anchor: self.id, states: [pending], limit: 366 }, action: loss, input: {} }, { reference: self.id, action: residual_loss, input: {} }]
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: {} }]
117
125
  }
118
126
  action residual_loss { allowZero: true,
119
127
  from: distributed, to: distributed, actor: { parent: current() }
@@ -4,7 +4,8 @@ header marketplace
4
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
- // and decision actions individually. Order fulfillment does not move escrow cash.
7
+ // and decision actions individually. Order fulfillment does not move escrow cash;
8
+ // only the fulfiller marks it, the Product operator unless the seller is bound.
8
9
  // Pair an order with escrow.hold and expose its fund, deliver, accept, dispute,
9
10
  // verify_return and refund aliases. Refund needs disputed -> return_verified,
10
11
  // with payee evidence, then returns the full price to payer; accept pays payee.
@@ -34,7 +35,7 @@ instrument listing(seller: party) {
34
35
  action sell { from: active, to: sold, actor: { party: seller }, requires count of { instrument: all(marketplace.reservation), reference: "listing", anchor: self.id, states: [active], limit: 366 } == 0 }
35
36
  }
36
37
 
37
- instrument order(listing: ref<marketplace.listing>, buyer: party) {
38
+ instrument order(listing: ref<marketplace.listing>, buyer: party, fulfiller: party = programOperator) {
38
39
  revisioned: true
39
40
  summary: "A buyer's order for one listing at its declared price."
40
41
  fields { listing: ref<listing>, buyer: account of buyer, price: money, reservations: integer?, ownReservations: integer?, blockingQuotes: integer?, ownBlockingQuotes: integer? }
@@ -60,7 +61,7 @@ instrument order(listing: ref<marketplace.listing>, buyer: party) {
60
61
  requires count of { instrument: current(), reference: "listing", anchor: self.listing, states: [placed, fulfilled], limit: 366 } <= 1
61
62
  }
62
63
  invariants count of { instrument: current(), reference: "listing", anchor: self.listing, states: [placed, fulfilled], limit: 366 } <= 1
63
- action fulfill { from: placed, to: fulfilled }
64
+ action fulfill { from: placed, to: fulfilled, actor: { party: fulfiller } }
64
65
  action cancel { from: [quoted, placed], to: cancelled, actor: { party: buyer } }
65
66
  }
66
67
 
package/std/money.hsx CHANGED
@@ -7,15 +7,20 @@ header money
7
7
  // Expose each attachment's create and caller actions explicitly. A transfer or
8
8
  // split pays once; a hold exposes fund, release and refund separately. Release
9
9
  // pays payee and refund returns payer. A hold has no dispute or damage decision.
10
- // A schedule needs count unique occurrence records before activation; each due
11
- // occurrence pays only while the schedule is active. A due time promises no retry.
10
+ // The payee proposes a schedule, which creates its count occurrences, and only
11
+ // the payer activates or cancels it. Each due occurrence pays only while the
12
+ // schedule is active. A due time promises no retry.
12
13
  // A pool accepts whole contributions only up to its remaining target and at most
13
14
  // 366 paid records. Expose pay to release the exact target. Clock failure refunds
14
15
  // original contributors; excess acceptance and partial contribution are absent.
15
16
  // A swap reserves both legs before exchange or voids reservations on expiry.
16
17
  // Payout instruct reserves across the selected adapter; confirm/reject require
17
18
  // matching terminal instruction evidence. An acknowledgement cannot settle it.
18
- // Metered bill charges a unique usage identity at the fixed unit price.
19
+ // Metered bill charges a unique usage identity at the fixed unit price, only
20
+ // under an active mandate the payer created: bills stop at its expiresAt, and at
21
+ // most 366 billed events across every metered attachment on the mandate may sum
22
+ // their payer debit, buyer fee and tax included, to its limit. The payer revokes
23
+ // it to stop billing. Only the payee cancels its own unbilled usage.
19
24
 
20
25
  // Cost: transfer.pay declares one transfer. Hold.create declares one owned account;
21
26
  // fund, release and refund each declare one transfer. Schedules and pools add bounded
@@ -70,16 +75,16 @@ instrument schedule(payer: party, payee: party, amount: money = runtime, count:
70
75
  summary: "A finite schedule whose dated pieces sum exactly to the declared total."
71
76
  fields { amount: money = amount, count: integer = count, dates: list(date, count) }
72
77
  lifecycle { states: [draft, active, complete, cancelled], initial: draft }
73
- action create {}
74
- action activate {
75
- from: draft, to: active
76
- requires count of { instrument: occurrence, reference: "schedule", anchor: self.id, states: [pending], limit: 366 } == self.count
78
+ action create {
79
+ actor: { party: payee }
80
+ invoke: [{ range: { count: { literal: count }, maximum: count, bind: position }, instrument: occurrence, action: create, input: { schedule: { field: self.id }, position: { field: position } } }]
77
81
  }
82
+ action activate { from: draft, to: active, actor: { party: payer } }
78
83
  action complete {
79
84
  from: active, to: complete
80
85
  requires count of { instrument: occurrence, reference: "schedule", anchor: self.id, states: [pending], limit: 366 } == 0
81
86
  }
82
- action cancel { from: [draft, active], to: cancelled }
87
+ action cancel { from: [draft, active], to: cancelled, actor: { party: payer } }
83
88
  records {
84
89
  occurrence: {
85
90
  fields {
@@ -92,7 +97,7 @@ instrument schedule(payer: party, payee: party, amount: money = runtime, count:
92
97
  amount: money = sum(self.base, self.adjustment)
93
98
  }
94
99
  lifecycle { states: [pending, paid, cancelled], initial: pending }
95
- action create { requires self.schedule in [draft]; requires unique "scheduled_position" on [self.schedule, self.position] }
100
+ action create { actor: { parent: parent }, input { schedule: ref<parent>, position: integer(1, count) }, requires unique "scheduled_position" on [self.schedule, self.position] }
96
101
  action pay { allowZero: true, from: pending, to: paid, actor: clock, due: { at: self.dueAt }, requires self.schedule in [active], moves self.amount from payer to payee }
97
102
  action cancel { from: pending, to: cancelled, requires self.schedule in [cancelled] }
98
103
  }
@@ -163,11 +168,20 @@ instrument payout(payer: party, payee: party, adapter: text, max_age: duration,
163
168
  }
164
169
  }
165
170
 
166
- instrument metered(payer: party, payee: party, unit_price: money, units: integer = 1, fee: fee = { seller: 0% }) {
171
+ instrument mandate(payer: party, payee: party) {
172
+ summary: "A payer's permission for one payee to charge up to a total until it expires."
173
+ fields { payer: account of payer, payee: account of payee, limit: money, expiresAt: date }
174
+ lifecycle { states: [active, revoked], initial: active }
175
+ action create { actor: { party: payer }, requires self.limit > "0" }
176
+ action revoke { from: active, to: revoked, actor: { party: payer } }
177
+ }
178
+
179
+ instrument metered(payer: party, payee: party, unit_price: money, units: integer = 1, fee: fee = { seller: 0% }, mandate: ref<money.mandate> = object(money.mandate)) {
167
180
  summary: "A unique usage event priced once at the fixed unit price."
168
- fields { usageIdentity: text, unitPrice: money = unit_price, units: integer = units, amount: money = multiply(self.unitPrice, self.units) }
181
+ fields { usageIdentity: text, mandate: ref<mandate>, payer: account of payer, payee: account of payee, unitPrice: money = unit_price, units: integer = units, amount: money = multiply(self.unitPrice, self.units) }
169
182
  lifecycle { states: [recorded, billed, cancelled], initial: recorded }
170
- action create { requires unique "usage_event" on [self.usageIdentity] }
171
- action bill { allowZero: true, requires self.amount > "0"; from: recorded, to: billed, actor: { party: payee }, moves self.amount from payer to payee fee fee }
172
- action cancel { from: recorded, to: cancelled }
183
+ invariants sum "bill_move1_debit" of { instrument: all(money.metered), reference: "mandate", anchor: self.mandate, states: [billed], limit: 366 } <= self.mandate.limit
184
+ action create { requires unique "usage_event" on [self.usageIdentity]; requires self.mandate.payer == self.payer; requires self.mandate.payee == self.payee }
185
+ action bill { allowZero: true, requires self.amount > "0"; from: recorded, to: billed, actor: { party: payee }, deadline: { at: self.mandate.expiresAt }, requires self.mandate in [active], moves self.amount from payer to payee fee fee }
186
+ action cancel { from: recorded, to: cancelled, actor: { party: payee } }
173
187
  }
package/std/savings.hsx CHANGED
@@ -17,6 +17,7 @@ header savings
17
17
  // Late and out-of-order collection is admitted while membership is active or
18
18
  // received and circle is active. Clock admission does not promise scheduler retry.
19
19
  // Circle.close needs every membership received and an empty held account.
20
+ // Only the Product operator disbands a forming circle.
20
21
  // Circle creation collects circleDates, one date per member for contributions and payouts.
21
22
 
22
23
  // Cost: circle.create declares one owned account. Each contribution payment
@@ -37,14 +38,14 @@ instrument circle(contribution: money, members: integer(1, 60), starts: date, me
37
38
  requires count of { instrument: memberships, reference: "circle", anchor: self.id, states: [received], limit: 60 } == self.members
38
39
  requires self.held.balance == "0"
39
40
  }
40
- action disband { from: forming, to: disbanded }
41
+ action disband { from: forming, to: disbanded, actor: { party: programOperator } }
41
42
  }
42
43
 
43
44
  instrument membership(circle: ref<savings.circle>, member: party) {
44
45
  summary: "A member contributes on the circle calendar and receives the pot in the assigned seat."
45
46
  fields { circle: ref<circle>, member: account of member, seat: integer(1, 60), preceding: integer = subtract(self.seat, 1), receivesAt: date = at(self.circle.circleDates, self.seat), contribution: money = self.circle.contribution, total: money = self.circle.pot }
46
47
  lifecycle { states: [joined, active, received, withdrawn], initial: joined }
47
- action create { requires self.seat <= self.circle.members; requires self.circle in [forming]; requires unique "circle_seat" on [self.circle, self.seat]; requires unique "circle_member" on [self.circle, self.member] }
48
+ action create { actor: { party: member }, requires self.seat <= self.circle.members; requires self.circle in [forming]; requires unique "circle_seat" on [self.circle, self.seat]; requires unique "circle_member" on [self.circle, self.member] }
48
49
  action activate {
49
50
  from: joined, to: active
50
51
  requires self.circle in [active]
package/std/wallet.hsx CHANGED
@@ -7,7 +7,9 @@ header wallet
7
7
  // Expose balance.create, activate, topup and withdraw as needed. Holder moves
8
8
  // cash only while active. Freeze blocks new topups, withdrawals and spend reserves;
9
9
  // close requires no held cash. Only the Product operator freezes or unfreezes.
10
- // A separate spend record checks its amount against spendLimit at creation.
10
+ // The holder closes the wallet, or the Product operator with operator_close.
11
+ // Only the wallet holder creates a spend record, which checks its amount against
12
+ // spendLimit; bind the spend holder to the balance holder.
11
13
  // Expose reserve, pay and cancel for the bound payee. Reserve requires an active
12
14
  // wallet before expiresAt; pay posts the whole reservation, cancel/expiry void it.
13
15
  // Existing reservations may still post after the wallet freezes. There is no
@@ -25,7 +27,7 @@ instrument balance(holder: party, spend_limit: money = 100000 SAR, topup_fee: pe
25
27
  summary: "A holder's reusable balance with an immutable per-spend ceiling."
26
28
  fields { holder: account of holder, held: account of self, spendLimit: money = spend_limit, topupFee: money?, topupCredit: money? }
27
29
  lifecycle { states: [pending, active, frozen, closed], initial: pending }
28
- action create { }
30
+ action create { actor: { party: holder } }
29
31
  action activate { from: pending, to: active, actor: { party: holder } }
30
32
  action topup {
31
33
  allowZero: true, requires input.amount > "0"
@@ -45,15 +47,18 @@ instrument balance(holder: party, spend_limit: money = 100000 SAR, topup_fee: pe
45
47
  }
46
48
  action freeze { from: active, to: frozen, actor: { party: programOperator } }
47
49
  action unfreeze { from: frozen, to: active, actor: { party: programOperator } }
48
- action close { from: [pending, active], to: closed, requires self.held.balance == "0" }
50
+ action close { from: [pending, active], to: closed, actor: { party: holder }, requires self.held.balance == "0" }
51
+ action operator_close { from: [pending, active], to: closed, actor: { party: programOperator }, requires self.held.balance == "0" }
49
52
  }
50
53
 
51
- instrument spend(wallet: ref<wallet.balance>, payee: party) {
54
+ instrument spend(wallet: ref<wallet.balance>, payee: party, holder: party = party(person)) {
52
55
  summary: "Reserve one wallet spend, then post or void the same reservation."
53
- fields { wallet: ref<wallet>, amount: money, expiresAt: date, reservation: text? }
56
+ fields { wallet: ref<wallet>, holder: account of holder, amount: money, expiresAt: date, reservation: text? }
54
57
  lifecycle { states: [pending, reserved, paid, cancelled, expired], initial: pending }
55
58
  action create {
59
+ actor: { party: holder }
56
60
  requires self.wallet in [active]
61
+ requires self.wallet.holder == self.holder
57
62
  requires self.amount <= self.wallet.spendLimit
58
63
  }
59
64
  action reserve {