@hyperscale0/hsx 1.0.342 → 1.0.347

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 (128) hide show
  1. package/README.md +1 -1
  2. package/bases/cards.hsx +0 -1
  3. package/bases/collections.hsx +0 -2
  4. package/bases/escrow.hsx +0 -1
  5. package/bases/financing.hsx +0 -1
  6. package/bases/insurance.hsx +0 -1
  7. package/bases/lending.hsx +0 -3
  8. package/bases/marketplace.hsx +0 -2
  9. package/bases/money.hsx +0 -1
  10. package/bases/reporting.hsx +0 -2
  11. package/bases/savings.hsx +0 -1
  12. package/bases/wallet.hsx +0 -1
  13. package/dist/src/ast.d.ts +57 -7
  14. package/dist/src/ast.d.ts.map +1 -1
  15. package/dist/src/ast.js.map +1 -1
  16. package/dist/src/bases-bundle.js +11 -11
  17. package/dist/src/bases-bundle.js.map +1 -1
  18. package/dist/src/catalog-program.d.ts +1 -1
  19. package/dist/src/catalog-program.d.ts.map +1 -1
  20. package/dist/src/catalog-program.js +0 -13
  21. package/dist/src/catalog-program.js.map +1 -1
  22. package/dist/src/checks.d.ts +60 -0
  23. package/dist/src/checks.d.ts.map +1 -0
  24. package/dist/src/checks.js +240 -0
  25. package/dist/src/checks.js.map +1 -0
  26. package/dist/src/cli.d.ts.map +1 -1
  27. package/dist/src/cli.js +6 -25
  28. package/dist/src/cli.js.map +1 -1
  29. package/dist/src/compile.d.ts +29 -12
  30. package/dist/src/compile.d.ts.map +1 -1
  31. package/dist/src/compile.js +414 -316
  32. package/dist/src/compile.js.map +1 -1
  33. package/dist/src/diagnostics.d.ts +14 -1
  34. package/dist/src/diagnostics.d.ts.map +1 -1
  35. package/dist/src/diagnostics.js +31 -0
  36. package/dist/src/diagnostics.js.map +1 -1
  37. package/dist/src/examples-bundle.js +15 -15
  38. package/dist/src/examples-bundle.js.map +1 -1
  39. package/dist/src/header-source.d.ts +30 -0
  40. package/dist/src/header-source.d.ts.map +1 -1
  41. package/dist/src/header-source.js +120 -9
  42. package/dist/src/header-source.js.map +1 -1
  43. package/dist/src/headers.d.ts +9 -18
  44. package/dist/src/headers.d.ts.map +1 -1
  45. package/dist/src/headers.js +25 -23
  46. package/dist/src/headers.js.map +1 -1
  47. package/dist/src/index.d.ts +4 -3
  48. package/dist/src/index.d.ts.map +1 -1
  49. package/dist/src/index.js +2 -2
  50. package/dist/src/index.js.map +1 -1
  51. package/dist/src/keywords.d.ts +2 -4
  52. package/dist/src/keywords.d.ts.map +1 -1
  53. package/dist/src/keywords.js +6 -5
  54. package/dist/src/keywords.js.map +1 -1
  55. package/dist/src/language.d.ts +3 -4
  56. package/dist/src/language.d.ts.map +1 -1
  57. package/dist/src/language.js +123 -60
  58. package/dist/src/language.js.map +1 -1
  59. package/dist/src/lex.d.ts +1 -1
  60. package/dist/src/lex.d.ts.map +1 -1
  61. package/dist/src/lex.js +3 -5
  62. package/dist/src/lex.js.map +1 -1
  63. package/dist/src/parse.d.ts +3 -1
  64. package/dist/src/parse.d.ts.map +1 -1
  65. package/dist/src/parse.js +234 -22
  66. package/dist/src/parse.js.map +1 -1
  67. package/dist/src/std-bundle.d.ts +2 -1
  68. package/dist/src/std-bundle.d.ts.map +1 -1
  69. package/dist/src/std-bundle.js +16 -15
  70. package/dist/src/std-bundle.js.map +1 -1
  71. package/dist/src/template-guide.d.ts +3 -9
  72. package/dist/src/template-guide.d.ts.map +1 -1
  73. package/dist/src/template-guide.js +16 -23
  74. package/dist/src/template-guide.js.map +1 -1
  75. package/dist/src/toolchain.d.ts +23 -0
  76. package/dist/src/toolchain.d.ts.map +1 -0
  77. package/dist/src/toolchain.js +29 -0
  78. package/dist/src/toolchain.js.map +1 -0
  79. package/docs/README.md +69 -28
  80. package/examples/camera-rental.hsx +0 -1
  81. package/examples/cards.hsx +0 -1
  82. package/examples/collections.hsx +0 -2
  83. package/examples/escrow.hsx +0 -1
  84. package/examples/financing.hsx +0 -1
  85. package/examples/insurance.hsx +0 -2
  86. package/examples/lending.hsx +0 -3
  87. package/examples/library.hsx +0 -3
  88. package/examples/marketplace.hsx +0 -2
  89. package/examples/money.hsx +0 -1
  90. package/examples/rental-hire.hsx +0 -1
  91. package/examples/reporting.hsx +0 -2
  92. package/examples/savings.hsx +0 -1
  93. package/examples/serviced.hsx +0 -4
  94. package/examples/wallet.hsx +0 -1
  95. package/package.json +4 -4
  96. package/src/ast.ts +56 -3
  97. package/src/bases-bundle.ts +11 -11
  98. package/src/catalog-program.ts +0 -13
  99. package/src/checks.ts +386 -0
  100. package/src/cli.ts +6 -25
  101. package/src/compile.ts +583 -438
  102. package/src/diagnostics.ts +49 -1
  103. package/src/examples-bundle.ts +15 -15
  104. package/src/header-source.ts +187 -11
  105. package/src/headers.ts +31 -32
  106. package/src/index.ts +10 -4
  107. package/src/keywords.ts +6 -5
  108. package/src/language.ts +153 -68
  109. package/src/lex.ts +3 -5
  110. package/src/parse.ts +216 -24
  111. package/src/std-bundle.ts +17 -15
  112. package/src/template-guide.ts +17 -34
  113. package/src/toolchain.ts +51 -0
  114. package/std/booking.hsx +14 -1
  115. package/std/collections.hsx +12 -2
  116. package/std/financing.hsx +10 -16
  117. package/std/money.hsx +2 -3
  118. package/std/reporting.hsx +10 -1
  119. package/dist/src/binding-contract.d.ts +0 -22
  120. package/dist/src/binding-contract.d.ts.map +0 -1
  121. package/dist/src/binding-contract.js +0 -80
  122. package/dist/src/binding-contract.js.map +0 -1
  123. package/dist/src/std-library.d.ts +0 -5
  124. package/dist/src/std-library.d.ts.map +0 -1
  125. package/dist/src/std-library.js +0 -5
  126. package/dist/src/std-library.js.map +0 -1
  127. package/src/binding-contract.ts +0 -111
  128. package/src/std-library.ts +0 -7
@@ -1,10 +1,10 @@
1
1
  import type { BlockExpr, Entry, Expr, InstrumentDecl } from "./ast.ts";
2
2
  import { catalogProgramSource } from "./catalog-program.ts";
3
- import { compile, type CompileOptions } from "./compile.ts";
3
+ import { compile } from "./compile.ts";
4
4
  import { parseHeader } from "./header-source.ts";
5
5
  import { headerManifest, HEADER_NAMES } from "./headers.ts";
6
6
  import { parseProgram } from "./parse.ts";
7
- import { bundledStandardLibrary, type StandardLibrary } from "./std-library.ts";
7
+ import { defaultToolchain, type Toolchain } from "./toolchain.ts";
8
8
 
9
9
  export interface TemplateIndexEntry {
10
10
  qualifiedName: string;
@@ -29,7 +29,6 @@ export interface TemplateGuide {
29
29
  qualifiedName: string;
30
30
  summary: string;
31
31
  signature: string;
32
- use: string;
33
32
  states: string[];
34
33
  tunables: Array<{
35
34
  name: string;
@@ -62,9 +61,9 @@ const SUBJECT_NOTE =
62
61
 
63
62
  /** One line per template, small enough to read whole. */
64
63
  export function templateIndex(
65
- library: StandardLibrary = bundledStandardLibrary,
64
+ toolchain: Toolchain = defaultToolchain,
66
65
  ): TemplateIndexEntry[] {
67
- return headerManifest(library).headers.flatMap((header) =>
66
+ return headerManifest(toolchain.std).headers.flatMap((header) =>
68
67
  header.objects.map((object) => ({
69
68
  qualifiedName: object.qualifiedName,
70
69
  summary: object.summary,
@@ -80,17 +79,16 @@ export function templateIndex(
80
79
  */
81
80
  export function templateGuide(
82
81
  qualifiedName: string,
83
- options: GuideOptions = {},
82
+ toolchain: Toolchain = defaultToolchain,
84
83
  ): TemplateGuide | undefined {
85
- const library = options.library ?? bundledStandardLibrary;
86
84
  const [header, name, extra] = qualifiedName.split(".");
87
85
  if (!header || !name || extra !== undefined) return undefined;
88
86
  if (!(HEADER_NAMES as readonly string[]).includes(header)) return undefined;
89
- const source = library.source(header);
87
+ const source = toolchain.std.get(header);
90
88
  if (!source) return undefined;
91
- const manifest = headerManifest(library, [header]).headers[0]!.objects.find(
92
- (object) => object.name === name,
93
- );
89
+ const manifest = headerManifest(toolchain.std, [
90
+ header,
91
+ ]).headers[0]!.objects.find((object) => object.name === name);
94
92
  const decl = parseHeader(source, header).decls.find(
95
93
  (d): d is InstrumentDecl => d.kind === "instrument" && d.name === name,
96
94
  );
@@ -103,11 +101,11 @@ export function templateGuide(
103
101
  const actions = guideActions(decl.body, spell);
104
102
  // Compile once bare, then expose each root action a party or caller runs
105
103
  // that these bindings keep, and compile again to prove the example.
106
- const example = exampleProgram(qualifiedName, library);
104
+ const example = exampleProgram(qualifiedName, toolchain);
107
105
  const bare = compiledAttachment(
108
106
  example.render([]),
109
107
  example.attachment,
110
- options,
108
+ toolchain,
111
109
  );
112
110
  const exposes = actions
113
111
  .filter(
@@ -118,7 +116,7 @@ export function templateGuide(
118
116
  )
119
117
  .map((action) => `expose ${action.name}`);
120
118
  const program = example.render(exposes);
121
- const compiled = compiledAttachment(program, example.attachment, options);
119
+ const compiled = compiledAttachment(program, example.attachment, toolchain);
122
120
  const create = compiled.actions.create;
123
121
  const economics = compiled.warnings.flatMap((warning) =>
124
122
  warning.code === "economic_purpose_missing" &&
@@ -130,7 +128,6 @@ export function templateGuide(
130
128
  qualifiedName,
131
129
  summary: manifest.summary,
132
130
  signature: manifest.signature,
133
- use: `use ${header}`,
134
131
  states: manifest.states,
135
132
  tunables: manifest.tunables.map((tunable) => ({
136
133
  name: tunable.name,
@@ -482,12 +479,9 @@ interface Example {
482
479
 
483
480
  /**
484
481
  * The template's attach from the catalog program, with every attach it names
485
- * and the parties and headers those need.
482
+ * and the parties those need.
486
483
  */
487
- function exampleProgram(
488
- qualifiedName: string,
489
- library: StandardLibrary,
490
- ): Example {
484
+ function exampleProgram(qualifiedName: string, toolchain: Toolchain): Example {
491
485
  const program = parseProgram(catalogProgramSource).program;
492
486
  const object = program.decls.find((decl) => decl.kind === "object");
493
487
  if (object?.kind !== "object")
@@ -499,7 +493,6 @@ function exampleProgram(
499
493
  {
500
494
  entry,
501
495
  name: match[1]!,
502
- header: match[2]!,
503
496
  template: `${match[2]}.${match[3]}`,
504
497
  },
505
498
  ]
@@ -518,7 +511,7 @@ function exampleProgram(
518
511
  // A parameter that defaults to object(header.template) binds the sibling
519
512
  // attach of that template, so the example carries that attach too.
520
513
  const templates = new Map(
521
- headerManifest(library).headers.flatMap((header) =>
514
+ headerManifest(toolchain.std).headers.flatMap((header) =>
522
515
  header.objects.map((object) => [object.qualifiedName, object] as const),
523
516
  ),
524
517
  );
@@ -552,7 +545,6 @@ function exampleProgram(
552
545
  render: (exposes) =>
553
546
  [
554
547
  `program example "${qualifiedName} example"`,
555
- ...new Set(included.map((attach) => `use ${attach.header}`)),
556
548
  ...program.decls.flatMap((decl) =>
557
549
  decl.kind === "party" && used.has(decl.name) ? [text(decl.span)] : [],
558
550
  ),
@@ -597,22 +589,13 @@ function wrapAttach(line: string): string {
597
589
  ].join("\n");
598
590
  }
599
591
 
600
- type GuideOptions = Pick<CompileOptions, "adapterRegistry"> & {
601
- library?: StandardLibrary;
602
- };
603
-
604
592
  /** The example's compiled root instrument, or the compiler's refusal. */
605
593
  function compiledAttachment(
606
594
  source: string,
607
595
  attachment: string,
608
- options: GuideOptions,
596
+ toolchain: Toolchain,
609
597
  ) {
610
- const result = compile(source, {
611
- ...(options.library ? { standardLibrary: options.library } : {}),
612
- ...(options.adapterRegistry
613
- ? { adapterRegistry: options.adapterRegistry }
614
- : {}),
615
- });
598
+ const result = compile(source, toolchain);
616
599
  const document = result.artifacts?.document;
617
600
  if (!document)
618
601
  throw new Error(
@@ -0,0 +1,51 @@
1
+ import { digest, UDL_FORMAT_VERSION } from "@hyperscale0/udl";
2
+ import type { ProviderAdapter } from "@hyperscale0/adl";
3
+ import { boundaryAdapters } from "@hyperscale0/adl/boundary-adapters";
4
+ import { BUNDLED_STD_HEADERS, HSX_VERSION } from "./std-bundle.ts";
5
+
6
+ export interface AdapterBindingTarget {
7
+ adapter: ProviderAdapter;
8
+ operation: string;
9
+ }
10
+
11
+ /**
12
+ * Every input a compile reads besides the source, named by one digest. The
13
+ * same source under the same toolchain lowers to the same document anywhere.
14
+ */
15
+ export interface Toolchain {
16
+ /** Header source by name, such as `money` for std/money.hsx. */
17
+ readonly std: ReadonlyMap<string, string>;
18
+ /** The boundary bindings a program may name, keyed by binding. */
19
+ readonly adapters: Readonly<Record<string, AdapterBindingTarget>>;
20
+ readonly udl: number;
21
+ readonly hsx: string;
22
+ readonly prover: string;
23
+ readonly digest: string;
24
+ }
25
+
26
+ export function toolchain(input: Omit<Toolchain, "digest">): Toolchain {
27
+ const { std, adapters, udl, hsx, prover } = input;
28
+ return {
29
+ std,
30
+ adapters,
31
+ udl,
32
+ hsx,
33
+ prover,
34
+ digest: digest({
35
+ std: Object.fromEntries(std),
36
+ adapters,
37
+ udl,
38
+ hsx,
39
+ prover,
40
+ }),
41
+ };
42
+ }
43
+
44
+ /** The only toolchain production compiles with. */
45
+ export const defaultToolchain = toolchain({
46
+ std: BUNDLED_STD_HEADERS,
47
+ adapters: boundaryAdapters,
48
+ udl: UDL_FORMAT_VERSION,
49
+ hsx: HSX_VERSION,
50
+ prover: `udl-${UDL_FORMAT_VERSION}-laws`,
51
+ });
package/std/booking.hsx CHANGED
@@ -23,7 +23,20 @@ header booking
23
23
  // composed counts.
24
24
 
25
25
  instrument reservation(customer: party = party(person), operator: party = programOperator, early_until: duration = 30d, late_from: duration = 15d, balance_due: duration = 15d, middle_forfeit: percent = 0%, late_forfeit: percent = 0%) {
26
- constraints { late_from: less_than(early_until) }
26
+ checks {
27
+ late_from < early_until {
28
+ message: "Attachment `{attachment}` sets `late_from` at or above `early_until`, so the late cancellation window opens before the early one closes."
29
+ fix: "Choose a `late_from` shorter than `early_until`, such as `early_until: 30d, late_from: 15d`."
30
+ }
31
+ middle_forfeit is percent {
32
+ message: "`middle_forfeit` is the share of the held money a middle cancellation keeps, from 0% to 100%."
33
+ fix: "Write `middle_forfeit` as a percentage from 0% to 100%, such as `middle_forfeit: 25%`."
34
+ }
35
+ late_forfeit is percent {
36
+ message: "`late_forfeit` is the share of the held money a late cancellation keeps, from 0% to 100%."
37
+ fix: "Write `late_forfeit` as a percentage from 0% to 100%, such as `late_forfeit: 50%`."
38
+ }
39
+ }
27
40
  revisioned: true
28
41
  summary: "Take a deposit and a balance for a dated booking, with cancellation windows before departure."
29
42
  fields {
@@ -37,7 +37,12 @@ instrument case(on: ref<financing.installments>[], agency: party, capital: party
37
37
  }
38
38
 
39
39
  instrument 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") {
40
- constraints { contact_from: less_than(contact_until) }
40
+ checks {
41
+ contact_from < contact_until {
42
+ message: "Attachment `{attachment}` sets `contact_from` at or after `contact_until`, so the daily contact window is empty."
43
+ fix: "Choose a `contact_from` hour before `contact_until`, such as `contact_from: 8, contact_until: 20`."
44
+ }
45
+ }
41
46
  summary: "An agency contact admitted against the locked rolling count and local hours."
42
47
  fields { agency: account of agency, case: ref<case>, attemptedAt: date?, evidence: text, maximum: integer = max_contacts }
43
48
  lifecycle { states: [recorded], initial: recorded }
@@ -62,7 +67,12 @@ instrument contact(case: ref<collections.case>[], agency: party, max_contacts: i
62
67
 
63
68
 
64
69
  instrument reminder(on: ref<financing.installments>[], operator: party = programOperator, before_days: integer(0, 366) = 3, overdue_days: integer(1, 366) = 1, max_per_day: integer(1, 10) = 1, send_from: integer(0, 23) = 8, send_until: integer(1, 24) = 20, timezone: text = "Asia/Riyadh") {
65
- constraints { send_from: less_than(send_until) }
70
+ checks {
71
+ send_from < send_until {
72
+ message: "Attachment `{attachment}` sets `send_from` at or after `send_until`, so the daily sending window is empty."
73
+ fix: "Choose a `send_from` hour before `send_until`, such as `send_from: 8, send_until: 20`."
74
+ }
75
+ }
66
76
  summary: "Send an email to the installment borrower's stored contact address."
67
77
  fields { slice: ref<on.slice> }
68
78
  lifecycle { states: [ready], initial: ready }
package/std/financing.hsx CHANGED
@@ -112,22 +112,22 @@ header financing
112
112
  // tariff.
113
113
 
114
114
  instrument installments(loan_kind: enum(mortgage, auto, personal, short_term) = personal, max_term: integer(1, 360) = 360, months: integer(1, 360) = runtime, max_history: integer(2, 366) = 366, pricing: enum(amortizing, flat_total), profit_rate: percent, charge_limit: integer(1, 24) = 12, disburse_to: enum(funds, borrower, seller) = funds, seller: party?, 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, refund_within: duration = 30d, returned_goods: enum(keep_profit, refund_all) = keep_profit, allow_overdue: enum(allowed, blocked) = allowed, assessed_fines: enum(carry, require_waiver) = carry, down_payment: percent = 0%, funds: ref?, 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)) {
115
- dependencies {
116
- funds {
117
- selector: disburse_to, is: funds
115
+ checks {
116
+ funds is set when disburse_to is funds {
118
117
  message: "Attachment `{attachment}` disburses into a hold, but `funds` is not bound."
119
118
  fix: "Bind `funds: checkout` to a funding composition. Choose `disburse_to: seller` to pay a seller on activation, or `disburse_to: borrower` only if the borrower should receive the money directly."
120
119
  }
121
- seller {
122
- selector: disburse_to, is: seller
120
+ seller is set when disburse_to is seller {
123
121
  message: "Attachment `{attachment}` pays a seller, but `seller` is not bound."
124
122
  fix: "Bind `seller: merchant` to the declared business that receives the principal."
125
123
  }
124
+ months <= max_term {
125
+ message: "Attachment `{attachment}` sets `months` above `max_term`, the longest term its loan kind allows."
126
+ fix: "Choose `months` of `max_term` or fewer, or set `max_term` up to the ceiling of its `loan_kind`."
127
+ }
126
128
  }
127
129
  ceilings { max_term { by: loan_kind, mortgage: 360, auto: 84, personal: 84, short_term: 12 } }
128
- constraints { months: at_most(max_term) }
129
130
  revisioned: true
130
- familyRevision: 1
131
131
  summary: "Funding, profit recognition and payment order chosen by the program."
132
132
  fields {
133
133
  funds: ref<funds>?, borrower: account of borrower, settlementIdentity: text, borrowerLimit: ref<limits>, portfolioLimit: ref<portfolio>
@@ -803,15 +803,13 @@ instrument installments(loan_kind: enum(mortgage, auto, personal, short_term) =
803
803
  // waiver and refund post one claim transfer each, and payoff and amendment post
804
804
  // one per attachment or kind, not one per charge.
805
805
  instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d, fine: money = 50 SAR, cap: money = 25 SAR, fines_to: party = programOperator, costs_to: party = programOperator, borrower: party = party(person)) {
806
- parameterDiagnostics {
807
- fine {
808
- accepts: "a fixed amount"
809
- percentage: "a percentage of overdue debt"
806
+ checks {
807
+ fine is money {
808
+ message: "`{instrument}.fine` accepts a fixed amount. It cannot express a percentage of overdue debt."
810
809
  fix: "Use a fixed amount only if that is the intended policy. A percentage charge needs an authored rate calculation; do not approximate it with a cash amount."
811
810
  }
812
811
  }
813
812
  revisioned: true
814
- familyRevision: 1
815
813
  summary: "Separate fine and evidenced recovery claims on an overdue installment."
816
814
  fields {
817
815
  plan: ref<on>, slice: ref<on.slice>, borrower: account of borrower, fine: money = fine, overdueAt: date?, payoffAmount: money?, payoffReceipt: text?, payoffLed: integer?
@@ -1008,7 +1006,6 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
1008
1006
  }
1009
1007
 
1010
1008
  instrument limits(per_borrower: money, borrower: party = party(person), active_plans: integer = 1) {
1011
- familyRevision: 1
1012
1009
  scope: product
1013
1010
  summary: "One outstanding balance ceiling per borrower."
1014
1011
  fields { borrower: account of borrower, limit: money = per_borrower, activePlans: integer = active_plans }
@@ -1018,7 +1015,6 @@ instrument limits(per_borrower: money, borrower: party = party(person), active_p
1018
1015
  }
1019
1016
 
1020
1017
  instrument portfolio_limit(limit: money) {
1021
- familyRevision: 1
1022
1018
  scope: product
1023
1019
  summary: "One shared ceiling on the principal the product may hold."
1024
1020
  fields { key: text = "portfolio", limit: money = limit }
@@ -1028,7 +1024,6 @@ instrument portfolio_limit(limit: money) {
1028
1024
  }
1029
1025
 
1030
1026
  instrument credit_line(borrower: party = programOperator, adapter: text, limit: money, expires: date) {
1031
- familyRevision: 1
1032
1027
  summary: "A revolving facility from an outside lender; each draw is a separate immutable advance."
1033
1028
  fields { borrower: account of borrower, facility: account(borrower, cash, "facility"), debt: account(borrower, claim, contra, "debt"), limit: money = limit, expiresAt: date = expires }
1034
1029
  lifecycle { states: [pending, active, suspended, closed], initial: pending }
@@ -1043,7 +1038,6 @@ instrument credit_line(borrower: party = programOperator, adapter: text, limit:
1043
1038
  }
1044
1039
 
1045
1040
  instrument advance(line: ref<financing.credit_line>, amount: money = runtime, borrower: party = programOperator) {
1046
- familyRevision: 1
1047
1041
  summary: "A single facility draw with an immutable principal and full repayment."
1048
1042
  fields { line: ref<line>, borrower: account of borrower, amount: money = amount, principalReceivable: account(self, claim) }
1049
1043
  lifecycle { states: [pending, drawn, repaid, cancelled], initial: pending }
package/std/money.hsx CHANGED
@@ -122,9 +122,8 @@ instrument split(payer: party, amount: money = runtime, shares: split = { progra
122
122
 
123
123
  instrument schedule(payer: party, payee: party, amount: money = runtime, count: integer(1, 366)?, every: period?) {
124
124
  summary: "Pieces due on listed dates that sum to the total, or one amount each period from a start date until stopped, or for count periods."
125
- dependencies {
126
- count {
127
- selector: every, is: unset
125
+ checks {
126
+ count is set when every is unset {
128
127
  message: "Attachment `{attachment}` charges on a list of dates, so it needs `count`, the number of dates."
129
128
  fix: "Add `count: 3` for three dated pieces, or add `every: 1 month` to charge each month until stopped or cancelled."
130
129
  }
package/std/reporting.hsx CHANGED
@@ -17,7 +17,16 @@ instrument portfolio(on: ref<financing.installments>[], default_days: integer(1,
17
17
  fields {}
18
18
  lifecycle { states: [declared], initial: declared }
19
19
  action create {}
20
- constraints { aging_first_days: less_than(aging_second_days), aging_second_days: less_than(aging_third_days) }
20
+ checks {
21
+ aging_first_days < aging_second_days {
22
+ message: "Attachment `{attachment}` sets `aging_first_days` at or above `aging_second_days`, so the aging buckets overlap."
23
+ fix: "Choose ascending bucket bounds, such as `aging_first_days: 30, aging_second_days: 60, aging_third_days: 90`."
24
+ }
25
+ aging_second_days < aging_third_days {
26
+ message: "Attachment `{attachment}` sets `aging_second_days` at or above `aging_third_days`, so the aging buckets overlap."
27
+ fix: "Choose ascending bucket bounds, such as `aging_first_days: 30, aging_second_days: 60, aging_third_days: 90`."
28
+ }
29
+ }
21
30
  reports: [
22
31
  {
23
32
  identity: { id: "portfolio_aging", version: 1, description: "Period-end outstanding principal by oldest unpaid installment. Remaining scheduled profit is distinct from accrued profit. Restructuring evidence is unavailable." },
@@ -1,22 +0,0 @@
1
- import type { Entry, InstrumentDecl } from "./ast.ts";
2
- export declare class BindingContractError extends Error {
3
- readonly entry: Entry;
4
- constructor(entry: Entry, message: string);
5
- }
6
- /** Conditional requirements are header data, independent of module names. */
7
- export declare function bindingDependencies(decl: InstrumentDecl): {
8
- binding: string;
9
- when: string;
10
- is: string;
11
- message: string;
12
- fix: string;
13
- span: import("./ast.ts").Span;
14
- }[];
15
- /** A header explains why a typed parameter cannot encode a proposed policy. */
16
- export declare function parameterDiagnostics(decl: InstrumentDecl): {
17
- parameter: string;
18
- accepts: string;
19
- percentage: string;
20
- fix: string;
21
- }[];
22
- //# sourceMappingURL=binding-contract.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"binding-contract.d.ts","sourceRoot":"","sources":["../../src/binding-contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAQ,cAAc,EAAE,MAAM,UAAU,CAAC;AAE5D,qBAAa,oBAAqB,SAAQ,KAAK;IAE3C,QAAQ,CAAC,KAAK,EAAE,KAAK;IADvB,YACW,KAAK,EAAE,KAAK,EACrB,OAAO,EAAE,MAAM,EAGhB;CACF;AAkDD,6EAA6E;AAC7E,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,cAAc;;;;;;;IAqCvD;AAED,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,cAAc;;;;;IAUxD"}
@@ -1,80 +0,0 @@
1
- export class BindingContractError extends Error {
2
- entry;
3
- constructor(entry, message) {
4
- super(message);
5
- this.entry = entry;
6
- }
7
- }
8
- function rows(value) {
9
- if (value.kind === "block")
10
- return value.entries;
11
- throw new BindingContractError({ key: "contract", value, span: value.span }, "Binding contract needs a block.");
12
- }
13
- function properties(entry, allowed) {
14
- const result = new Map();
15
- for (const slot of rows(entry.value)) {
16
- if (!allowed.includes(slot.key) ||
17
- result.has(slot.key) ||
18
- (slot.value.kind !== "text" && slot.value.kind !== "name"))
19
- throw new BindingContractError(slot, "Invalid binding contract property.");
20
- result.set(slot.key, slot.value.value);
21
- }
22
- if (allowed.some((key) => !result.get(key)))
23
- throw new BindingContractError(entry, `Binding contract needs ${allowed.join(", ")}.`);
24
- return result;
25
- }
26
- function contractEntries(decl, name) {
27
- const blocks = decl.body.entries.filter((entry) => entry.key === name);
28
- if (blocks.length > 1)
29
- throw new BindingContractError(blocks[1], `Duplicate ${name}.`);
30
- const entries = blocks[0] ? rows(blocks[0].value) : [];
31
- const seen = new Set();
32
- for (const entry of entries) {
33
- if (seen.has(entry.key) ||
34
- !decl.parameters.some((parameter) => parameter.key === entry.key))
35
- throw new BindingContractError(entry, "Binding contract must name each declared tunable once.");
36
- seen.add(entry.key);
37
- }
38
- return entries;
39
- }
40
- /** Conditional requirements are header data, independent of module names. */
41
- export function bindingDependencies(decl) {
42
- return contractEntries(decl, "dependencies").map((entry) => {
43
- const values = properties(entry, ["selector", "is", "message", "fix"]);
44
- const selector = decl.parameters.find((parameter) => parameter.key === values.get("selector"));
45
- const type = selector?.value.kind === "default"
46
- ? selector.value.type
47
- : selector?.value;
48
- // An optional selector reads like `when every is unset`.
49
- const presence = (type?.kind === "type" || type?.kind === "call") &&
50
- type.name !== "enum" &&
51
- !!type.optional &&
52
- ["set", "unset"].includes(values.get("is"));
53
- if (!presence &&
54
- (type?.kind !== "call" ||
55
- type.name !== "enum" ||
56
- !type.args.some((arg) => arg.kind === "name" && arg.value === values.get("is"))))
57
- throw new BindingContractError(entry, "Dependency must select a declared enum choice, or set or unset of an optional tunable.");
58
- return {
59
- binding: entry.key,
60
- when: values.get("selector"),
61
- is: values.get("is"),
62
- message: values.get("message"),
63
- fix: values.get("fix"),
64
- span: entry.span,
65
- };
66
- });
67
- }
68
- /** A header explains why a typed parameter cannot encode a proposed policy. */
69
- export function parameterDiagnostics(decl) {
70
- return contractEntries(decl, "parameterDiagnostics").map((entry) => {
71
- const values = properties(entry, ["accepts", "percentage", "fix"]);
72
- return {
73
- parameter: entry.key,
74
- accepts: values.get("accepts"),
75
- percentage: values.get("percentage"),
76
- fix: values.get("fix"),
77
- };
78
- });
79
- }
80
- //# sourceMappingURL=binding-contract.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"binding-contract.js","sourceRoot":"","sources":["../../src/binding-contract.ts"],"names":[],"mappings":"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"}
@@ -1,5 +0,0 @@
1
- export interface StandardLibrary {
2
- source(header: string): string | undefined;
3
- }
4
- export declare const bundledStandardLibrary: StandardLibrary;
5
- //# sourceMappingURL=std-library.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"std-library.d.ts","sourceRoot":"","sources":["../../src/std-library.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CAC5C;AACD,eAAO,MAAM,sBAAsB,EAAE,eAEpC,CAAC"}
@@ -1,5 +0,0 @@
1
- import { BUNDLED_STD_FILES } from "./std-bundle.js";
2
- export const bundledStandardLibrary = {
3
- source: (header) => BUNDLED_STD_FILES.get(`${header}.hsx`),
4
- };
5
- //# sourceMappingURL=std-library.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"std-library.js","sourceRoot":"","sources":["../../src/std-library.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAIpD,MAAM,CAAC,MAAM,sBAAsB,GAAoB;IACrD,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,iBAAiB,CAAC,GAAG,CAAC,GAAG,MAAM,MAAM,CAAC;CAC3D,CAAC"}
@@ -1,111 +0,0 @@
1
- import type { Entry, Expr, InstrumentDecl } from "./ast.ts";
2
-
3
- export class BindingContractError extends Error {
4
- constructor(
5
- readonly entry: Entry,
6
- message: string,
7
- ) {
8
- super(message);
9
- }
10
- }
11
-
12
- function rows(value: Expr): Entry[] {
13
- if (value.kind === "block") return value.entries;
14
- throw new BindingContractError(
15
- { key: "contract", value, span: value.span },
16
- "Binding contract needs a block.",
17
- );
18
- }
19
- function properties(entry: Entry, allowed: string[]): Map<string, string> {
20
- const result = new Map<string, string>();
21
- for (const slot of rows(entry.value)) {
22
- if (
23
- !allowed.includes(slot.key) ||
24
- result.has(slot.key) ||
25
- (slot.value.kind !== "text" && slot.value.kind !== "name")
26
- )
27
- throw new BindingContractError(
28
- slot,
29
- "Invalid binding contract property.",
30
- );
31
- result.set(slot.key, slot.value.value);
32
- }
33
- if (allowed.some((key) => !result.get(key)))
34
- throw new BindingContractError(
35
- entry,
36
- `Binding contract needs ${allowed.join(", ")}.`,
37
- );
38
- return result;
39
- }
40
- function contractEntries(decl: InstrumentDecl, name: string): Entry[] {
41
- const blocks = decl.body.entries.filter((entry) => entry.key === name);
42
- if (blocks.length > 1)
43
- throw new BindingContractError(blocks[1]!, `Duplicate ${name}.`);
44
- const entries = blocks[0] ? rows(blocks[0].value) : [];
45
- const seen = new Set<string>();
46
- for (const entry of entries) {
47
- if (
48
- seen.has(entry.key) ||
49
- !decl.parameters.some((parameter) => parameter.key === entry.key)
50
- )
51
- throw new BindingContractError(
52
- entry,
53
- "Binding contract must name each declared tunable once.",
54
- );
55
- seen.add(entry.key);
56
- }
57
- return entries;
58
- }
59
-
60
- /** Conditional requirements are header data, independent of module names. */
61
- export function bindingDependencies(decl: InstrumentDecl) {
62
- return contractEntries(decl, "dependencies").map((entry) => {
63
- const values = properties(entry, ["selector", "is", "message", "fix"]);
64
- const selector = decl.parameters.find(
65
- (parameter) => parameter.key === values.get("selector"),
66
- );
67
- const type =
68
- selector?.value.kind === "default"
69
- ? selector.value.type
70
- : selector?.value;
71
- // An optional selector reads like `when every is unset`.
72
- const presence =
73
- (type?.kind === "type" || type?.kind === "call") &&
74
- type.name !== "enum" &&
75
- !!type.optional &&
76
- ["set", "unset"].includes(values.get("is")!);
77
- if (
78
- !presence &&
79
- (type?.kind !== "call" ||
80
- type.name !== "enum" ||
81
- !type.args.some(
82
- (arg) => arg.kind === "name" && arg.value === values.get("is"),
83
- ))
84
- )
85
- throw new BindingContractError(
86
- entry,
87
- "Dependency must select a declared enum choice, or set or unset of an optional tunable.",
88
- );
89
- return {
90
- binding: entry.key,
91
- when: values.get("selector")!,
92
- is: values.get("is")!,
93
- message: values.get("message")!,
94
- fix: values.get("fix")!,
95
- span: entry.span,
96
- };
97
- });
98
- }
99
-
100
- /** A header explains why a typed parameter cannot encode a proposed policy. */
101
- export function parameterDiagnostics(decl: InstrumentDecl) {
102
- return contractEntries(decl, "parameterDiagnostics").map((entry) => {
103
- const values = properties(entry, ["accepts", "percentage", "fix"]);
104
- return {
105
- parameter: entry.key,
106
- accepts: values.get("accepts")!,
107
- percentage: values.get("percentage")!,
108
- fix: values.get("fix")!,
109
- };
110
- });
111
- }
@@ -1,7 +0,0 @@
1
- import { BUNDLED_STD_FILES } from "./std-bundle.ts";
2
- export interface StandardLibrary {
3
- source(header: string): string | undefined;
4
- }
5
- export const bundledStandardLibrary: StandardLibrary = {
6
- source: (header) => BUNDLED_STD_FILES.get(`${header}.hsx`),
7
- };