@hyperscale0/hsx 1.0.125 → 1.0.128
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/README.md +2 -2
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +62 -14
- package/dist/src/compile.js.map +1 -1
- package/dist/src/examples-bundle.js +8 -8
- package/dist/src/examples-bundle.js.map +1 -1
- package/dist/src/headers.d.ts +1 -1
- package/dist/src/headers.d.ts.map +1 -1
- package/dist/src/headers.js +1 -0
- package/dist/src/headers.js.map +1 -1
- package/dist/src/std-bundle.d.ts.map +1 -1
- package/dist/src/std-bundle.js +7 -6
- package/dist/src/std-bundle.js.map +1 -1
- package/docs/README.md +74 -47
- package/examples/collections.hsx +1 -1
- package/examples/financing.hsx +1 -1
- package/examples/insurance.hsx +4 -4
- package/examples/lending.hsx +1 -1
- package/examples/library.hsx +1 -1
- package/examples/rental-deposit.hsx +1 -1
- package/examples/reporting.hsx +1 -1
- package/examples/serviced.hsx +1 -1
- package/package.json +4 -4
- package/src/compile.ts +80 -20
- package/src/examples-bundle.ts +8 -8
- package/src/headers.ts +1 -0
- package/src/std-bundle.ts +7 -6
- package/std/booking.hsx +75 -0
- package/std/escrow.hsx +24 -7
- package/std/financing.hsx +83 -26
- package/std/insurance.hsx +9 -5
- package/std/marketplace.hsx +3 -3
- package/std/money.hsx +3 -3
- package/std/wallet.hsx +17 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"std-bundle.js","sourceRoot":"","sources":["../../src/std-bundle.ts"],"names":[],"mappings":"AAAA,oEAAoE;AAEpE,MAAM,CAAC,MAAM,iBAAiB,GAAgC,IAAI,GAAG,CAAiB;IACpF,CAAC,WAAW,EAAE,2oKAA2oK,CAAC;IAC1pK,CAAC,iBAAiB,EAAE,2sNAA2sN,CAAC;IAChuN,CAAC,YAAY,EAAE
|
|
1
|
+
{"version":3,"file":"std-bundle.js","sourceRoot":"","sources":["../../src/std-bundle.ts"],"names":[],"mappings":"AAAA,oEAAoE;AAEpE,MAAM,CAAC,MAAM,iBAAiB,GAAgC,IAAI,GAAG,CAAiB;IACpF,CAAC,aAAa,EAAE,soLAAsoL,CAAC;IACvpL,CAAC,WAAW,EAAE,2oKAA2oK,CAAC;IAC1pK,CAAC,iBAAiB,EAAE,2sNAA2sN,CAAC;IAChuN,CAAC,YAAY,EAAE,i9JAAi9J,CAAC;IACj+J,CAAC,eAAe,EAAE,uzpEAAuzpE,CAAC;IAC10pE,CAAC,eAAe,EAAE,uxLAAuxL,CAAC;IAC1yL,CAAC,aAAa,EAAE,qwTAAqwT,CAAC;IACtxT,CAAC,iBAAiB,EAAE,mpTAAmpT,CAAC;IACxqT,CAAC,WAAW,EAAE,+1VAA+1V,CAAC;IAC92V,CAAC,eAAe,EAAE,+q2BAA+q2B,CAAC;IACls2B,CAAC,aAAa,EAAE,85JAA85J,CAAC;IAC/6J,CAAC,YAAY,EAAE,kyHAAkyH,CAAC;CACnzH,CAAC,CAAC"}
|
package/docs/README.md
CHANGED
|
@@ -82,11 +82,16 @@ the executor's current time when the action runs, not a caller-supplied return d
|
|
|
82
82
|
A late `due` permits the operator action; it does not schedule that action.
|
|
83
83
|
|
|
84
84
|
This sample binds a declared business named `renter` in Product configuration.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
The renter is one business for the whole Build, so the instrument takes no
|
|
86
|
+
parameters. Changing the renter binding is a Build configuration decision, not a
|
|
87
|
+
different party ID on each request. For per-object participants, give a program
|
|
88
|
+
instrument parameters as a header would, such as `instrument purse(holder: party)`,
|
|
89
|
+
and bind `holder: owner` or `holder: actor` in each `attach` block. A
|
|
90
|
+
parameterized program instrument exists only through its attachments. A `fee` on
|
|
91
|
+
a move of an action input, such as `moves input.amount from holder to self.held
|
|
92
|
+
fee { seller: 1% }`, is calculated when that action runs. Reusable custom headers
|
|
93
|
+
require a host-supplied `StandardLibrary`; `use` cannot load a local file through
|
|
94
|
+
this CLI.
|
|
90
95
|
|
|
91
96
|
Inspect `objects` in the compiled UDL for the attachment and public names, then
|
|
92
97
|
its instrument for fields, transitions, actors, requirements and ordered moves.
|
|
@@ -104,11 +109,15 @@ executor. Compilation cannot establish balances, permissions or provider readine
|
|
|
104
109
|
|
|
105
110
|
Every attached party parameter binds to `owner`, `actor`, `operator`, a declared
|
|
106
111
|
business, or a declared staff party with a supported role. Configure declared
|
|
107
|
-
business participants per Build.
|
|
112
|
+
business participants per Build. The host binds a declared business to an
|
|
113
|
+
organization customer that holds one open balance account in the Product, so the
|
|
114
|
+
Product exists before the binding and a recomposition adds it. Declared person
|
|
115
|
+
parties cannot bind attachments.
|
|
108
116
|
`owner` is the object's resolved customer or business, `actor` is the authenticated
|
|
109
117
|
caller, and `operator` is the program operator. A parameter with one of these
|
|
110
|
-
names binds by name. Other party parameters use a same-named eligible
|
|
111
|
-
a header default, or an explicit binding.
|
|
118
|
+
names binds by name. Other required party parameters use a same-named eligible
|
|
119
|
+
declaration, a header default, or an explicit binding. An optional party parameter
|
|
120
|
+
stays unset unless the attachment binds it. Explicit bindings take precedence.
|
|
112
121
|
Unbound parameters report `subject_party_unbound`. These bindings are frozen in
|
|
113
122
|
UDL `objects[].attachments[].parties`; no caller supplies party IDs at creation.
|
|
114
123
|
|
|
@@ -156,6 +165,8 @@ decimal places and percentages at most two. The compiler uses integer minor unit
|
|
|
156
165
|
and basis points. Fee and tax calculations round down. Split percentages sum to
|
|
157
166
|
100%; the last declared recipient receives the rounding remainder. Finite
|
|
158
167
|
schedules use positions 1 through n and put the division remainder in position 1.
|
|
168
|
+
Amortizing financing is the exception: it rounds half up and puts the remainder in
|
|
169
|
+
the last instalment, as a standard amortization table does.
|
|
159
170
|
Tax binds `programTax`. Fees default to `programOperator`. Fine, recovery and
|
|
160
171
|
residual destinations are party tunables.
|
|
161
172
|
|
|
@@ -261,20 +272,30 @@ and a correction. No artifact is returned with diagnostics.
|
|
|
261
272
|
|
|
262
273
|
## Financing policies
|
|
263
274
|
|
|
264
|
-
`financing.installments` keeps principal and fixed
|
|
265
|
-
activation. The company chooses where funding goes and when
|
|
266
|
-
All choices use accounts, calculations and moves in the same
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
275
|
+
`financing.installments` keeps principal and fixed profit as claims from
|
|
276
|
+
activation. The company chooses how profit is priced, where funding goes and when
|
|
277
|
+
profit becomes earned. All choices use accounts, calculations and moves in the same
|
|
278
|
+
two books.
|
|
279
|
+
|
|
280
|
+
`pricing` has no default, so every plan states it. `amortizing` reads
|
|
281
|
+
`profit_rate` as an annual rate on the declining balance: equal monthly
|
|
282
|
+
instalments from standard annuity maths, interest and instalment rounded half up,
|
|
283
|
+
the last instalment absorbing the rounding. 12,000 SAR at 18% over 12 months is
|
|
284
|
+
11 instalments of 1,100.16 SAR and a last one of 1,100.19 SAR, 13,201.95 SAR in
|
|
285
|
+
total. `flat_total` reads `profit_rate` as one charge on principal for the whole
|
|
286
|
+
term, rounded down and split evenly with the remainder in position 1: murabaha and
|
|
287
|
+
fixed-fee pay-later. The same 12,000 SAR at 18% `flat_total` costs 14,160 SAR.
|
|
288
|
+
|
|
289
|
+
| Tunable | Default | Selected behavior |
|
|
290
|
+
| ---------------------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
291
|
+
| `disburse_to` | `funds` | `funds` pays down payment and principal into the hold and confirms it. `borrower` credits principal to the borrower's balance. `seller` pays principal to the `seller` party at activation; after capital refunds every paid payment, capital's `unwind` debits it back from the seller and cancels open slices. |
|
|
292
|
+
| `profit_earned` | `on_payment` | `on_payment` collects unearned profit on payment. `by_schedule` moves unearned claims to earned claims at each slice's date. `at_disbursement` makes that move at activation. Cash arrives only on payment. |
|
|
293
|
+
| `apply` | `fines_profit_principal` | Visit each slice in position order; within that slice collect assessed fines by overdue date, evidenced costs, profit, then principal. `principal_profit` collects each slice in position order, principal before profit. `pro_rata` uses the slice's fixed principal:profit ratio (its own principal and profit under `amortizing`), caps each side by its collectible balance, and assigns the remainder without stranding cash. |
|
|
294
|
+
| `payoff_rebate` | `100%` | Reverse this share of unearned profit to borrower debt. Collect the remainder and all earned profit with outstanding principal. |
|
|
295
|
+
| `late_charge.fines_to` | `programOperator` | Receive fine cash and fund its refunds. |
|
|
296
|
+
| `late_charge.costs_to` | `programOperator` | Receive evidenced recovery cash and fund its refunds. |
|
|
297
|
+
| `collections.case.fee` | `20%` | After payment succeeds, transfer this share of the payment amount from plan capital to the agency. |
|
|
298
|
+
| `lending.distribution.residual_to` | `programOperator` | Receive the cash remainder and own the claim loss remainder after weighted distribution. |
|
|
278
299
|
|
|
279
300
|
Each slice owns separate unearned and earned claim accounts. Scheduled recognition
|
|
280
301
|
uses a dated child record so a partial payment cannot disable the maturity clock.
|
|
@@ -362,9 +383,9 @@ The compiler API is `compile(source)`. On success, `result.verdict` is `valid`
|
|
|
362
383
|
and `result.artifacts` contains `document`, `costManifest` and `originMap`.
|
|
363
384
|
On refusal, inspect `result.diagnostics`; no artifact is returned.
|
|
364
385
|
|
|
365
|
-
`hsx headers` prints the instrument inventory from the declarations. The
|
|
386
|
+
`hsx headers --json` prints the instrument inventory from the declarations. The
|
|
366
387
|
[object example](../examples/library.hsx) shows authenticated role bindings.
|
|
367
|
-
The [playground](
|
|
388
|
+
The [playground](https://hyperscale0.ai/playground/) compiles locally in the browser.
|
|
368
389
|
|
|
369
390
|
## Standard-library behavior
|
|
370
391
|
|
|
@@ -372,18 +393,21 @@ Read the instrument's states, time gates and moves before promising a money
|
|
|
372
393
|
outcome. A compiler pass does not prove that required actions are exposed,
|
|
373
394
|
adapters are bound, participants have funds, or the flow can finish.
|
|
374
395
|
|
|
375
|
-
| Instrument | Behavior
|
|
376
|
-
| ------------------------------------------ |
|
|
377
|
-
| `financing.installments` | `share` allocates collected profit to a cash payable owned by `capital`. It does not itself pay operator income.
|
|
378
|
-
| `financing.limits` | `per_borrower` caps outstanding principal. Both borrower and portfolio limits must be approved before disbursement; attaching them does not approve them.
|
|
379
|
-
| `lending.round` | The target is the linked plan's principal. Closing requires commitments and held funds to equal that amount.
|
|
380
|
-
| `lending.distribution` | Cash distribution needs an eligible recorded settlement, `prepare_cash`, one share record per funded commitment, then `distribute_cash`. Attaching it moves nothing.
|
|
381
|
-
| `insurance.cover.slice` | `collect` credits premium net of commission to the insurer account. The tenant retains commission. `refund` returns both portions; claims reserve insurer funds. External settlement uses the boundary protocol.
|
|
382
|
-
| `financing.installments`, `savings.circle` | Supply explicit date lists when creating agreements. A term count does not generate a monthly calendar. Savings supports at most 60 distinct member seats.
|
|
383
|
-
| `escrow.hold` | `fund` takes the whole price. Financing into pending escrow collects the remaining down payment and adds capital principal at disbursement; `fund` is not a down-payment checkout.
|
|
384
|
-
| `escrow.hold` | Acceptance timeout enters `disputed` without paying the seller. Delivery and return verification belong to `payee`; rebinding it also changes who receives accepted funds.
|
|
385
|
-
| `
|
|
386
|
-
| `
|
|
396
|
+
| Instrument | Behavior |
|
|
397
|
+
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
398
|
+
| `financing.installments` | `share` allocates collected profit to a cash payable owned by `capital`. It does not itself pay operator income. |
|
|
399
|
+
| `financing.limits` | `per_borrower` caps outstanding principal. Both borrower and portfolio limits must be approved before disbursement; attaching them does not approve them. |
|
|
400
|
+
| `lending.round` | The target is the linked plan's principal. Closing requires commitments and held funds to equal that amount. |
|
|
401
|
+
| `lending.distribution` | Cash distribution needs an eligible recorded settlement, `prepare_cash`, one share record per funded commitment, then `distribute_cash`. Attaching it moves nothing. |
|
|
402
|
+
| `insurance.cover.slice` | `collect` credits premium net of commission to the insurer account. The tenant retains commission. `refund` returns both portions; claims reserve insurer funds. External settlement uses the boundary protocol. |
|
|
403
|
+
| `financing.installments`, `savings.circle` | Supply explicit date lists when creating agreements. A term count does not generate a monthly calendar. Savings supports at most 60 distinct member seats. |
|
|
404
|
+
| `escrow.hold` | `fund` takes the whole price. Financing into pending escrow collects the remaining down payment and adds capital principal at disbursement; `fund` is not a down-payment checkout. |
|
|
405
|
+
| `escrow.hold` | Acceptance timeout enters `disputed` without paying the seller. Delivery and return verification belong to `payee`; rebinding it also changes who receives accepted funds. |
|
|
406
|
+
| `escrow.hold` | `deliver_within` (default `14d`) starts at funding. After it passes without delivery, an exposed `refund_undelivered` lets the payer take the whole held price back. Nothing runs it automatically. |
|
|
407
|
+
| `wallet.balance` | `topup_fee` (default `0%`) takes a floored percentage of each top-up for the Company. At `1%`, a 100.00 top-up credits 99.00 and earns 1.00. |
|
|
408
|
+
| `booking.reservation` | The balance is payable only from `deposit_paid`, so a cancelled booking never takes it. `middle_forfeit` and `late_forfeit` default to `0%`, so every cancellation returns all held money until the author sets them. The clock completes a paid booking at departure. |
|
|
409
|
+
| `financing.late_charge` | `fine` is a fixed money amount, not a percentage of overdue principal. |
|
|
410
|
+
| `cards.card` | `spend_limit` is a per-authorization ceiling, not a monthly aggregate. |
|
|
387
411
|
|
|
388
412
|
A seller-owned marketplace object can declare `entryActions: [create_order]`
|
|
389
413
|
when `create_order` exposes an attachment's create action with its buyer bound
|
|
@@ -441,8 +465,9 @@ has no checkout collection action. The plan and escrow must agree on payer and
|
|
|
441
465
|
price.
|
|
442
466
|
|
|
443
467
|
Expose escrow `cancel` to return money credited before full funding. Plan `void`
|
|
444
|
-
cancels the signed financing agreement but does not cancel its escrow.
|
|
445
|
-
funding and delivery, the
|
|
468
|
+
cancels the signed financing agreement but does not cancel its escrow. Between
|
|
469
|
+
funding and delivery, the payee's `refund` returns the full price. After
|
|
470
|
+
delivery, the default return path needs three separate aliases for
|
|
446
471
|
`dispute`, `verify_return` and `refund`. The payee supplies return evidence and
|
|
447
472
|
returns the full price to the payer. `accept` pays the payee instead.
|
|
448
473
|
Marketplace order fulfillment alone does not execute any of these money actions.
|
|
@@ -458,7 +483,10 @@ Bind `insurance.cover.broker` to the agency that receives commission. Collection
|
|
|
458
483
|
pays the insurer's net premium and the broker's commission as two moves. Refunds
|
|
459
484
|
reverse those same portions. The default broker is the program operator.
|
|
460
485
|
`claim.deny` accepts only `submitted`; after approval, `pay` or expiry consumes
|
|
461
|
-
the reservation.
|
|
486
|
+
the reservation. `claim_limit` is the sum insured, supplied per cover at creation:
|
|
487
|
+
approved and paid claims on one cover never total more. `adapter` names a
|
|
488
|
+
registered boundary adapter, `conformance_boundary` in sandbox. `covers` is
|
|
489
|
+
optional; the attached object is already the insured thing.
|
|
462
490
|
|
|
463
491
|
A booking cancellation refunds only its own held cash. Expose `cover.cancel`
|
|
464
492
|
separately for eligible premium refunds before the paid period starts. Active
|
|
@@ -497,8 +525,9 @@ instruments stay generic. New Builds carry the purpose; existing agreements
|
|
|
497
525
|
and postings keep their retained Build and classification.
|
|
498
526
|
|
|
499
527
|
Held customer funding and its unspent return are `principal`. A release for the
|
|
500
|
-
company's sale is `earning
|
|
501
|
-
|
|
528
|
+
company's sale is `earning`; its release to another participant is
|
|
529
|
+
`pass_through`, because the company never owned it. A payment from company cash
|
|
530
|
+
to another participant is `participant_payout`. Claims, waivers, write-offs and own-account allocations
|
|
502
531
|
are `internal`; prepaid balance topups and returns are `prepaid_credit`. Tax is
|
|
503
532
|
`pass_through`, excluded from earnings and costs. Unpaid claims count as neither.
|
|
504
533
|
Refunds of paid profit, company charges and commission capture the original
|
|
@@ -511,16 +540,14 @@ product fees. Automatic platform fees keep their commercial classification.
|
|
|
511
540
|
|
|
512
541
|
A move's economics block can select by a resolved party: `economics { recipient:
|
|
513
542
|
recipient, company: { purpose: earning, sourceParty: payer }, participant:
|
|
514
|
-
{ purpose:
|
|
543
|
+
{ purpose: pass_through, sourceParty: programOperator } }`. The compiler
|
|
515
544
|
chooses `company` for `operator`, `programOperator`, or a declared party with the
|
|
516
545
|
`program_operator` role; it chooses `participant` for other bound parties.
|
|
517
546
|
Both branches must be supplied. A branch may be `unclassified` when no purpose
|
|
518
547
|
is established. Outside financing-profit and recovery recipients refund directly
|
|
519
|
-
to the customer
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
costs. Direct customer payments to
|
|
523
|
-
outside recipients are pass-through; payments from held company cash are costs.
|
|
548
|
+
to the customer; those refund branches remain unclassified. Customer cash paid to
|
|
549
|
+
outside recipients is pass-through, whether paid direct or released from a hold
|
|
550
|
+
the customer funded as principal. Only company-owned cash paid out is a cost.
|
|
524
551
|
|
|
525
552
|
Generic transfers and mixed principal/profit distributions remain unclassified
|
|
526
553
|
when their structure does not establish a purpose. Adapter-owned premium and
|
package/examples/collections.hsx
CHANGED
|
@@ -12,7 +12,7 @@ object facility "Business loan" {
|
|
|
12
12
|
attach loan = financing.installments {
|
|
13
13
|
expose create as create_loan
|
|
14
14
|
borrower: owner, capital: operator
|
|
15
|
-
months: 12,
|
|
15
|
+
months: 12, pricing: amortizing, profit_rate: 6%, disburse_to: borrower
|
|
16
16
|
limits: allowance, portfolio: budget
|
|
17
17
|
expose sign as sign_loan
|
|
18
18
|
expose disburse as release_loan
|
package/examples/financing.hsx
CHANGED
|
@@ -18,7 +18,7 @@ object enrolment "Enrolment" {
|
|
|
18
18
|
attach tuition = financing.installments {
|
|
19
19
|
expose create as create_tuition
|
|
20
20
|
borrower: owner, capital: operator
|
|
21
|
-
months: 3,
|
|
21
|
+
months: 3, pricing: flat_total, profit_rate: 0%, disburse_to: borrower
|
|
22
22
|
limits: allowance, portfolio: budget
|
|
23
23
|
expose sign as sign_tuition_plan
|
|
24
24
|
expose disburse as fund_tuition
|
package/examples/insurance.hsx
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
program device_insurance "Device insurance"
|
|
3
3
|
use insurance
|
|
4
4
|
use money
|
|
5
|
-
party inspector: staff role claims
|
|
6
5
|
|
|
7
6
|
object device "Insured device" {
|
|
8
7
|
fields { model: text, serialNumber: text, purchaseDate: date }
|
|
@@ -13,16 +12,17 @@ object device "Insured device" {
|
|
|
13
12
|
economics pay { purpose: earning, sourceParty: owner }
|
|
14
13
|
expose pay as pay_for_device
|
|
15
14
|
}
|
|
16
|
-
//
|
|
15
|
+
// conformance_boundary is the sandbox insurer adapter; swap in the insurer's registered adapter to go live.
|
|
16
|
+
// Premiums net of commission go to its account. claim_limit is the sum insured, set per cover at creation.
|
|
17
17
|
attach protection = insurance.cover {
|
|
18
18
|
expose create as create_protection
|
|
19
|
-
holder: owner, adapter: "
|
|
19
|
+
holder: owner, adapter: "conformance_boundary", commission: 10%, covers: purchase
|
|
20
20
|
expose activate as activate_cover
|
|
21
21
|
expose cancel as cancel_cover
|
|
22
22
|
}
|
|
23
23
|
attach claim = insurance.claim {
|
|
24
24
|
expose create as create_claim
|
|
25
|
-
cover: protection, inspector:
|
|
25
|
+
cover: protection, inspector: operator
|
|
26
26
|
expose approve as approve_claim
|
|
27
27
|
expose deny as deny_claim
|
|
28
28
|
expose pay as pay_claim
|
package/examples/lending.hsx
CHANGED
|
@@ -20,7 +20,7 @@ object business "Business funding" {
|
|
|
20
20
|
attach loan = financing.installments {
|
|
21
21
|
expose create as create_loan
|
|
22
22
|
borrower: owner, capital: operator
|
|
23
|
-
months: 12,
|
|
23
|
+
months: 12, pricing: amortizing, profit_rate: 6%, disburse_to: borrower
|
|
24
24
|
limits: allowance, portfolio: budget
|
|
25
25
|
expose sign as sign_loan
|
|
26
26
|
expose disburse as release_loan
|
package/examples/library.hsx
CHANGED
|
@@ -9,7 +9,7 @@ object car "Cars" {
|
|
|
9
9
|
attach ceiling = financing.portfolio_limit { expose create as create_ceiling, limit: 1500000 SAR }
|
|
10
10
|
attach plan = financing.installments {
|
|
11
11
|
borrower: actor, capital: operator, share: 25%
|
|
12
|
-
months: 3,
|
|
12
|
+
months: 3, pricing: flat_total, profit_rate: 2.5%, funds: sale, limits: limits, portfolio: ceiling
|
|
13
13
|
expose create as finance
|
|
14
14
|
}
|
|
15
15
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
program equipment_rental "Equipment rental"
|
|
4
4
|
party renter: business
|
|
5
5
|
|
|
6
|
-
//
|
|
6
|
+
// The renter is one declared business, so this instrument takes no parameters.
|
|
7
7
|
instrument deposit {
|
|
8
8
|
summary: "Hold a rental deposit, then return it less any fixed late fee."
|
|
9
9
|
fields {
|
package/examples/reporting.hsx
CHANGED
|
@@ -19,7 +19,7 @@ object purchase "Equipment loan" {
|
|
|
19
19
|
attach loan = financing.installments {
|
|
20
20
|
expose create as create_loan
|
|
21
21
|
borrower: owner, capital: operator
|
|
22
|
-
months: 12,
|
|
22
|
+
months: 12, pricing: amortizing, profit_rate: 6%, disburse_to: borrower
|
|
23
23
|
limits: allowance, portfolio: budget
|
|
24
24
|
expose sign as sign_purchase_loan
|
|
25
25
|
expose disburse as fund_purchase
|
package/examples/serviced.hsx
CHANGED
|
@@ -11,7 +11,7 @@ object car "Car" {
|
|
|
11
11
|
attach ceiling = financing.portfolio_limit { expose create as create_ceiling, limit: 10000000 SAR }
|
|
12
12
|
attach plan = financing.installments {
|
|
13
13
|
borrower: actor, capital: operator
|
|
14
|
-
months: 36,
|
|
14
|
+
months: 36, pricing: flat_total, profit_rate: 8.5%, down_payment: 10%
|
|
15
15
|
apply: fines_profit_principal, payoff_rebate: 100%, write_off_after: 90d
|
|
16
16
|
funds: sale, limits: limits, portfolio: ceiling
|
|
17
17
|
expose create as finance, expose sign as sign_contract, expose payoff as payoff
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperscale0/hsx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.128",
|
|
4
4
|
"description": "The strictly typed HSX language: compile total financial programs into canonical UDL.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"hsx",
|
|
@@ -84,8 +84,8 @@
|
|
|
84
84
|
"examples:bundle": "bun scripts/bundle-examples.ts"
|
|
85
85
|
},
|
|
86
86
|
"dependencies": {
|
|
87
|
-
"@hyperscale0/adl": "1.0.
|
|
88
|
-
"@hyperscale0/udl": "1.0.
|
|
87
|
+
"@hyperscale0/adl": "1.0.128",
|
|
88
|
+
"@hyperscale0/udl": "1.0.128",
|
|
89
89
|
"fast-sha256": "1.3.0"
|
|
90
90
|
},
|
|
91
91
|
"devDependencies": {
|
|
@@ -97,5 +97,5 @@
|
|
|
97
97
|
"Amir Ayub",
|
|
98
98
|
"Sara AlBakaawi"
|
|
99
99
|
],
|
|
100
|
-
"gitHead": "
|
|
100
|
+
"gitHead": "d22a6727cbbb9fc59ffc1625b2d162711dccbe98"
|
|
101
101
|
}
|
package/src/compile.ts
CHANGED
|
@@ -534,14 +534,6 @@ export function compile(
|
|
|
534
534
|
}
|
|
535
535
|
for (const decl of program.decls)
|
|
536
536
|
if (decl.kind === "instrument") templates.set(decl.name, decl);
|
|
537
|
-
for (const decl of program.decls) {
|
|
538
|
-
if (decl.kind === "instrument" && decl.parameters.length)
|
|
539
|
-
fail(
|
|
540
|
-
decl,
|
|
541
|
-
"This compiler cannot instantiate a parameterized instrument declared inside a program.",
|
|
542
|
-
"For this version, specialize the instrument with fixed bindings and remove its parameters. Custom reusable headers require a host-supplied library.",
|
|
543
|
-
);
|
|
544
|
-
}
|
|
545
537
|
const document: UdlDocument = {
|
|
546
538
|
udl: UDL_FORMAT_VERSION,
|
|
547
539
|
version: 1,
|
|
@@ -906,8 +898,12 @@ export function compile(
|
|
|
906
898
|
const typeName =
|
|
907
899
|
type.kind === "type" || type.kind === "call" ? type.name : text(type);
|
|
908
900
|
const partyParameter = attachmentInfo && typeName === "party";
|
|
901
|
+
const optional = type.kind === "type" && type.optional;
|
|
902
|
+
// An optional party stays unset unless the attachment binds it, so a
|
|
903
|
+
// program party that shares its name does not switch it on.
|
|
909
904
|
const byName: Expr | undefined =
|
|
910
905
|
partyParameter &&
|
|
906
|
+
!optional &&
|
|
911
907
|
(subjectPartyRoles.includes(param.key as SubjectPartyRole) ||
|
|
912
908
|
document.parties[param.key])
|
|
913
909
|
? { kind: "name", value: param.key, span: origin }
|
|
@@ -920,7 +916,7 @@ export function compile(
|
|
|
920
916
|
source: declarationSources.get(decl) ?? "program",
|
|
921
917
|
});
|
|
922
918
|
if (!actual) {
|
|
923
|
-
if (
|
|
919
|
+
if (optional) continue;
|
|
924
920
|
parameterErrors.push({
|
|
925
921
|
span: origin,
|
|
926
922
|
code: partyParameter ? "subject_party_unbound" : "HSX1001",
|
|
@@ -989,7 +985,12 @@ export function compile(
|
|
|
989
985
|
.filter((entry) => entry.key.startsWith("action "))
|
|
990
986
|
.flatMap(
|
|
991
987
|
(entry) =>
|
|
992
|
-
|
|
988
|
+
// Read subject rows directly: entries() refuses repeated when branches.
|
|
989
|
+
asBlock(
|
|
990
|
+
asBlock(entry.value).entries.find(
|
|
991
|
+
(row) => row.key === "subject",
|
|
992
|
+
)?.value,
|
|
993
|
+
).entries,
|
|
993
994
|
);
|
|
994
995
|
const moneyFields = [
|
|
995
996
|
...new Set(
|
|
@@ -2286,12 +2287,12 @@ export function compile(
|
|
|
2286
2287
|
};
|
|
2287
2288
|
}
|
|
2288
2289
|
}
|
|
2289
|
-
const
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
) {
|
|
2290
|
+
const from = slots.get("from");
|
|
2291
|
+
// A policy member may sit in a state list such as `[funded, dispute.refund_after]`.
|
|
2292
|
+
const fromBinding = (from?.kind === "list" ? from.items : [from]).find(
|
|
2293
|
+
(item) => item?.kind === "name" && item.value.includes("."),
|
|
2294
|
+
);
|
|
2295
|
+
if (fromBinding?.kind === "name" && slots.has("moves")) {
|
|
2295
2296
|
const [parameter, member] = fromBinding.value.split(".");
|
|
2296
2297
|
const suppliedPolicy = supplied.get(parameter!);
|
|
2297
2298
|
if (suppliedPolicy?.kind === "block") {
|
|
@@ -2332,6 +2333,7 @@ export function compile(
|
|
|
2332
2333
|
...(actionSubject ? { subject: actionSubject } : {}),
|
|
2333
2334
|
};
|
|
2334
2335
|
authored.set(a, row.value);
|
|
2336
|
+
const actionFeeCalculations: UdlCalculation[] = [];
|
|
2335
2337
|
for (const binding of boundaryBindings)
|
|
2336
2338
|
if (
|
|
2337
2339
|
!a.subject?.adapters.find((entry) => entry.binding === binding)
|
|
@@ -2692,6 +2694,11 @@ export function compile(
|
|
|
2692
2694
|
"write a percentage such as 1%",
|
|
2693
2695
|
);
|
|
2694
2696
|
const prefix = `${name}_${key}`;
|
|
2697
|
+
// Instrument calculations run at create, before any action
|
|
2698
|
+
// input exists, so a fee on an input amount runs with its action.
|
|
2699
|
+
const perAction =
|
|
2700
|
+
"field" in transfer.amount &&
|
|
2701
|
+
transfer.amount.field.startsWith("input.");
|
|
2695
2702
|
const derived = (
|
|
2696
2703
|
suffix: string,
|
|
2697
2704
|
label: string,
|
|
@@ -2704,8 +2711,13 @@ export function compile(
|
|
|
2704
2711
|
`generated fee field ${target} conflicts with a field`,
|
|
2705
2712
|
"rename the authored field or move key",
|
|
2706
2713
|
);
|
|
2707
|
-
fields.push({
|
|
2708
|
-
|
|
2714
|
+
fields.push({
|
|
2715
|
+
name: target,
|
|
2716
|
+
type: "money",
|
|
2717
|
+
label,
|
|
2718
|
+
...(perAction ? { optional: true } : {}),
|
|
2719
|
+
});
|
|
2720
|
+
(perAction ? actionFeeCalculations : calculations).push({
|
|
2709
2721
|
...calculation,
|
|
2710
2722
|
target,
|
|
2711
2723
|
} as UdlCalculation);
|
|
@@ -2828,6 +2840,8 @@ export function compile(
|
|
|
2828
2840
|
}
|
|
2829
2841
|
} else (a as unknown as Record<string, unknown>)[key] = data(expr);
|
|
2830
2842
|
}
|
|
2843
|
+
if (actionFeeCalculations.length)
|
|
2844
|
+
a.calculate = [...(a.calculate ?? []), ...actionFeeCalculations];
|
|
2831
2845
|
if (attachmentInfo && !attachmentInfo.child) {
|
|
2832
2846
|
if (attachmentInfo.exposed.has(name)) {
|
|
2833
2847
|
if (automatic(a.actor))
|
|
@@ -3307,7 +3321,9 @@ export function compile(
|
|
|
3307
3321
|
});
|
|
3308
3322
|
};
|
|
3309
3323
|
for (const decl of program.decls) {
|
|
3310
|
-
|
|
3324
|
+
// A parameterized program instrument is a template, like a header
|
|
3325
|
+
// instrument: each attachment instantiates it with its own bindings.
|
|
3326
|
+
if (decl.kind === "instrument" && !decl.parameters.length) {
|
|
3311
3327
|
addInstrument(decl, decl.name, emptyBlock, decl.span);
|
|
3312
3328
|
}
|
|
3313
3329
|
if (decl.kind === "assignment") {
|
|
@@ -3647,7 +3663,9 @@ export function compile(
|
|
|
3647
3663
|
fail(
|
|
3648
3664
|
at,
|
|
3649
3665
|
`\`${name}\` is not a declared party.${didYouMean(name, names)}`,
|
|
3650
|
-
|
|
3666
|
+
subjectPartyRoles.includes(name as SubjectPartyRole)
|
|
3667
|
+
? `Declare a party parameter such as \`holder: party\` on the instrument, use it here, and bind \`holder: ${name}\` where you attach it.`
|
|
3668
|
+
: `Declare it with \`party ${name}: business\`, or name a declared party (${names.join(", ")}).`,
|
|
3651
3669
|
);
|
|
3652
3670
|
};
|
|
3653
3671
|
const checkInput = (
|
|
@@ -3725,6 +3743,42 @@ export function compile(
|
|
|
3725
3743
|
inst,
|
|
3726
3744
|
label: declarations.get(inst)!.name,
|
|
3727
3745
|
});
|
|
3746
|
+
// A family member drops an optional reference field its binding left
|
|
3747
|
+
// unset (a borrower-funded plan has no `funds`), so it cannot match and
|
|
3748
|
+
// leaves the selection. The header must still declare the field.
|
|
3749
|
+
const matchable = (selection: UdlSelection) => {
|
|
3750
|
+
const members = [selection.instrument].flat();
|
|
3751
|
+
const header = selection.family
|
|
3752
|
+
? templates.get(
|
|
3753
|
+
`${selection.family.module}.${selection.family.exportPath}`,
|
|
3754
|
+
)
|
|
3755
|
+
: undefined;
|
|
3756
|
+
if (
|
|
3757
|
+
!header ||
|
|
3758
|
+
!entries(asBlock(entries(header.body).get("fields"))).has(
|
|
3759
|
+
selection.reference,
|
|
3760
|
+
)
|
|
3761
|
+
)
|
|
3762
|
+
return members;
|
|
3763
|
+
return members.filter((id) =>
|
|
3764
|
+
resolveField(document, byId.get(id)!, `self.${selection.reference}`),
|
|
3765
|
+
);
|
|
3766
|
+
};
|
|
3767
|
+
// A count over no matchable member is zero.
|
|
3768
|
+
for (const inst of document.instruments)
|
|
3769
|
+
for (const action of Object.values(inst.actions))
|
|
3770
|
+
action.requires = action.requires.map((requirement) =>
|
|
3771
|
+
requirement.kind === "aggregate" &&
|
|
3772
|
+
requirement.measure === "count" &&
|
|
3773
|
+
!matchable(requirement.selection).length
|
|
3774
|
+
? {
|
|
3775
|
+
kind: "compare",
|
|
3776
|
+
left: { literal: 0 },
|
|
3777
|
+
operator: requirement.operator,
|
|
3778
|
+
right: requirement.value,
|
|
3779
|
+
}
|
|
3780
|
+
: requirement,
|
|
3781
|
+
);
|
|
3728
3782
|
const checkSelection = (
|
|
3729
3783
|
selection: UdlSelection,
|
|
3730
3784
|
at: Located,
|
|
@@ -3732,6 +3786,12 @@ export function compile(
|
|
|
3732
3786
|
) => {
|
|
3733
3787
|
walk(selection.anchor, "anchor", site(selection, "anchor", at), scope);
|
|
3734
3788
|
walk(selection.where, "where", site(selection, "where", at), scope);
|
|
3789
|
+
const members = matchable(selection);
|
|
3790
|
+
if (
|
|
3791
|
+
members.length &&
|
|
3792
|
+
members.length < [selection.instrument].flat().length
|
|
3793
|
+
)
|
|
3794
|
+
selection.instrument = members;
|
|
3735
3795
|
for (const id of [selection.instrument].flat()) {
|
|
3736
3796
|
checkTarget(id, site(selection, "instrument", at));
|
|
3737
3797
|
const target = scopeOf(byId.get(id)!);
|