@hyperscale0/hsx 5.2.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.
Files changed (74) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +2 -2
  3. package/bin/hsx.ts +0 -2
  4. package/dist/bin/hsx.js +0 -2
  5. package/dist/bin/hsx.js.map +1 -1
  6. package/dist/src/ast.d.ts +0 -1
  7. package/dist/src/ast.d.ts.map +1 -1
  8. package/dist/src/ast.js +0 -11
  9. package/dist/src/ast.js.map +1 -1
  10. package/dist/src/cli.d.ts +0 -2
  11. package/dist/src/cli.d.ts.map +1 -1
  12. package/dist/src/cli.js +10 -5
  13. package/dist/src/cli.js.map +1 -1
  14. package/dist/src/compile.d.ts.map +1 -1
  15. package/dist/src/compile.js +204 -107
  16. package/dist/src/compile.js.map +1 -1
  17. package/dist/src/diagnostics.d.ts +14 -0
  18. package/dist/src/diagnostics.d.ts.map +1 -0
  19. package/dist/src/diagnostics.js +20 -0
  20. package/dist/src/diagnostics.js.map +1 -0
  21. package/dist/src/header-source.d.ts +3 -0
  22. package/dist/src/header-source.d.ts.map +1 -0
  23. package/dist/src/header-source.js +41 -0
  24. package/dist/src/header-source.js.map +1 -0
  25. package/dist/src/headers.d.ts.map +1 -1
  26. package/dist/src/headers.js +4 -6
  27. package/dist/src/headers.js.map +1 -1
  28. package/dist/src/keywords.d.ts +8 -2
  29. package/dist/src/keywords.d.ts.map +1 -1
  30. package/dist/src/keywords.js +13 -5
  31. package/dist/src/keywords.js.map +1 -1
  32. package/dist/src/lex.d.ts +1 -1
  33. package/dist/src/lex.d.ts.map +1 -1
  34. package/dist/src/lex.js +11 -3
  35. package/dist/src/lex.js.map +1 -1
  36. package/dist/src/parse.d.ts.map +1 -1
  37. package/dist/src/parse.js +22 -5
  38. package/dist/src/parse.js.map +1 -1
  39. package/dist/src/std-bundle.js +12 -12
  40. package/dist/src/std-bundle.js.map +1 -1
  41. package/dist/src/tunables.d.ts.map +1 -1
  42. package/dist/src/tunables.js +2 -1
  43. package/dist/src/tunables.js.map +1 -1
  44. package/dist/src/version.d.ts +1 -2
  45. package/dist/src/version.d.ts.map +1 -1
  46. package/dist/src/version.js +1 -2
  47. package/dist/src/version.js.map +1 -1
  48. package/docs/README.md +54 -7
  49. package/docs/headers.md +1 -1
  50. package/package.json +3 -3
  51. package/src/ast.ts +0 -10
  52. package/src/cli.ts +13 -7
  53. package/src/compile.ts +296 -133
  54. package/src/diagnostics.ts +28 -0
  55. package/src/header-source.ts +54 -0
  56. package/src/headers.ts +4 -6
  57. package/src/keywords.ts +13 -5
  58. package/src/lex.ts +13 -5
  59. package/src/parse.ts +29 -5
  60. package/src/std-bundle.ts +12 -12
  61. package/src/tunables.ts +6 -1
  62. package/src/version.ts +1 -2
  63. package/std/cards.hsx +12 -0
  64. package/std/collections.hsx +11 -0
  65. package/std/escrow.hsx +16 -2
  66. package/std/financing.hsx +36 -5
  67. package/std/insurance.hsx +20 -5
  68. package/std/lending.hsx +14 -0
  69. package/std/marketplace.hsx +14 -0
  70. package/std/money.hsx +13 -0
  71. package/std/reporting.hsx +10 -0
  72. package/std/savings.hsx +28 -11
  73. package/std/travel.hsx +22 -4
  74. package/std/wallet.hsx +9 -0
package/src/tunables.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { fail } from "./diagnostics.ts";
1
2
  import type { Expr } from "./ast.ts";
2
3
 
3
4
  /** Bounds use UDL units: minor units, basis points and milliseconds. */
@@ -24,7 +25,11 @@ export function tunableBounds(
24
25
  numbers.some((value) => !Number.isSafeInteger(value)) ||
25
26
  numbers[0]! > numbers[1]!
26
27
  )
27
- throw new Error("integer tunable needs a minimum and maximum");
28
+ fail(
29
+ type,
30
+ "integer tunable needs ordered safe integer bounds",
31
+ "write integer(1, 12)",
32
+ );
28
33
  return { minimum: numbers[0]!, maximum: numbers[1]! };
29
34
  }
30
35
  return { minimum: 0, maximum: Number.MAX_SAFE_INTEGER };
package/src/version.ts CHANGED
@@ -1,2 +1 @@
1
- export const HSX_VERSION = "5.2.0";
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 }
@@ -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 subject.price from payer to self.held
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 { from: pending, to: cancelled }
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,12 +482,12 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
451
482
  }
452
483
  revisioned: true
453
484
  familyRevision: 1
454
- summary: "Separate approved fine and evidenced recovery claims on an overdue installment."
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")
458
489
  }
459
- lifecycle { states: [proposed, assessed, collected, waived, refunded], initial: proposed }
490
+ lifecycle { states: [proposed, assessed, collected, waived], initial: proposed }
460
491
  action create { calculate: [{ target: overdueAt, op: shift, date: { field: self.slice.dueAt }, milliseconds: { literal: grace }, direction: after }], requires unique "late_assessment" on [self.slice, self.overdueAt]; requires self.borrower == self.slice.plan.borrower; requires self.plan == self.slice.plan }
461
492
  action assess { allowZero: true,
462
493
  from: proposed, to: assessed, actor: clock, due: { at: self.overdueAt }
@@ -523,7 +554,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
523
554
  plan: ref<on>, assessment: ref<parent>, overdueAt: date?, evidence: text, evidencedCost: money, amount: money = minimum(self.evidencedCost, cap)
524
555
  receivable: account(self, claim), debt: account(borrower, claim, contra, "debt")
525
556
  }
526
- lifecycle { states: [proposed, assessed, collected, waived, refunded], initial: proposed }
557
+ lifecycle { states: [proposed, assessed, collected, waived], initial: proposed }
527
558
  action create { calculate: [{ target: overdueAt, op: shift, date: { field: self.assessment.overdueAt }, milliseconds: { literal: 0 }, direction: after }], requires unique "recovery_assessment" on [self.assessment]; requires self.plan == self.assessment.plan }
528
559
  action assess { allowZero: true,
529
560
  from: proposed, to: assessed, actor: clock, due: { at: self.assessment.overdueAt }
package/std/insurance.hsx CHANGED
@@ -1,6 +1,21 @@
1
1
  header insurance
2
2
 
3
- instrument cover(holder: party, adapter: text, commission: percent = 0%, covers: ref, premium: money = runtime) {
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.premium from holder to programOperator
37
- moves self.insurerPremium from programOperator to self.cover.insurer
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 programOperator
46
- moves self.premium from programOperator to holder
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 {
@@ -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, dates: list(date, members), pot: money = multiply(self.contribution, self.members), held: account of self }
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: [paid], limit: 60 } == self.members
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 position."
22
- fields { circle: ref<circle>, member: account of member, position: integer(1, 60), preceding: integer = subtract(self.position, 1), receivesAt: date = at(self.circle.dates, self.position), contribution: money = self.circle.contribution, total: money = self.circle.pot }
23
- lifecycle { states: [joined, active, paid, withdrawn], initial: joined }
24
- action create { requires self.position <= self.circle.members; requires self.circle in [forming]; requires unique "circle_seat" on [self.circle, self.position]; requires unique "circle_member" on [self.circle, self.member] }
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: paid, actor: clock, due: { at: self.receivesAt }
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: [paid], limit: 60 } == self.preceding
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>, position: integer(1, 60), dueAt: date = at(self.membership.circle.dates, self.position), amount: money = self.membership.contribution }
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.position <= self.membership.circle.members; requires self.membership in [joined]; requires unique "seat_period" on [self.membership, self.position] }
42
- action pay { from: pending, to: paid, actor: clock, due: { at: self.dueAt }, requires self.membership in [active, paid]; requires self.membership.circle in [active], moves self.amount from self.membership.member to self.membership.circle.held }
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 programOperator
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 programOperator
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 programOperator
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 programOperator
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 }