@hyperscale0/hsx 1.0.342 → 1.0.350
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 +1 -1
- package/bases/cards.hsx +0 -1
- package/bases/collections.hsx +0 -2
- package/bases/escrow.hsx +0 -1
- package/bases/financing.hsx +0 -1
- package/bases/insurance.hsx +0 -1
- package/bases/lending.hsx +0 -3
- package/bases/marketplace.hsx +0 -2
- package/bases/money.hsx +0 -1
- package/bases/reporting.hsx +0 -2
- package/bases/savings.hsx +0 -1
- package/bases/wallet.hsx +0 -1
- package/dist/bin/hsx.d.ts.map +1 -1
- package/dist/bin/hsx.js.map +1 -1
- package/dist/src/ast.d.ts +57 -7
- package/dist/src/ast.d.ts.map +1 -1
- package/dist/src/ast.js.map +1 -1
- package/dist/src/attachment-economics.d.ts.map +1 -1
- package/dist/src/attachment-economics.js.map +1 -1
- package/dist/src/bases-bundle.d.ts.map +1 -1
- package/dist/src/bases-bundle.js +11 -11
- package/dist/src/bases-bundle.js.map +1 -1
- package/dist/src/bases.d.ts.map +1 -1
- package/dist/src/bases.js.map +1 -1
- package/dist/src/catalog-program.d.ts +1 -1
- package/dist/src/catalog-program.d.ts.map +1 -1
- package/dist/src/catalog-program.js +0 -13
- package/dist/src/catalog-program.js.map +1 -1
- package/dist/src/checks.d.ts +60 -0
- package/dist/src/checks.d.ts.map +1 -0
- package/dist/src/checks.js +240 -0
- package/dist/src/checks.js.map +1 -0
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +6 -25
- package/dist/src/cli.js.map +1 -1
- package/dist/src/compile.d.ts +29 -12
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +414 -316
- package/dist/src/compile.js.map +1 -1
- package/dist/src/cost.d.ts.map +1 -1
- package/dist/src/cost.js.map +1 -1
- package/dist/src/diagnostics.d.ts +14 -1
- package/dist/src/diagnostics.d.ts.map +1 -1
- package/dist/src/diagnostics.js +31 -0
- package/dist/src/diagnostics.js.map +1 -1
- package/dist/src/examples-bundle.d.ts.map +1 -1
- package/dist/src/examples-bundle.js +15 -15
- package/dist/src/examples-bundle.js.map +1 -1
- package/dist/src/examples.d.ts.map +1 -1
- package/dist/src/examples.js.map +1 -1
- package/dist/src/format.d.ts.map +1 -1
- package/dist/src/format.js.map +1 -1
- package/dist/src/header-source.d.ts +30 -0
- package/dist/src/header-source.d.ts.map +1 -1
- package/dist/src/header-source.js +120 -9
- package/dist/src/header-source.js.map +1 -1
- package/dist/src/headers.d.ts +9 -18
- package/dist/src/headers.d.ts.map +1 -1
- package/dist/src/headers.js +25 -23
- package/dist/src/headers.js.map +1 -1
- package/dist/src/index.d.ts +4 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/keywords.d.ts +2 -4
- package/dist/src/keywords.d.ts.map +1 -1
- package/dist/src/keywords.js +6 -5
- package/dist/src/keywords.js.map +1 -1
- package/dist/src/language.d.ts +3 -4
- package/dist/src/language.d.ts.map +1 -1
- package/dist/src/language.js +123 -60
- package/dist/src/language.js.map +1 -1
- package/dist/src/lex.d.ts +1 -1
- package/dist/src/lex.d.ts.map +1 -1
- package/dist/src/lex.js +3 -5
- package/dist/src/lex.js.map +1 -1
- package/dist/src/parse.d.ts +3 -1
- package/dist/src/parse.d.ts.map +1 -1
- package/dist/src/parse.js +234 -22
- package/dist/src/parse.js.map +1 -1
- package/dist/src/std-bundle.d.ts +2 -1
- package/dist/src/std-bundle.d.ts.map +1 -1
- package/dist/src/std-bundle.js +16 -15
- package/dist/src/std-bundle.js.map +1 -1
- package/dist/src/template-guide.d.ts +3 -9
- package/dist/src/template-guide.d.ts.map +1 -1
- package/dist/src/template-guide.js +16 -23
- package/dist/src/template-guide.js.map +1 -1
- package/dist/src/toolchain.d.ts +23 -0
- package/dist/src/toolchain.d.ts.map +1 -0
- package/dist/src/toolchain.js +29 -0
- package/dist/src/toolchain.js.map +1 -0
- package/dist/src/tunables.d.ts.map +1 -1
- package/dist/src/tunables.js.map +1 -1
- package/docs/README.md +69 -28
- package/examples/camera-rental.hsx +0 -1
- package/examples/cards.hsx +0 -1
- package/examples/collections.hsx +0 -2
- package/examples/escrow.hsx +0 -1
- package/examples/financing.hsx +0 -1
- package/examples/insurance.hsx +0 -2
- package/examples/lending.hsx +0 -3
- package/examples/library.hsx +0 -3
- package/examples/marketplace.hsx +0 -2
- package/examples/money.hsx +0 -1
- package/examples/rental-hire.hsx +0 -1
- package/examples/reporting.hsx +0 -2
- package/examples/savings.hsx +0 -1
- package/examples/serviced.hsx +0 -4
- package/examples/wallet.hsx +0 -1
- package/package.json +8 -8
- package/src/ast.ts +56 -3
- package/src/bases-bundle.ts +11 -11
- package/src/catalog-program.ts +0 -13
- package/src/checks.ts +386 -0
- package/src/cli.ts +6 -25
- package/src/compile.ts +583 -438
- package/src/diagnostics.ts +49 -1
- package/src/examples-bundle.ts +15 -15
- package/src/header-source.ts +187 -11
- package/src/headers.ts +31 -32
- package/src/index.ts +10 -4
- package/src/keywords.ts +6 -5
- package/src/language.ts +153 -68
- package/src/lex.ts +3 -5
- package/src/parse.ts +216 -24
- package/src/std-bundle.ts +17 -15
- package/src/template-guide.ts +17 -34
- package/src/toolchain.ts +51 -0
- package/std/booking.hsx +14 -1
- package/std/collections.hsx +12 -2
- package/std/financing.hsx +10 -16
- package/std/money.hsx +2 -3
- package/std/reporting.hsx +10 -1
- package/dist/src/binding-contract.d.ts +0 -22
- package/dist/src/binding-contract.d.ts.map +0 -1
- package/dist/src/binding-contract.js +0 -80
- package/dist/src/binding-contract.js.map +0 -1
- package/dist/src/std-library.d.ts +0 -5
- package/dist/src/std-library.d.ts.map +0 -1
- package/dist/src/std-library.js +0 -5
- package/dist/src/std-library.js.map +0 -1
- package/src/binding-contract.ts +0 -111
- package/src/std-library.ts +0 -7
package/src/template-guide.ts
CHANGED
|
@@ -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
|
|
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 {
|
|
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
|
-
|
|
64
|
+
toolchain: Toolchain = defaultToolchain,
|
|
66
65
|
): TemplateIndexEntry[] {
|
|
67
|
-
return headerManifest(
|
|
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
|
-
|
|
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 =
|
|
87
|
+
const source = toolchain.std.get(header);
|
|
90
88
|
if (!source) return undefined;
|
|
91
|
-
const manifest = headerManifest(
|
|
92
|
-
|
|
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,
|
|
104
|
+
const example = exampleProgram(qualifiedName, toolchain);
|
|
107
105
|
const bare = compiledAttachment(
|
|
108
106
|
example.render([]),
|
|
109
107
|
example.attachment,
|
|
110
|
-
|
|
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,
|
|
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
|
|
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(
|
|
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
|
-
|
|
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(
|
package/src/toolchain.ts
ADDED
|
@@ -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
|
-
|
|
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 {
|
package/std/collections.hsx
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
807
|
-
fine {
|
|
808
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 +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"}
|
package/dist/src/std-library.js
DELETED
|
@@ -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"}
|
package/src/binding-contract.ts
DELETED
|
@@ -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
|
-
}
|
package/src/std-library.ts
DELETED