@hyperscale0/hsx 5.3.0 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/README.md +2 -2
- package/bin/hsx.ts +0 -2
- package/dist/bin/hsx.js +0 -2
- package/dist/bin/hsx.js.map +1 -1
- package/dist/src/cli.d.ts +0 -2
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +9 -4
- package/dist/src/cli.js.map +1 -1
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +22 -9
- package/dist/src/compile.js.map +1 -1
- package/dist/src/header-source.d.ts.map +1 -1
- package/dist/src/header-source.js +5 -0
- package/dist/src/header-source.js.map +1 -1
- package/dist/src/std-bundle.js +12 -12
- package/dist/src/std-bundle.js.map +1 -1
- package/dist/src/version.d.ts +1 -2
- package/dist/src/version.d.ts.map +1 -1
- package/dist/src/version.js +1 -2
- package/dist/src/version.js.map +1 -1
- package/docs/README.md +49 -4
- package/docs/headers.md +1 -1
- package/package.json +3 -3
- package/src/cli.ts +12 -6
- package/src/compile.ts +32 -9
- package/src/header-source.ts +10 -0
- package/src/std-bundle.ts +12 -12
- package/src/version.ts +1 -2
- package/std/cards.hsx +12 -0
- package/std/collections.hsx +11 -0
- package/std/escrow.hsx +16 -2
- package/std/financing.hsx +34 -3
- package/std/insurance.hsx +20 -5
- package/std/lending.hsx +14 -0
- package/std/marketplace.hsx +14 -0
- package/std/money.hsx +13 -0
- package/std/reporting.hsx +10 -0
- package/std/savings.hsx +28 -11
- package/std/travel.hsx +22 -4
- package/std/wallet.hsx +9 -0
package/src/version.ts
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export const HSX_VERSION = "5.
|
|
2
|
-
export const HSX_TARGET_UDL_VERSION = 4;
|
|
1
|
+
export const HSX_VERSION = "5.4.0";
|
package/std/cards.hsx
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
header cards
|
|
2
2
|
|
|
3
|
+
// Activate holder and card before creating and approving an authorization.
|
|
4
|
+
// Expose authorization.create, approve and capture; expose decline and reverse
|
|
5
|
+
// separately if callers need those paths. Issuer declines only from pending;
|
|
6
|
+
// merchant captures or reverses an approved reservation. Expiry voids approved
|
|
7
|
+
// reservations or abandons pending ones; it does not delay a captured refund.
|
|
8
|
+
// Create one transaction for the captured authorization and expose its refund.
|
|
9
|
+
// Refund is available immediately from posted, even before authorization.expiresAt,
|
|
10
|
+
// and moves the whole captured amount from merchant to holder under programOperator.
|
|
11
|
+
// No partial capture, repeated capture or partial transaction refund is provided.
|
|
12
|
+
// Disputes need a posted transaction, issuer review and win before their deadline;
|
|
13
|
+
// win invokes the same refund. The alias never changes the declared action actor.
|
|
14
|
+
|
|
3
15
|
instrument cardholder(holder: party) {
|
|
4
16
|
summary: "An account holder whose card access can be activated, suspended or closed."
|
|
5
17
|
fields { holder: account of holder }
|
package/std/collections.hsx
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
header collections
|
|
2
2
|
|
|
3
|
+
// Expose case.create, assign and recover as separate steps. Creation needs overdue
|
|
4
|
+
// outstanding principal and a matching slice/plan. Assignment belongs to capital;
|
|
5
|
+
// recovery belongs to agency and pays the linked payment before the agency fee.
|
|
6
|
+
// Payment must belong to the same plan. Recovered means that payment was processed,
|
|
7
|
+
// not that the entire debt was repaid. Fee debits plan.capital, not the borrower.
|
|
8
|
+
// Expose recall for capital and return_uncollected for agency when needed.
|
|
9
|
+
// Contacts need an assigned case, matching agency, local hours and rolling count.
|
|
10
|
+
// Contact outcomes are separate records. Reminder dispatch is operator-owned;
|
|
11
|
+
// before_due/overdue are clock-owned and require active debt. Scheduled admission
|
|
12
|
+
// does not prove delivery or retry. No collector can infer authority from an alias.
|
|
13
|
+
|
|
3
14
|
instrument case(on: ref<financing.installments>[], agency: party, capital: party = programOperator, overdue: duration = 3d, fee: percent = 20%) {
|
|
4
15
|
revisioned: true
|
|
5
16
|
summary: "Refer an overdue installment without creating another debt."
|
package/std/escrow.hsx
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
header escrow
|
|
2
2
|
|
|
3
|
+
// Expose create, fund, deliver and accept for the purchase path. Create captures
|
|
4
|
+
// subject.price; acceptance pays payee less the configured fee, not payer.
|
|
5
|
+
// Expose dispute, verify_return and refund separately for the return path.
|
|
6
|
+
// Payer disputes delivered goods; payee verifies return evidence from disputed;
|
|
7
|
+
// payee refunds the entire held price from return_verified by default.
|
|
8
|
+
// Acceptance expiry enters disputed, never released. Neither verify_return nor
|
|
9
|
+
// refund becomes public merely because dispute is exposed. Clock actions remain
|
|
10
|
+
// internal. Expose cancel for a pending hold, including a checkout-funded hold;
|
|
11
|
+
// it returns its credited balance to payer before cancelling. Confirm requires
|
|
12
|
+
// the whole captured price in held. This flow has no partial release or refund.
|
|
13
|
+
|
|
3
14
|
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 }) {
|
|
4
15
|
revisioned: true
|
|
5
16
|
summary: "Hold the subject price through delivery, acceptance and verified return."
|
|
@@ -16,7 +27,7 @@ instrument hold(payer: party = party(person), payee: party = programOperator, ac
|
|
|
16
27
|
action fund {
|
|
17
28
|
from: pending, to: funded, actor: { party: payer }
|
|
18
29
|
subject { price: money }
|
|
19
|
-
moves
|
|
30
|
+
moves self.price from payer to self.held
|
|
20
31
|
}
|
|
21
32
|
action confirm {
|
|
22
33
|
from: pending, to: funded
|
|
@@ -48,5 +59,8 @@ instrument hold(payer: party = party(person), payee: party = programOperator, ac
|
|
|
48
59
|
from: dispute.refund_after, to: refunded, actor: { party: payee }
|
|
49
60
|
moves self.price from self.held to payer
|
|
50
61
|
}
|
|
51
|
-
action cancel {
|
|
62
|
+
action cancel { allowZero: true,
|
|
63
|
+
from: pending, to: cancelled, actor: { party: payer }
|
|
64
|
+
moves self.held.balance from self.held to payer
|
|
65
|
+
}
|
|
52
66
|
}
|
package/std/financing.hsx
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
header financing
|
|
2
2
|
|
|
3
|
+
// Expose create and sign on installments, then collect_down_payment as checkout
|
|
4
|
+
// for disburse_to: funds. Checkout requires a signed plan and pending escrow;
|
|
5
|
+
// it tops up only the missing down payment, without advancing lender principal.
|
|
6
|
+
// Disburse still collects any missing down payment, activates slices, and confirms
|
|
7
|
+
// the fully funded hold. Existing escrow credit counts once toward the down payment.
|
|
8
|
+
// Borrower-directed disbursement excludes collect_down_payment. A down_payment
|
|
9
|
+
// there reduces principal but does not collect cash. Price is subject metadata,
|
|
10
|
+
// not the borrower limit. The escrow payer and price must match the plan.
|
|
11
|
+
// Expose escrow cancel to return checkout cash if financing is abandoned; plan
|
|
12
|
+
// void alone cancels the agreement and does not refund its separate escrow.
|
|
13
|
+
// Approve borrower and portfolio limits before disbursement. Create builds the
|
|
14
|
+
// dated slice records; payments need a separate payment record and pay action.
|
|
15
|
+
// Expose parent payoff separately and create a current, unexpired payoff_quote.
|
|
16
|
+
// Expose late_charge.create as propose_assessment before the clock can assess;
|
|
17
|
+
// provide matching plan, slice and borrower. Fine is fixed money, not a percentage.
|
|
18
|
+
// Collection uses the minimum of payment cash and outstanding fine; a partial
|
|
19
|
+
// payment does not settle the remaining fine. Recovery cost needs its own record.
|
|
20
|
+
// Profit share credits capital's allocatedProfitPayable, not an outside agency.
|
|
21
|
+
// Credit lines need adapter binding at activation; draws are ledger advances,
|
|
22
|
+
// not evidence of outside funding. Caller-authorized limit approvals and advances
|
|
23
|
+
// need the Product's operation authority. No clock declaration proves retries.
|
|
24
|
+
|
|
3
25
|
instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3, profit: percent, disburse_to: enum(funds, borrower) = funds, profit_earned: enum(on_payment, by_schedule, at_disbursement) = on_payment, apply: enum(fines_profit_principal, principal_profit, pro_rata) = fines_profit_principal, payoff_rebate: percent = 100%, write_off_after: duration = 90d, max_extension: duration = 90d, max_amendments: integer(0, 366) = 2, waiver_limit: percent = 100%, amendment_expiry: duration = 7d, payoff_quote_expiry: duration = 1h, allow_overdue: enum(allowed, blocked) = allowed, assessed_fines: enum(carry, require_waiver) = carry, down_payment: percent = 0%, funds: ref<escrow.hold>?, borrower: party = party(person), capital: party = programOperator, share: percent = 0%, limits: ref<financing.limits> = object(financing.limits), portfolio: ref<financing.portfolio_limit> = object(financing.portfolio_limit)) {
|
|
4
26
|
dependencies {
|
|
5
27
|
funds {
|
|
@@ -29,13 +51,22 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
29
51
|
action create {
|
|
30
52
|
subject { price: money }
|
|
31
53
|
requires self.borrowerLimit.borrower == self.borrower
|
|
32
|
-
when disburse_to is funds { requires self.borrower == self.funds.payer }
|
|
54
|
+
when disburse_to is funds { requires self.borrower == self.funds.payer; requires self.price == self.funds.price }
|
|
33
55
|
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 } } }]
|
|
34
56
|
}
|
|
35
57
|
action sign {
|
|
36
58
|
from: quoted, to: signed, actor: { party: borrower }, input { signedOffer: text, affordabilityReference: text }
|
|
37
59
|
requires count of { instrument: slice, reference: "plan", anchor: self.id, states: [draft], limit: 366 } == self.months
|
|
38
60
|
}
|
|
61
|
+
action collect_down_payment { allowZero: true,
|
|
62
|
+
from: signed, to: signed, actor: { party: borrower }
|
|
63
|
+
when disburse_to is borrower { requires true == false }
|
|
64
|
+
when disburse_to is funds {
|
|
65
|
+
requires self.funds in [pending]
|
|
66
|
+
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 }] }]
|
|
67
|
+
moves self.downPaymentRemaining from self.borrower to self.funds.held
|
|
68
|
+
}
|
|
69
|
+
}
|
|
39
70
|
action disburse { allowZero: true,
|
|
40
71
|
from: signed, to: active, actor: { party: capital }
|
|
41
72
|
requires self.borrowerLimit in [approved]
|
|
@@ -350,7 +381,7 @@ instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3
|
|
|
350
381
|
fields { slice: ref<slice>, dueAt: date = after(self.slice.dueAt, 0) }
|
|
351
382
|
lifecycle { states: [pending, recognised], initial: pending }
|
|
352
383
|
action create { actor: { parent: slice }, input { slice: ref<slice> }, requires unique "profit_recognition" on [self.slice] }
|
|
353
|
-
action reschedule { from: pending, to: pending, actor: { parent: slice } }
|
|
384
|
+
action reschedule { from: pending, to: pending, actor: { parent: slice }, calculate: [{ target: dueAt, op: shift, date: { field: self.slice.dueAt }, milliseconds: { literal: 0 }, direction: after }] }
|
|
354
385
|
action mature { allowZero: true, from: pending, to: recognised, actor: clock, due: { at: self.dueAt }, moves self.slice.profitReceivable.balance from self.slice.profitReceivable to self.slice.profitEarned }
|
|
355
386
|
}
|
|
356
387
|
payment: {
|
|
@@ -451,7 +482,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
|
|
|
451
482
|
}
|
|
452
483
|
revisioned: true
|
|
453
484
|
familyRevision: 1
|
|
454
|
-
summary: "Separate
|
|
485
|
+
summary: "Separate fine and evidenced recovery claims on an overdue installment."
|
|
455
486
|
fields {
|
|
456
487
|
plan: ref<on>, slice: ref<on.slice>, borrower: account of borrower, fine: money = fine, overdueAt: date?
|
|
457
488
|
receivable: account(self, claim), debt: account(borrower, claim, contra, "debt")
|
package/std/insurance.hsx
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
header insurance
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// Expose cover.create, activate and cancel separately. Activation binds adapter;
|
|
4
|
+
// each slice needs a separately created unique period and an active cover to collect.
|
|
5
|
+
// Collection debits holder for insurerPremium to the insurer and commission to
|
|
6
|
+
// broker as two moves. Broker defaults to programOperator; bind a declared business
|
|
7
|
+
// for a distinct agency. The two amounts sum to premium. Zero portions are allowed.
|
|
8
|
+
// Cover.cancel invokes refund only on paid slices, before each startsAt. It returns
|
|
9
|
+
// insurerPremium from insurer and commission from broker to holder. Active or
|
|
10
|
+
// expired periods are excluded; insufficient recipient funds refuse the reversal.
|
|
11
|
+
// Slice refund is parent-owned, not a public alias. Expose cover.cancel instead.
|
|
12
|
+
// Claims require active cover. Expose claim.create, approve, deny and pay as needed.
|
|
13
|
+
// Inspector approves OR denies from submitted. After approval only pay or clock
|
|
14
|
+
// expiry can consume the reservation; deny is never available after approval.
|
|
15
|
+
// Inspector is a bound party, not necessarily staff. These are ledger transfers;
|
|
16
|
+
// outside settlement needs the boundary protocol, not a claim of provider success.
|
|
17
|
+
|
|
18
|
+
instrument cover(holder: party, adapter: text, commission: percent = 0%, broker: party = programOperator, covers: ref, premium: money = runtime) {
|
|
4
19
|
summary: "Recurring cover bound to an outside insurer across an ADL boundary."
|
|
5
20
|
fields { covers: ref<covers>, holder: account of holder, insurer: account(adapter(adapter), cash, "premium"), expiresAt: date }
|
|
6
21
|
lifecycle { states: [pending, active, cancelled, expired], initial: pending }
|
|
@@ -33,8 +48,8 @@ instrument cover(holder: party, adapter: text, commission: percent = 0%, covers:
|
|
|
33
48
|
requires self.premium > "0"
|
|
34
49
|
from: pending, to: paid, actor: { party: holder }
|
|
35
50
|
requires self.cover in [active]
|
|
36
|
-
moves self.
|
|
37
|
-
moves self.
|
|
51
|
+
moves self.insurerPremium from holder to self.cover.insurer
|
|
52
|
+
moves self.commission from holder to broker
|
|
38
53
|
}
|
|
39
54
|
action start { from: paid, to: active, actor: clock, due: { at: self.startsAt } }
|
|
40
55
|
action expire { from: active, to: expired, actor: clock, due: { at: self.endsAt } }
|
|
@@ -42,8 +57,8 @@ instrument cover(holder: party, adapter: text, commission: percent = 0%, covers:
|
|
|
42
57
|
allowZero: true
|
|
43
58
|
requires self.premium > "0"
|
|
44
59
|
from: paid, to: refunded, actor: { parent: parent }, deadline: { at: self.startsAt }
|
|
45
|
-
moves self.insurerPremium from self.cover.insurer to
|
|
46
|
-
moves self.
|
|
60
|
+
moves self.insurerPremium from self.cover.insurer to holder
|
|
61
|
+
moves self.commission from broker to holder
|
|
47
62
|
}
|
|
48
63
|
}
|
|
49
64
|
}
|
package/std/lending.hsx
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
header lending
|
|
2
2
|
|
|
3
|
+
// Round target is plan.principal from runtime subject.price, not its credit limit.
|
|
4
|
+
// Expose round.create and close plus commitment.create and withdraw. A commitment
|
|
5
|
+
// debits the bound investor's original active wallet before closesAt. Investor cap
|
|
6
|
+
// aggregates by investor across wallets. Failure refunds those original wallets.
|
|
7
|
+
// Closing needs the exact target; it funds plan.capital and does not disburse the
|
|
8
|
+
// loan. Expose plan.disburse separately after signing and limit approval.
|
|
9
|
+
// Distribution.create needs a recorded, unconsumed settlement matching the plan.
|
|
10
|
+
// Prepare cash or loss first, then create shares against funded commitments of
|
|
11
|
+
// that round. Distribution needs pending share weights equal to the round target.
|
|
12
|
+
// Cash fee is a percentage of retained profit, VAT a percentage of that fee.
|
|
13
|
+
// Principal plus retained profit less fee and VAT goes to weighted wallets;
|
|
14
|
+
// floored residual goes to residual_to. Loss mode moves claim balances, not cash.
|
|
15
|
+
// Child share execution is parent-owned. No report or clock proves outside payout.
|
|
16
|
+
|
|
3
17
|
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)) {
|
|
4
18
|
summary: "A threshold funding round; failed rounds return each original wallet contribution."
|
|
5
19
|
fields {
|
package/std/marketplace.hsx
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
header marketplace
|
|
2
2
|
|
|
3
|
+
// Listing, order and reservation are separate agreements. Expose their create
|
|
4
|
+
// and decision actions individually. Order fulfillment does not move escrow cash.
|
|
5
|
+
// Pair an order with escrow.hold and expose its fund, deliver, accept, dispute,
|
|
6
|
+
// verify_return and refund aliases. Refund needs disputed -> return_verified,
|
|
7
|
+
// with payee evidence, then returns the full price to payer; accept pays payee.
|
|
8
|
+
// A reservation holds its own deposit. Activate requires an active listing and
|
|
9
|
+
// no placed order. Convert must wire the declared converters to invoke credit;
|
|
10
|
+
// naming a converter alone does not add that invocation. Credit consumes the
|
|
11
|
+
// deposit once, credits at most the contribution, and returns excess to buyer.
|
|
12
|
+
// Expose cancel, seller_cancel or operator_cancel for the intended actors.
|
|
13
|
+
// Buyer cancel and expiry use configured refund percentages; seller/operator
|
|
14
|
+
// cancellation return the whole reservation balance. Converted money is in escrow,
|
|
15
|
+
// so subsequent return verification and refund use the escrow aliases.
|
|
16
|
+
|
|
3
17
|
instrument listing(seller: party) {
|
|
4
18
|
revisioned: true
|
|
5
19
|
summary: "A seller's offer and its asking price."
|
package/std/money.hsx
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
header money
|
|
2
2
|
|
|
3
|
+
// Expose each attachment's create and caller actions explicitly. A transfer or
|
|
4
|
+
// split pays once; a hold exposes fund, release and refund separately. Release
|
|
5
|
+
// pays payee and refund returns payer. A hold has no dispute or damage decision.
|
|
6
|
+
// A schedule needs count unique occurrence records before activation; each due
|
|
7
|
+
// occurrence pays only while the schedule is active. A due time promises no retry.
|
|
8
|
+
// A pool accepts whole contributions only up to its remaining target and at most
|
|
9
|
+
// 366 paid records. Expose pay to release the exact target. Clock failure refunds
|
|
10
|
+
// original contributors; excess acceptance and partial contribution are absent.
|
|
11
|
+
// A swap reserves both legs before exchange or voids reservations on expiry.
|
|
12
|
+
// Payout instruct reserves across the selected adapter; confirm/reject require
|
|
13
|
+
// matching terminal instruction evidence. An acknowledgement cannot settle it.
|
|
14
|
+
// Metered bill charges a unique usage identity at the fixed unit price.
|
|
15
|
+
|
|
3
16
|
instrument transfer(payer: party, payee: party, amount: money = runtime) {
|
|
4
17
|
summary: "One transfer between the bound parties."
|
|
5
18
|
fields { amount: money = amount }
|
package/std/reporting.hsx
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
header reporting
|
|
2
2
|
|
|
3
|
+
// Attach portfolio to one or more financing plans. Its only lifecycle action is
|
|
4
|
+
// create; reports use the declared report request/read roles, not a money action.
|
|
5
|
+
// Aging selects active facilities and includes earned profit when deciding arrears.
|
|
6
|
+
// The default-rate report measures period-end stock, not new default incidence.
|
|
7
|
+
// Paid facilities are excluded and write-offs counted separately. Amounts are SAR
|
|
8
|
+
// minor units; declared scale and rounding govern ratios. Missing identity joins
|
|
9
|
+
// or unreconciled schedule counts refuse. Origination cohorts and bureau output
|
|
10
|
+
// explicitly refuse until their unavailable governed facts exist. No bureau
|
|
11
|
+
// submission, provider layout, external transport or restructuring proof is supplied.
|
|
12
|
+
|
|
3
13
|
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) {
|
|
4
14
|
summary: "Declared financing reports evaluated over frozen Product facts."
|
|
5
15
|
fields {}
|
package/std/savings.hsx
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
header savings
|
|
2
2
|
|
|
3
|
+
// Expose circle.create and membership.create; create one pending contribution
|
|
4
|
+
// record per member per circle date before membership.activate. Circle activation
|
|
5
|
+
// is clock-owned and needs exactly members joined memberships at or after startsAt.
|
|
6
|
+
// Membership.activate needs the active circle and its full pending schedule.
|
|
7
|
+
// Membership.received means the member received the pot. Contribution.paid means
|
|
8
|
+
// one contribution was collected. Neither record's state proves the other's state.
|
|
9
|
+
// A member can have received the pot with future unpaid contributions, or paid
|
|
10
|
+
// a contribution while their membership still waits for its payout seat.
|
|
11
|
+
// Receive counts preceding received memberships, never paid contributions.
|
|
12
|
+
// Eight members at 300 SAR require 2400 SAR per payout; seven payments supply 2100,
|
|
13
|
+
// so a full payout lacks 300 unless other cash exists. There is no reduced payout.
|
|
14
|
+
// Contribution.pay has a due time and no deadline or prior-payment requirement.
|
|
15
|
+
// Late and out-of-order collection is admitted while membership is active or
|
|
16
|
+
// received and circle is active. Clock admission does not promise scheduler retry.
|
|
17
|
+
// Circle.close needs every membership received and an empty held account.
|
|
18
|
+
// Circle creation collects circleDates, one date per member for contributions and payouts.
|
|
19
|
+
|
|
3
20
|
instrument circle(contribution: money, members: integer(1, 60), starts: date, memberships: ref<savings.membership> = object(savings.membership)) {
|
|
4
21
|
summary: "A fixed group contributes on its explicit calendar and takes the pot in order."
|
|
5
|
-
fields { contribution: money = contribution, members: integer = members, startsAt: date = starts,
|
|
22
|
+
fields { contribution: money = contribution, members: integer = members, startsAt: date = starts, circleDates: list(date, members), pot: money = multiply(self.contribution, self.members), held: account of self }
|
|
6
23
|
lifecycle { states: [forming, active, closed, disbanded], initial: forming }
|
|
7
24
|
action create { }
|
|
8
25
|
action activate {
|
|
@@ -11,35 +28,35 @@ instrument circle(contribution: money, members: integer(1, 60), starts: date, me
|
|
|
11
28
|
}
|
|
12
29
|
action close {
|
|
13
30
|
from: active, to: closed
|
|
14
|
-
requires count of { instrument: memberships, reference: "circle", anchor: self.id, states: [
|
|
31
|
+
requires count of { instrument: memberships, reference: "circle", anchor: self.id, states: [received], limit: 60 } == self.members
|
|
15
32
|
requires self.held.balance == "0"
|
|
16
33
|
}
|
|
17
34
|
action disband { from: forming, to: disbanded }
|
|
18
35
|
}
|
|
19
36
|
|
|
20
37
|
instrument membership(circle: ref<savings.circle>, member: party) {
|
|
21
|
-
summary: "A member contributes on the circle calendar and receives the pot in the assigned
|
|
22
|
-
fields { circle: ref<circle>, member: account of member,
|
|
23
|
-
lifecycle { states: [joined, active,
|
|
24
|
-
action create { requires self.
|
|
38
|
+
summary: "A member contributes on the circle calendar and receives the pot in the assigned seat."
|
|
39
|
+
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 }
|
|
40
|
+
lifecycle { states: [joined, active, received, withdrawn], initial: joined }
|
|
41
|
+
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] }
|
|
25
42
|
action activate {
|
|
26
43
|
from: joined, to: active
|
|
27
44
|
requires self.circle in [active]
|
|
28
45
|
requires count of { instrument: contribution, reference: "membership", anchor: self.id, states: [pending], limit: 60 } == self.circle.members
|
|
29
46
|
}
|
|
30
47
|
action receive {
|
|
31
|
-
from: active, to:
|
|
48
|
+
from: active, to: received, actor: clock, due: { at: self.receivesAt }
|
|
32
49
|
requires self.circle in [active]
|
|
33
|
-
requires count of { instrument: all(savings.membership), reference: "circle", anchor: self.circle, states: [
|
|
50
|
+
requires count of { instrument: all(savings.membership), reference: "circle", anchor: self.circle, states: [received], limit: 60 } == self.preceding
|
|
34
51
|
moves self.total from self.circle.held to self.member
|
|
35
52
|
}
|
|
36
53
|
action withdraw { from: joined, to: withdrawn, requires self.circle in [disbanded] }
|
|
37
54
|
records {
|
|
38
55
|
contribution: {
|
|
39
|
-
fields { membership: ref<parent>,
|
|
56
|
+
fields { membership: ref<parent>, period: integer(1, 60), dueAt: date = at(self.membership.circle.circleDates, self.period), amount: money = self.membership.contribution }
|
|
40
57
|
lifecycle { states: [pending, paid], initial: pending }
|
|
41
|
-
action create { requires self.
|
|
42
|
-
action pay { from: pending, to: paid, actor: clock, due: { at: self.dueAt }, requires self.membership in [active,
|
|
58
|
+
action create { requires self.period <= self.membership.circle.members; requires self.membership in [joined]; requires unique "seat_period" on [self.membership, self.period] }
|
|
59
|
+
action pay { from: pending, to: paid, actor: clock, due: { at: self.dueAt }, requires self.membership in [active, received]; requires self.membership.circle in [active], moves self.amount from self.membership.member to self.membership.circle.held }
|
|
43
60
|
}
|
|
44
61
|
}
|
|
45
62
|
}
|
package/std/travel.hsx
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
header travel
|
|
2
2
|
|
|
3
|
+
// Publish a package before booking.create. Expose deposit, balance and confirm
|
|
4
|
+
// separately. Deposit needs pending premium slices whose cover is active, and
|
|
5
|
+
// collects those premiums in addition to booking cash. Balance is due by balanceAt.
|
|
6
|
+
// Expose cancel_early, cancel_middle and cancel_late for the desired booking paths.
|
|
7
|
+
// Early returns all held booking cash, whether deposit-only or fully paid. Middle
|
|
8
|
+
// returns held minus the original deposit; a deposit-only booking gets zero.
|
|
9
|
+
// Supplier penalty is based on supplierCost; tax applies to retained margin,
|
|
10
|
+
// and the bound operator receives the remainder. Late returns no buyer cash.
|
|
11
|
+
// Middle opens at earlyAt and ends at lateAt, which is middleAt plus 1ms; late
|
|
12
|
+
// starts there. Default_balance can already cancel a deposit_paid booking at
|
|
13
|
+
// balanceAt, after which no booking cancellation is reachable. Clock order matters.
|
|
14
|
+
// Booking refunds exclude insurance premiums, broker commission and all balances
|
|
15
|
+
// outside booking.held. Expose cover.cancel independently to refund eligible paid
|
|
16
|
+
// premium periods before startsAt; slice.refund is parent-owned. Active/expired
|
|
17
|
+
// premium periods remain excluded. Confirmation timeout returns the full booking
|
|
18
|
+
// price from paid only. A penalty above held cash or retained deposit refuses;
|
|
19
|
+
// this header does not subsidize a shortfall or reduce the configured penalty.
|
|
20
|
+
|
|
3
21
|
instrument package(price: money, supplier_cost: money, departure: date) {
|
|
4
22
|
constraints { supplier_cost: at_most(price) }
|
|
5
23
|
summary: "Immutable package price, supplier cost and departure."
|
|
@@ -48,7 +66,7 @@ instrument booking(package: ref<travel.package>, buyer: party, supplier: party,
|
|
|
48
66
|
due: { at: self.package.departure }, deadline: { at: self.confirmBy }
|
|
49
67
|
moves self.supplierCost from self.held to supplier
|
|
50
68
|
moves self.tax from self.held to programTax
|
|
51
|
-
moves self.operatorNet from self.held to
|
|
69
|
+
moves self.operatorNet from self.held to operator
|
|
52
70
|
}
|
|
53
71
|
action timeout {
|
|
54
72
|
from: paid, to: refunded, actor: clock, due: { at: self.confirmBy }
|
|
@@ -69,7 +87,7 @@ instrument booking(package: ref<travel.package>, buyer: party, supplier: party,
|
|
|
69
87
|
moves self.refund from self.held to buyer
|
|
70
88
|
moves self.middlePenalty from self.held to supplier
|
|
71
89
|
moves self.retainedTax from self.held to programTax
|
|
72
|
-
moves self.retainedNet from self.held to
|
|
90
|
+
moves self.retainedNet from self.held to operator
|
|
73
91
|
}
|
|
74
92
|
action cancel_late { allowZero: true, requires self.price > "0";
|
|
75
93
|
from: [deposit_paid, paid], to: cancelled, actor: { party: buyer }, due: { at: self.lateAt }
|
|
@@ -80,7 +98,7 @@ instrument booking(package: ref<travel.package>, buyer: party, supplier: party,
|
|
|
80
98
|
]
|
|
81
99
|
moves self.latePenalty from self.held to supplier
|
|
82
100
|
moves self.retainedTax from self.held to programTax
|
|
83
|
-
moves self.retainedNet from self.held to
|
|
101
|
+
moves self.retainedNet from self.held to operator
|
|
84
102
|
}
|
|
85
103
|
action default_balance { allowZero: true, requires self.price > "0";
|
|
86
104
|
from: deposit_paid, to: cancelled, actor: clock, due: { at: self.balanceAt }
|
|
@@ -91,6 +109,6 @@ instrument booking(package: ref<travel.package>, buyer: party, supplier: party,
|
|
|
91
109
|
]
|
|
92
110
|
moves self.latePenalty from self.held to supplier
|
|
93
111
|
moves self.retainedTax from self.held to programTax
|
|
94
|
-
moves self.retainedNet from self.held to
|
|
112
|
+
moves self.retainedNet from self.held to operator
|
|
95
113
|
}
|
|
96
114
|
}
|
package/std/wallet.hsx
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
header wallet
|
|
2
2
|
|
|
3
|
+
// Expose balance.create, activate, topup and withdraw as needed. Holder moves
|
|
4
|
+
// cash only while active. Freeze blocks new topups, withdrawals and spend reserves;
|
|
5
|
+
// close requires no held cash. Freeze/unfreeze use caller operation authority.
|
|
6
|
+
// A separate spend record checks its amount against spendLimit at creation.
|
|
7
|
+
// Expose reserve, pay and cancel for the bound payee. Reserve requires an active
|
|
8
|
+
// wallet before expiresAt; pay posts the whole reservation, cancel/expiry void it.
|
|
9
|
+
// Existing reservations may still post after the wallet freezes. There is no
|
|
10
|
+
// daily aggregate limit, partial spend or card-network settlement in this header.
|
|
11
|
+
|
|
3
12
|
instrument balance(holder: party, spend_limit: money = 100000 SAR) {
|
|
4
13
|
summary: "A holder's reusable balance with an immutable per-spend ceiling."
|
|
5
14
|
fields { holder: account of holder, held: account of self, spendLimit: money = spend_limit }
|