@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.
@@ -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,uhIAAuhI,CAAC;IACtiI,CAAC,iBAAiB,EAAE,ooKAAooK,CAAC;IACzpK,CAAC,YAAY,EAAE,o8DAAo8D,CAAC;IACp9D,CAAC,eAAe,EAAE,g5vDAAg5vD,CAAC;IACn6vD,CAAC,eAAe,EAAE,+0GAA+0G,CAAC;IACl2G,CAAC,aAAa,EAAE,mvPAAmvP,CAAC;IACpwP,CAAC,iBAAiB,EAAE,m0PAAm0P,CAAC;IACx1P,CAAC,WAAW,EAAE,y1RAAy1R,CAAC;IACx2R,CAAC,eAAe,EAAE,w77CAAw77C,CAAC;IAC387C,CAAC,aAAa,EAAE,wrGAAwrG,CAAC;IACzsG,CAAC,YAAY,EAAE,8kMAA8kM,CAAC;IAC9lM,CAAC,YAAY,EAAE,m6DAAm6D,CAAC;CACp7D,CAAC,CAAC"}
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,k6JAAk6J,CAAC;IACj7J,CAAC,iBAAiB,EAAE,s8LAAs8L,CAAC;IAC39L,CAAC,YAAY,EAAE,00FAA00F,CAAC;IAC11F,CAAC,eAAe,EAAE,2z0DAA2z0D,CAAC;IAC900D,CAAC,eAAe,EAAE,0+IAA0+I,CAAC;IAC7/I,CAAC,aAAa,EAAE,oyRAAoyR,CAAC;IACrzR,CAAC,iBAAiB,EAAE,y1RAAy1R,CAAC;IAC92R,CAAC,WAAW,EAAE,0yTAA0yT,CAAC;IACzzT,CAAC,eAAe,EAAE,uq9CAAuq9C,CAAC;IAC1r9C,CAAC,aAAa,EAAE,o+IAAo+I,CAAC;IACr/I,CAAC,YAAY,EAAE,s6OAAs6O,CAAC;IACt7O,CAAC,YAAY,EAAE,qjFAAqjF,CAAC;CACtkF,CAAC,CAAC"}
@@ -1,3 +1,2 @@
1
- export declare const HSX_VERSION = "5.3.0";
2
- export declare const HSX_TARGET_UDL_VERSION = 4;
1
+ export declare const HSX_VERSION = "5.4.0";
3
2
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,UAAU,CAAC;AACnC,eAAO,MAAM,sBAAsB,IAAI,CAAC"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,UAAU,CAAC"}
@@ -1,3 +1,2 @@
1
- export const HSX_VERSION = "5.3.0";
2
- export const HSX_TARGET_UDL_VERSION = 4;
1
+ export const HSX_VERSION = "5.4.0";
3
2
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC;AACnC,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC"}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC"}
package/docs/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # HSX 5.0.1
1
+ # HSX
2
2
 
3
3
  A program declares business objects and financial instruments. Objects contain
4
4
  optional metadata. Attachments expose named actions against an object.
@@ -49,6 +49,9 @@ library's declared reference type. References can name a child, such as
49
49
  objects of type T. `on: [plan_3, plan_6]` creates one late-charge or collections
50
50
  policy. Its references can point into either plan; selections can span both.
51
51
  A plain `ref<T>` accepts one object only.
52
+ Tunable constructors are `enum(choices)` and `integer(minimum, maximum)`.
53
+ Other constructors, including `party(business)` and bounded `money(...)`, are
54
+ rejected at the header declaration. Their arguments do not restrict bindings.
52
55
  Imports are `use header`; there are no file imports, macros or executable strings.
53
56
 
54
57
  Amounts use `750 SAR`; percentages use `2.5%`; durations use `48h`, `3d`, or `1w`.
@@ -78,8 +81,9 @@ same book and key within the Product. Providers remain outside parties.
78
81
  A missing binding refuses before money moves. Reference paths such as
79
82
  `self.cover.insurer` use the referenced agreement's account.
80
83
 
81
- Insurance collection credits the premium to the operator, then moves the premium
82
- net of commission to the insurer account. Refunds reverse those two portions.
84
+ Insurance collection pays the premium net of commission to the insurer account
85
+ and pays commission separately to the bound broker, which defaults to the operator.
86
+ Refunds return those two portions directly from insurer and broker to holder.
83
87
  Claims reserve from the cover's insurer account. These moves record ledger money;
84
88
  external confirmation still needs the boundary protocol.
85
89
  `account(buyer, claim, contra, "debt")` declares the borrower's claim contra account.
@@ -271,7 +275,7 @@ adapters are bound, participants have funds, or the flow can finish.
271
275
  | `lending.distribution` | Cash distribution needs an eligible recorded settlement, `prepare_cash`, one share record per funded commitment, then `distribute_cash`. Attaching it moves nothing. |
272
276
  | `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. |
273
277
  | `travel.booking` | From `deposit_paid` or `paid`, early cancellation returns the full held balance, middle returns held balance minus deposit, and late has no buyer refund transfer. State, time and balance requirements still apply. A deposit-only early cancellation refunds the deposit; a deposit-only middle cancellation refunds zero. |
274
- | `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 positions. |
278
+ | `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. |
275
279
  | `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. |
276
280
  | `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. |
277
281
  | `financing.late_charge` | `fine` is a fixed money amount, not a percentage of overdue principal. |
@@ -282,3 +286,44 @@ commitment creation, and cash prepare/distribute actions. Its repayment and shar
282
286
  child actions still lack a public execution path. It is a composition example,
283
287
  not a complete public repayment flow. Callers still need dates, agreement inputs,
284
288
  funded wallets, eligible settlements and the distribution's share records.
289
+
290
+ ## Checkout and reversals
291
+
292
+ For financed checkout, expose `installments.collect_down_payment` after `create`
293
+ and `sign`. It credits the linked pending escrow with the missing down payment.
294
+ It does not disburse principal or activate the plan. The later `disburse` action
295
+ uses that credit before collecting any remainder. Borrower-directed financing
296
+ has no checkout collection action. The plan and escrow must agree on payer and
297
+ price.
298
+
299
+ Expose escrow `cancel` to return money credited before full funding. Plan `void`
300
+ cancels the signed financing agreement but does not cancel its escrow. After
301
+ funding and delivery, the default return path needs three separate aliases for
302
+ `dispute`, `verify_return` and `refund`. The payee supplies return evidence and
303
+ returns the full price to the payer. `accept` pays the payee instead.
304
+ Marketplace order fulfillment alone does not execute any of these money actions.
305
+
306
+ Expose `late_charge.create` to create an assessment with matching plan, slice
307
+ and borrower. Only that existing assessment can reach clock-owned `assess` after
308
+ grace. A payment can collect part of a fine without settling the rest. A recovery
309
+ cost assessment is a separate record.
310
+
311
+ ## Separate payments and states
312
+
313
+ Bind `insurance.cover.broker` to the agency that receives commission. Collection
314
+ pays the insurer's net premium and the broker's commission as two moves. Refunds
315
+ reverse those same portions. The default broker is the program operator.
316
+ `claim.deny` accepts only `submitted`; after approval, `pay` or expiry consumes
317
+ the reservation.
318
+
319
+ A booking cancellation refunds only its own held cash. Expose `cover.cancel`
320
+ separately for eligible premium refunds before the paid period starts. Active
321
+ and expired premium periods are excluded. A card transaction needs a captured
322
+ authorization and its own record before refund, but no authorization-expiry wait.
323
+
324
+ A savings membership becomes `received` after its pot payout. Its contribution
325
+ records become `paid` independently. Future contributions can remain pending
326
+ after the member receives the pot; paying a contribution does not advance their
327
+ payout seat. Late contributions remain eligible while the membership is
328
+ `active` or `received` and the circle is `active`. These guards do not promise
329
+ scheduler retries or permit a payout larger than the available cash.
package/docs/headers.md CHANGED
@@ -29,7 +29,7 @@ Bind party parameters to owner, actor, operator or declared parties. Configure d
29
29
  | lending.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)` |
30
30
  | lending.commitment | `round: ref<lending.round>`, `wallet: ref<wallet.balance>`, `investor: party = party(person)` |
31
31
  | lending.distribution | `round: ref<lending.round>`, `receipt: ref`, `commitments: ref<lending.commitment> = object(lending.commitment)`, `mode: enum(cash, loss) = cash`, `residual_to: party = programOperator`, `operator: party = programOperator`, `fee: percent = 1%`, `tax: percent = 15%` |
32
- | insurance.cover | `holder: party`, `adapter: text`, `commission: percent = 0%`, `covers: ref`, `premium: money = runtime` |
32
+ | insurance.cover | `holder: party`, `adapter: text`, `commission: percent = 0%`, `broker: party = programOperator`, `covers: ref`, `premium: money = runtime` |
33
33
  | insurance.claim | `cover: ref<insurance.cover>`, `inspector: party` |
34
34
  | collections.case | `on: ref<financing.installments>[]`, `agency: party`, `capital: party = programOperator`, `overdue: duration = 3d`, `fee: percent = 20%` |
35
35
  | collections.contact | `case: ref<collections.case>[]`, `agency: party`, `max_contacts: integer = 10`, `window: duration = 30d`, `contact_from: integer(0, 23) = 8`, `contact_until: integer(1, 23) = 20`, `timezone: text = "Asia/Riyadh"`; contact_from less_than contact_until |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperscale0/hsx",
3
- "version": "5.3.0",
3
+ "version": "5.4.0",
4
4
  "description": "The strictly typed HSX language: compile total financial programs into canonical UDL.",
5
5
  "keywords": [
6
6
  "hsx",
@@ -86,7 +86,7 @@
86
86
  },
87
87
  "dependencies": {
88
88
  "@hyperscale0/adl": "1.0.0-beta.8",
89
- "@hyperscale0/udl": "4.4.1",
89
+ "@hyperscale0/udl": "4.5.0",
90
90
  "fast-sha256": "1.3.0"
91
91
  },
92
92
  "devDependencies": {
@@ -98,5 +98,5 @@
98
98
  "Amir Ayub",
99
99
  "Sara AlBakaawi"
100
100
  ],
101
- "gitHead": "c4c05d21a84c60a5dd5fcd94412f0997a091b05c"
101
+ "gitHead": "4141f2296d9427b30d04933c735be4af1ba88dec"
102
102
  }
package/src/cli.ts CHANGED
@@ -5,6 +5,8 @@ import { HSX_VERSION } from "./version.ts";
5
5
  import { compile } from "./compile.ts";
6
6
  import { format } from "./format.ts";
7
7
  import { serializeUdl } from "@hyperscale0/udl";
8
+ const usage =
9
+ "hsx build|format|cost <file> [--out path]\nhsx check <file>\nhsx headers --json";
8
10
  const standardLibrary = {
9
11
  source: (header: string) => {
10
12
  for (const base of ["../std/", "../../std/"]) {
@@ -20,8 +22,6 @@ export interface Io {
20
22
  err(line: string): void;
21
23
  readFile(path: string): Promise<string>;
22
24
  writeFile(path: string, text: string): Promise<void>;
23
- stdin?: NodeJS.ReadableStream;
24
- stdout?: NodeJS.WritableStream;
25
25
  }
26
26
  export async function runCli(argv: readonly string[], io: Io): Promise<number> {
27
27
  const [command, file, ...options] = argv;
@@ -34,7 +34,7 @@ export async function runCli(argv: readonly string[], io: Io): Promise<number> {
34
34
  return 0;
35
35
  }
36
36
  if (["--help", "help", "-h"].includes(command ?? "")) {
37
- io.out("hsx build|check|format|cost <file> [--out path]");
37
+ io.out(usage);
38
38
  return 0;
39
39
  }
40
40
  if (command === "--version") {
@@ -46,14 +46,20 @@ export async function runCli(argv: readonly string[], io: Io): Promise<number> {
46
46
  !file ||
47
47
  !["build", "check", "format", "cost"].includes(command)
48
48
  ) {
49
- io.err("usage: hsx build|check|format|cost <file> [--out path]");
49
+ io.err(`usage:\n${usage}`);
50
50
  return 2;
51
51
  }
52
52
  let output: string | undefined;
53
53
  for (let i = 0; i < options.length; i++) {
54
- if (options[i] === "--out" && options[i + 1] && output === undefined)
54
+ if (
55
+ command !== "check" &&
56
+ options[i] === "--out" &&
57
+ options[i + 1] &&
58
+ !options[i + 1]!.startsWith("--") &&
59
+ output === undefined
60
+ )
55
61
  output = options[++i];
56
- else if (!["--json", "--strict"].includes(options[i]!)) {
62
+ else {
57
63
  io.err(`unknown option ${options[i]}`);
58
64
  return 2;
59
65
  }
package/src/compile.ts CHANGED
@@ -1099,11 +1099,16 @@ export function compile(
1099
1099
  (binding.kind === "name" && binding.value === expr.value))
1100
1100
  )
1101
1101
  return expr;
1102
- // A resolved party parameter takes precedence over a same-named attachment.
1102
+ // A resolved party parameter or an own parameter bound to a value takes
1103
+ // precedence over a same-named sibling attachment; `limits: limits`
1104
+ // binds by identity and still names the sibling.
1103
1105
  if (attachmentInfo && !resolvedParties.has(expr.value)) {
1104
1106
  const [local, ...tail] = expr.value.split(".");
1107
+ const own = environment.get(local!);
1108
+ const ownValue =
1109
+ own !== undefined && !(own.kind === "name" && own.value === local);
1105
1110
  const target = `${attachmentInfo.subjectKindId}_${local}`;
1106
- if (attachmentSubjects.has(target))
1111
+ if (!ownValue && attachmentSubjects.has(target))
1107
1112
  return { ...expr, value: [target, ...tail].join("_") };
1108
1113
  }
1109
1114
  if (expr.value.startsWith("party.")) {
@@ -2629,7 +2634,7 @@ export function compile(
2629
2634
  }
2630
2635
  }
2631
2636
  if (automatic(a.actor)) delete a.publicAction;
2632
- const checkSubjectPaths = (obj: unknown, span: Span, key = "") => {
2637
+ const checkSubjectPaths = (obj: unknown, key = "") => {
2633
2638
  if (typeof obj === "string") {
2634
2639
  if (
2635
2640
  [
@@ -2649,7 +2654,7 @@ export function compile(
2649
2654
  );
2650
2655
  if (!req) {
2651
2656
  failWithCode(
2652
- { span },
2657
+ row,
2653
2658
  "subject_field_unknown",
2654
2659
  `subject.${subField} names no declared subject requirement in action ${name}`,
2655
2660
  `declare ${subField} in subject { ... }`,
@@ -2657,15 +2662,15 @@ export function compile(
2657
2662
  }
2658
2663
  }
2659
2664
  } else if (Array.isArray(obj)) {
2660
- for (const item of obj) checkSubjectPaths(item, span, key);
2665
+ for (const item of obj) checkSubjectPaths(item, key);
2661
2666
  } else if (obj !== null && typeof obj === "object") {
2662
2667
  for (const [key, val] of Object.entries(obj))
2663
- checkSubjectPaths(val, span, key);
2668
+ checkSubjectPaths(val, key);
2664
2669
  }
2665
2670
  };
2666
- checkSubjectPaths(a.requires, row.span);
2667
- checkSubjectPaths(a.set, row.span);
2668
- checkSubjectPaths(a.invoke, row.span);
2671
+ checkSubjectPaths(a.requires);
2672
+ checkSubjectPaths(a.set);
2673
+ checkSubjectPaths(a.invoke);
2669
2674
  currentAction = undefined;
2670
2675
  currentActionName = undefined;
2671
2676
  for (const requirement of a.subject?.requirements ?? []) {
@@ -3394,6 +3399,24 @@ export function compile(
3394
3399
  );
3395
3400
  else action!.publicAction = decl.name!;
3396
3401
  }
3402
+ for (const decl of program.decls) {
3403
+ if (decl.kind !== "expose") continue;
3404
+ const parts = decl.target.split(".");
3405
+ parts.pop();
3406
+ const instrument = document.instruments.find(
3407
+ (item) => item.id === parts.join("_"),
3408
+ )!;
3409
+ if (
3410
+ Object.values(instrument.actions).filter(
3411
+ (action) => action.publicAction === decl.name,
3412
+ ).length > 1
3413
+ )
3414
+ fail(
3415
+ decl,
3416
+ `public action ${decl.name} is used more than once on ${parts.join(".")}`,
3417
+ "give each exposed action a distinct public name",
3418
+ );
3419
+ }
3397
3420
  const usedParties = new Set<string>();
3398
3421
  const collectParties = (value: unknown): void => {
3399
3422
  if (typeof value === "string") {
@@ -38,6 +38,16 @@ export function parseHeader(source: string, name: string) {
38
38
  "declare each parameter once",
39
39
  );
40
40
  parameters.add(parameter.key);
41
+ const type =
42
+ parameter.value.kind === "default"
43
+ ? parameter.value.type
44
+ : parameter.value;
45
+ if (type.kind === "call" && !["enum", "integer"].includes(type.name))
46
+ fail(
47
+ type,
48
+ `unsupported tunable constructor ${type.name}`,
49
+ "Only enum(choices) and integer(minimum, maximum) are supported tunable constructors. This constructor's restrictions cannot be enforced.",
50
+ );
41
51
  }
42
52
  }
43
53
  return program;